├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cogs ├── developer.py ├── fun.py ├── general.py ├── helper.py ├── listeners.py ├── misc.py ├── moderation.py ├── modmail.py ├── tasks.py └── ticket.py ├── ext ├── consts.py ├── errors.py ├── helpers.py ├── http.py ├── logger.py ├── models.py └── ui │ └── view.py ├── main.py ├── requirements.txt ├── startup.bat ├── startup.sh └── storage ├── README.md ├── Trash.png ├── banned_word.txt ├── banner.png └── fonts ├── Poppins ├── OFL.txt ├── Poppins-Black.ttf ├── Poppins-BlackItalic.ttf ├── Poppins-Bold.ttf ├── Poppins-BoldItalic.ttf ├── Poppins-ExtraBold.ttf ├── Poppins-ExtraBoldItalic.ttf ├── Poppins-ExtraLight.ttf ├── Poppins-ExtraLightItalic.ttf ├── Poppins-Italic.ttf ├── Poppins-Light.ttf ├── Poppins-LightItalic.ttf ├── Poppins-Medium.ttf ├── Poppins-MediumItalic.ttf ├── Poppins-Regular.ttf ├── Poppins-SemiBold.ttf ├── Poppins-SemiBoldItalic.ttf ├── Poppins-Thin.ttf └── Poppins-ThinItalic.ttf └── spotify.ttf /.env.example: -------------------------------------------------------------------------------- 1 | TOKEN = 2 | MODMAIL_WEBHOOK_URL = 3 | GEMINI_API_KEY = 4 | GITHUB_TOKEN = -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 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 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. iOS, Linux, Windows] 24 | 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | 29 | 30 | - [ ] I have checked and made sure that this issue is not a duplicate. 31 | - [ ] I have provided the complete traceback I received as an error. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 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.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | ## Checklist 5 | 6 | 7 | 8 | - [ ] If code changes were made then they have been tested. 9 | - [ ] This PR fixes an issue. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # pycache files 3 | __pycache__/ 4 | 5 | # enviroment variables 6 | .env 7 | 8 | # IDE configuration 9 | .idea 10 | .vscode 11 | 12 | # Virtual Environment 13 | venv/ 14 | .venv/ 15 | env/ 16 | virtualenv/ 17 | 18 | # database files 19 | 20 | database/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Coding Bot V6 is an open sourced discord bot, and we are looking more contributors to improve its code. Please check the wiki section reagarding to [How to Contribute](https://github.com/The-Coding-Realm/coding-bot-v6/wiki/How-To-Contribute) 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 The Coding Realm 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Discord 3 | 4 | 5 | # Coding-Bot-v6 6 | 7 | > The sixth version of the beloved Coding Bot 8 | 9 | ## What is Coding-Bot-v6 10 | 11 | Coding-Bot-v6 is a community bot designed to assist and support programmers within our coding community. It is the sixth version of our bot and offers advanced features and enhancements. The bot provides code-related help, suggestions, snippets, and fun. 12 | 13 | 14 | ## Commands 15 | 16 | | Special Permissions | Category | Name (prefixes) | Description | Cooldown
(`x` uses/`y` seconds per [Member/User/Guild/Channel]) | Usage | 17 | | --------- | --------- | --------- | --------- | --------- | --------- | 18 | | Owner Only | Developer | **Sync** | None | Sync all the slash commands globally | `{prefix}sync` | 19 | | Owner Only | Developer | **load** | Load a Cog. | None | `{prefix}load [cog]` | 20 | | Owner Only | Developer | **unload** | Unload a Cog. | None | `{prefix}unload [cog]` | 21 | | Owner Only | Developer | **reload** | Reload a Cog. | None | `{prefix}reload [cog]` | 22 | | Owner Only | Developer | **loadall** | Load all Cog. | None | `{prefix}loadall` | 23 | | Owner Only | Developer | **unloadall** | Unload all Cog. | None | `{prefix}unloadall` | 24 | | Owner Only | Developer | **reloadall** | Reload all Cog. | None | `{prefix}reloadall` | 25 | | Owner Only | Developer | **getusermetric** | Get User Metrics. | None | `{prefix}getusermetric ` | 26 | | None | Fun | **trash** | Throw someone in the trash. | None | `{prefix}trash ` | 27 | | None | Fun | **number** | Gets a random number. | None | `{prefix}number`: *will get a random number*
`{prefix}number [number]`: *will get the [number]* | 28 | | None | Fun | **meme** | Gets a random meme. | None | `{prefix}meme`: *will get a random meme* | 29 | | None | Fun | **joke** | Gets a random joke. | None | `{prefix}meme`: *will get a random joke* | 30 | | None | Fun | **eightball** | Returns a random response. | None | `{prefix}eightball [question]`: *will return a random response* | 31 | | None | Fun | **token** | Generates a random token. | None | `{prefix}token`: *will generate a token* | 32 | | None | Fun | **binary** | Commands for binary | None |`{prefix}binary` | 33 | | None | Fun | **binary encode** | Encodes plaintext and return binary | None | `{prefix}binary [text]`: *return encoded text* | 34 | | None | Fun | **binary decode** | Decode binary text to plaintext | None | `{prefix}binary [text]`: *return plaintext* | 35 | | None | Fun | **reverse** | Reverse inputted string | None | `{prefix}reverse [string]`: *returns string reversed* | 36 | | None | Fun | **owofy** | Owofy inputted string | None | `{prefix}owofy [text]`: *returns owofy text* | 37 | | None | Fun | **mock** | Mockify inputted string | None | `{prefix}mock [text]`: *returns mocked text* | 38 | | None | Fun | **beerparty** | Start a beerparty 🍻!! | None | `{prefix}beerparty [reason]`: *start a beer party* | 39 | | None | General | **source** (github, code) | Get the source of this bot | 1/1 [channel] | `{prefix}source` *will send link to my source code*
`{prefix}source [command]` *will send link to the source code of the command*
`{prefix}source [command] [subcommand]`: *will send link to the source code of the subcommand* | 40 | | None | General | **define** | Gets deinitions from Urban Dictionary. | 1/5 [channel] | `{prefix}define [word]`: *will send the definition of the word* | 41 | | None | General | **avatar** | Commands for getting avatars. | None | `{prefix}avatar`: *will send a list of available methods* | 42 | | None | General | **avatar main** | Returns the main avatar of a user. | None | `{prefix}avatar main `: *will send the main avatar of the user* | 43 | | None | General | **avatar display** | Returns the display avatar of a user. | None | `{prefix}avatar display `: *will send the display avatar of the user* | 44 | | Helpers Only | Helper | **helper** | Help command for helpers to manage the help channels | None | `{prefix}helper`: *will send a list of available commands* | 45 | | Helpers Only | Helper | **helper warn** | Warns a member breaking rules in help channels | None | `{prefix}helper [reason]`: *will give the member a warning for x reason* | 46 | | Helpers Only | Helper | **helper warnings** | Shows a list of help warnings for a member. | None | `{prefix}helper warnings[ [index]`: *will give the member a warning for x reason* | 48 | | Helpers Only | Helper | **helper ban** | Ban a member from help channels | None | `{prefix}helper [reason]`: *will ban from help channels for x reason* | 49 | | Helpers Only | Helper | **helper unban** | Unban a member from help channels | None | `{prefix}helper `: *will unban from help channels* | 50 | | Helpers Only | Helper | **helper verify** | Help verify a member for help channels | None | `{prefix}verify `: *will verify a member for help channels if the member can't be verified* | 51 | | None | Miscellaneous | **retry** (re) | Reinvoke a command | None | `{prefix}retry`: *will retry a commandy by replying to a message* | 52 | | None | Miscellaneous | **afk** (afk-set, set-afk) | Set your afk status | 1/10 [Member] | `{prefix}afk [reason]`: *will set your afk status to [reason]* | 53 | | None | Miscellaneous | **run** | Run code in a codeblock | None | `{prefix}run [code]`: *will return the result of your code* | 54 | | None | Miscellaneous | **thank** | Thank someone | 1/10 [Member] | `{prefix}thank [reason]`: *will thank for [reason]* | 55 | | Limited to some roles | Miscellaneous | **thank show** | Show the thanks information of a user. | None | `{prefix}thank show `: *will show the thanks information of user* | 56 | | Limited to some roles | Miscellaneous | **thank delete** | Delete a thank. | None | `{prefix}thank delete [thank_id]` : *will delete the thank with the id [thank_id]* | 57 | | None | Miscellaneous | **thank leaderboard** (lb) | Show the thanks leaderboard. | None | `{prefix}thank leaderboard`: *will show the thanks leaderboard* | 58 | | None | Miscellaneous | **trainee** | Sends the trainee help menu. | None | `{prefix}trainee`: *will show the trainee help menu* | 59 | | None | Miscellaneous | **trainee list** | Lists all the trainees in the server. | 1/10 [Member] | `{prefix}list trainees`: *will list all the trainees in the server* | 60 | | None | Miscellaneous | **spotify** (sp) | Shows the spotify status of a member. | 5/60 [User] | `{prefix}spotify`: *will show your spotify status*
`{prefix}spotify `: *will show the spotify status of * | 61 | | Trainee or Higher role | Moderation | **kick** | Kicks a member from the server | None | `{prefix}kick [reason]` : *will kick for [reason]* | 62 | | Trainee or Higher role | Moderation | **ban** | Bans a member from the server | None | `{prefix}ban [reason]` : *will ban for [reason]* | 63 | | Trainee or Higher role | Moderation | **unban** | Unbans a member from the server | None | `{prefix}unban ` : *will unban * | 64 | | Trainee or Higher role | Moderation | **mute** | Timeouts a member from the server. | None | `{prefix}mute [reason]` : *will timeout for because [reason]* | 65 | | Trainee or Higher role | Moderation | **unmute** | Unmutes/removes timeout of a member from the server. | None | `{prefix}mute [reason]` : *will remove timeout for because [reason]* | 66 | | Trainee or Higher role | Moderation | **massban** | Mass bans multiple users from the server | None | `{prefix}massban ...` : *will ban all users inputted* | 67 | | Trainee or Higher role | Moderation | **warn** | Warn a member from the server | None | `{prefix}warn [reason]` : *will warn for [reason]* | 68 | | Trainee or Higher role | Moderation | **Purge** | Purges a number of messages from the current channel | None | `{prefix}purge [amount]` : *will delete [amount] message from current channel* | 69 | | Trainee or Higher role | Moderation | **warnings** | Lists all warnings of a member | None | `{prefix}warnings ` : *will show all warnings for * | 70 | | Trainee or Higher role | Moderation | **clearwarnings** | Clears a certain warning of a member. If no index is provided, it will clear all warnings of a member. | None | `{prefix}clearwarning [index]` : *will clear a warning or all warnings for * | 71 | | Trainee or Higher role | Moderation | **verify** | Verifies a member in the server | None | `{prefix}verify ` : *will verify * | 72 | | Trainee or Higher role | Moderation | **verify** | Verifies a member in the server | None | `{prefix}verify ` : *will verify * | 73 | | None | Moderation | **whois** | Give information about a member | None | `{prefix}whois ` : *will give info about * | 74 | | Has permission (manage_messages) | Moderation | **delete** | Delete a message. Either the message ID can be provided or user can reply to the message. | None | `{prefix}delete [channel] [message]` : *will delete [message] from [channel]* | 75 | | Has permission (manage_messages) | Moderation | **slowmode** (sm) | Sets the slowmode of a channel. | None | `{prefix}slowmode [seconds] [channel]` : *will set slowmode to [seconds] in [channel]* | 76 | | Has permission (administrator) | Moderation | **lockdown** | Lock down the server, requires administrator permissions. | None | `{prefix}lockdown` : *will lockdown whole server (RAID ONLY)* | 77 | | None | Moderation | **welcomer** | Welcome commands | None | `{prefix}welcomer` : *will show welcomer commands* | 78 | | None | Moderation | **welcomer enable** | Enable welcomer | None | `{prefix}welcomer enable` : *will enable welcomer* | 79 | | None | Moderation | **welcomer disable** | Disable welcomer | None | `{prefix}welcomer disable` : *will disable welcomer* | 80 | | None | Moderation | **welcomer redirect** | Change welcome channel | None | `{prefix}welcomer redirect [channel]` : *will change welcomer channel to [channel]* | 81 | | Has permission (manage_messages) | Moderation | **raid-mode** | Raid mode commands | None | `{prefix}raid-mode` : *will show raid mode commands* | 82 | | Has permission (manage_messages) | Moderation | **raid-mode enable** | Enable raid mode | None | `{prefix}raid-mode enable` : *will enable raid mode* | 83 | | Has permission (manage_messages) | Moderation | **raid-mode disable** | Disable raid mode | None | `{prefix}raid-mode disable` : *will disable raid mode* | 84 | 85 | 86 | 87 | ## Self-Hosting: 88 | ### Requirements 89 | * [Python 3.10+](https://www.python.org/downloads/) 90 | * [Modules in requirements.txt](https://github.com/The-Coding-Realm/coding-bot-v6/blob/master/requirements.txt) 91 | 92 | ### Quick Start 93 | ```sh 94 | git clone https://github.com/The-Coding-Realm/coding-bot-v6.git #Clone the repository 95 | cd coding-bot-v6 #Go to the directory 96 | python -m pip install -r requirements.txt #Install required packages 97 | ``` 98 | 99 | ### Configuration 100 | 1. Rename .env.example to .env 101 | 2. Replace the empty space with your token: 102 | ``` 103 | TOKEN = your_token.here 104 | ``` 105 | 106 | 107 | After installing all packages and configuring your bot, start your bot by running `python main.py` 108 | 109 | 110 | ## Contribution 111 | Coding Bot V6 is an open sourced discord bot, and we are looking more contributors to improve its code. Please check the wiki section reagarding to [How to Contribute](https://github.com/The-Coding-Realm/coding-bot-v6/wiki/How-To-Contribute) -------------------------------------------------------------------------------- /cogs/developer.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import traceback 5 | 6 | import discord 7 | from discord.ext import commands 8 | from typing import TYPE_CHECKING, Dict, List, Mapping 9 | 10 | if TYPE_CHECKING: 11 | from ext.models import CodingBot 12 | from types import ModuleType 13 | 14 | 15 | class Developer(commands.Cog, command_attrs=dict(hidden=True)): 16 | hidden = True 17 | 18 | def __init__(self, bot: CodingBot) -> None: 19 | self.bot = bot 20 | 21 | @commands.command(name="sync") 22 | @commands.is_owner() 23 | async def sync(self, ctx: commands.Context[CodingBot]): 24 | """ 25 | Sync all the slash commands globally 26 | 27 | It is not meant to be used after every restart. 28 | 29 | Usage: 30 | ------ 31 | `{prefix}sync` 32 | """ 33 | await self.bot.tree.sync() 34 | await ctx.send("Finished syncing commands globally") 35 | 36 | @commands.command(name="load", aliases=["l"]) 37 | @commands.is_owner() 38 | async def _load(self, ctx: commands.Context[CodingBot], cog_: str): 39 | """ 40 | Load a cog 41 | 42 | Usage: 43 | ------ 44 | `{prefix}load [cog]` 45 | """ 46 | try: 47 | await self.bot.load_extension(cog_) 48 | embed = discord.Embed( 49 | title=f"Successfully loaded extension: `{cog_}`", 50 | color=discord.Color.green(), 51 | ) 52 | except Exception: 53 | embed = discord.Embed( 54 | title=f"Failed to load extension: `{cog_}`", color=discord.Color.red() 55 | ) 56 | embed.description = f"```py\n{traceback.format_exc()}\n```" 57 | 58 | await ctx.send(embed=embed) 59 | 60 | @commands.command(name="unload", aliases=["u"]) 61 | @commands.is_owner() 62 | async def _unload(self, ctx: commands.Context[CodingBot], cog_: str): 63 | """ 64 | Unload a cog 65 | 66 | Usage: 67 | ------ 68 | `{prefix}unload [cog]` 69 | 70 | """ 71 | try: 72 | await self.bot.unload_extension(cog_) 73 | embed = discord.Embed( 74 | title=f"Successfully unloaded extension: `{cog_}`", 75 | color=discord.Color.green(), 76 | ) 77 | except Exception: 78 | embed = discord.Embed( 79 | title=f"Failed to unload extension: `{cog_}`", color=discord.Color.red() 80 | ) 81 | embed.description = f"```py\n{traceback.format_exc()}\n```" 82 | 83 | await ctx.send(embed=embed) 84 | 85 | @commands.command(name="reload", aliases=["r"]) 86 | @commands.is_owner() 87 | async def _reload(self, ctx: commands.Context[CodingBot], cog_: str): 88 | """ 89 | Reload a cog 90 | 91 | Usage: 92 | ------ 93 | `{prefix}reload [cog]` 94 | """ 95 | try: 96 | await self.bot.reload_extension(cog_) 97 | embed = discord.Embed( 98 | title=f"Successfully reloaded extension: `{cog_}`", 99 | color=discord.Color.green(), 100 | ) 101 | except Exception: 102 | embed = discord.Embed( 103 | title=f"Failed to reload extension: `{cog_}`", color=discord.Color.red() 104 | ) 105 | embed.description = f"```py\n{traceback.format_exc()}\n```" 106 | 107 | await ctx.send(embed=embed) 108 | 109 | @commands.command(name="loadall", aliases=["la"]) 110 | @commands.is_owner() 111 | async def _loadall(self, ctx: commands.Context[CodingBot]): 112 | """ 113 | Load all cogs 114 | 115 | Usage: 116 | ------ 117 | `{prefix}loadall` 118 | 119 | """ 120 | data = os.listdir("./cogs") 121 | cogs: Dict[str, List[str]] = {"loaded": [], "not": []} 122 | for cog in data: 123 | if not cog.endswith(".py"): 124 | continue 125 | if f"cogs.{cog[:-3]}" in self.bot.extensions: 126 | continue 127 | try: 128 | await self.bot.load_extension(f"cogs.{cog[:-3]}") 129 | cogs["loaded"].append(f"cogs.{cog[:-3]}") 130 | except discord.DiscordException: 131 | cogs["not"].append(f"cogs.{cog[:-3]}") 132 | embed = discord.Embed( 133 | title="Load all cogs", 134 | description="\n".join( 135 | [ 136 | ("\U00002705" if cog_ in cogs["loaded"] else "\U0000274c") + cog_ 137 | for cog_ in data 138 | if cog_.endswith(".py") 139 | ] 140 | ), 141 | ) 142 | await ctx.send(embed=embed) 143 | 144 | @commands.command(name="unloadall", aliases=["ua", "uall"]) 145 | @commands.is_owner() 146 | async def _unloadall(self, ctx: commands.Context[CodingBot]): 147 | """ 148 | Unload all cogs 149 | 150 | Usage: 151 | ------ 152 | `{prefix}unloadall` 153 | 154 | """ 155 | cogs: Dict[str, List[str]] = {"unloaded": [], "not": []} 156 | processing: Mapping[str, ModuleType] = self.bot.extensions.copy() 157 | for cog in processing: 158 | try: 159 | await self.bot.unload_extension(cog) 160 | cogs["unloaded"].append(cog) 161 | except discord.DiscordException: 162 | cogs["not"].append(cog) 163 | embed = discord.Embed( 164 | title="Unload all cogs", 165 | description="\n".join( 166 | [ 167 | ("\U00002705" if cog_ in cogs["unloaded"] else "\U0000274c") + cog_ 168 | for cog_ in processing 169 | ] 170 | ), 171 | ) 172 | await ctx.send(embed=embed) 173 | 174 | @commands.command(name="reloadall", aliases=["ra", "rall"]) 175 | @commands.is_owner() 176 | async def _reloadall(self, ctx: commands.Context[CodingBot]): 177 | """ 178 | Reload all cogs 179 | 180 | Usage: 181 | ------ 182 | `{prefix}reloadall` 183 | 184 | """ 185 | cogs: Dict[str, List[str]] = {"reloaded": [], "not": []} 186 | processing: Mapping[str, ModuleType] = self.bot.extensions.copy() 187 | for cog in processing: 188 | try: 189 | await self.bot.reload_extension(cog) 190 | cogs["reloaded"].append(cog) 191 | except discord.DiscordException: 192 | cogs["not"].append(cog) 193 | embed = discord.Embed( 194 | title="Reload all cogs", 195 | description="\n".join( 196 | [ 197 | ("\U00002705" if cog_ in cogs["reloaded"] else "\U0000274c") 198 | + f" `{cog_}`" 199 | for cog_ in processing 200 | ] 201 | ), 202 | ) 203 | await ctx.send(embed=embed) 204 | 205 | @commands.command(name="getusermetric", aliases=["gum"], hidden=True) 206 | @commands.is_owner() 207 | async def _getusermetric( 208 | self, ctx: commands.Context[CodingBot], member: discord.Member 209 | ): 210 | """ 211 | Get user metric 212 | 213 | Usage: 214 | ------ 215 | `{prefix}getusermetric [member]` 216 | 217 | """ 218 | member = member or ctx.author 219 | 220 | record = await self.bot.conn.select_record( 221 | "thanks", 222 | table="thanks_info", 223 | arguments=["thanks_count"], 224 | where=["guild_id", "user_id"], 225 | values=[ctx.guild.id, member.id], 226 | ) 227 | total_thank_count = record[0].thanks_count if record else 0 228 | revoked_thank_count = await self.bot.conn.select_record( 229 | "thanks", 230 | table="thanks_data", 231 | arguments=["count(*)"], 232 | where=["guild_id", "user_id", "thank_revoked"], 233 | values=[ctx.guild.id, member.id, 1], 234 | ) 235 | revoked_thank_count = revoked_thank_count[0].count 236 | surviving_thank_count = total_thank_count - revoked_thank_count 237 | 238 | message_metric = await self.bot.conn.select_record( 239 | "metrics", 240 | table="message_metric", 241 | arguments=[ 242 | "user_id", 243 | "guild_id", 244 | "message_count", 245 | "deleted_message_count", 246 | "offline", 247 | "online", 248 | "dnd", 249 | "idle", 250 | ], 251 | where=["user_id", "guild_id"], 252 | values=[member.id, ctx.guild.id], 253 | ) 254 | record = message_metric[0] if message_metric else None 255 | if record: 256 | deleted_message_percent = ( 257 | record.deleted_message_count / record.message_count * 100 258 | ) 259 | actual_message_percent = ( 260 | (record.message_count - record.deleted_message_count) 261 | / record.message_count 262 | * 100 263 | ) 264 | offline_message_percent = record.offline / record.message_count * 100 265 | online_message_percent = record.online / record.message_count * 100 266 | dnd_message_percent = record.dnd / record.message_count * 100 267 | idle_message_percent = record.idle / record.message_count * 100 268 | formatted_message = f""" 269 | **__Message metrics__** For {member.mention}: 270 | \u3164 • **__Total message count__**: {record.message_count} 271 | \u3164 • **__Deleted message count__**: \ 272 | {record.deleted_message_count} (`{deleted_message_percent}%`) 273 | \u3164 • **__Actual message count__**: \ 274 | {record.message_count - record.deleted_message_count} \ 275 | (`{actual_message_percent}%`) 276 | \u3164 • **__Offline message count__**: \ 277 | {record.offline} (`{offline_message_percent}%`) 278 | \u3164 • **__Online message count__**: \ 279 | {record.online} (`{online_message_percent}%`) 280 | \u3164 • **__Dnd message count__**: {record.dnd} (`{dnd_message_percent}%`) 281 | \u3164 • **__Idle message count__**: \ 282 | {record.idle} (`{idle_message_percent}%`) 283 | """ 284 | 285 | revoked_thanks_percent = ( 286 | revoked_thank_count / total_thank_count * 100 287 | if total_thank_count > 0 288 | else 0 289 | ) 290 | actual_thanks_percent = ( 291 | surviving_thank_count / total_thank_count * 100 292 | if total_thank_count > 0 293 | else 0 294 | ) 295 | embed = discord.Embed( 296 | title=f"{member.name}#{member.discriminator} Detailed anaylysis", 297 | description=f"Total thanks this month: {total_thank_count}\n" 298 | f"Revoked thanks this month: {revoked_thank_count} " 299 | f"(`{revoked_thanks_percent}%`)\n" 300 | f"Actual thanks this month: \ 301 | {surviving_thank_count} (`{actual_thanks_percent}%`)" 302 | f'\n{formatted_message if record else ""}', 303 | timestamp=discord.utils.utcnow(), 304 | ) 305 | embed.set_thumbnail(url=member.display_avatar.url) 306 | embed.set_footer( 307 | text=f"Requested by {ctx.author}", 308 | ) 309 | await ctx.send(embed=embed) 310 | 311 | 312 | async def setup(bot: CodingBot): 313 | await bot.add_cog(Developer(bot)) 314 | -------------------------------------------------------------------------------- /cogs/fun.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import random 4 | import time 5 | from io import BytesIO 6 | from typing import TYPE_CHECKING, Optional 7 | 8 | import base64 9 | import discord 10 | from discord.ext import commands 11 | from ext.helpers import create_trash_meme, invert_string 12 | from ext.http import Http 13 | 14 | import asyncio 15 | 16 | if TYPE_CHECKING: 17 | from ext.models import CodingBot 18 | 19 | 20 | class Fun(commands.Cog, command_attrs=dict(hidden=False)): 21 | hidden = False 22 | 23 | def __init__(self, bot: CodingBot) -> None: 24 | self.http = Http(bot.session) 25 | self.bot = bot 26 | 27 | @commands.command(name="trash") 28 | async def trash(self, ctx: commands.Context[CodingBot], *, user: discord.Member = commands.Author): 29 | """ 30 | Throw someone in the trash 31 | Usage: 32 | ------ 33 | `{prefix}trash ` 34 | 35 | """ 36 | resp1 = await ctx.author.display_avatar.read() 37 | resp2 = await user.display_avatar.read() 38 | 39 | avatar_one = BytesIO(resp1) 40 | avatar_two = BytesIO(resp2) 41 | file = await create_trash_meme(avatar_one, avatar_two) 42 | await self.bot.send(ctx, file=file) 43 | 44 | @commands.hybrid_command() 45 | async def number( 46 | self, ctx: commands.Context[CodingBot], start: int = None, end: int = None 47 | ) -> None: 48 | """ 49 | Gets a random number. 50 | Usage: 51 | ------ 52 | `{prefix}number`: *will get a random number b/w 1 & 100* 53 | `{prefix}number [start] [end]`: *will get the random number b/w [start] & [end]* 54 | `{prefix}number [start]`: *will get the random number b/w 1 & [start]* 55 | Args: 56 | `start (Optional[int])`: Number to begin from 57 | `end (Optional[int])`: Number to end at 58 | """ 59 | if start is not None and end is None: 60 | end = start 61 | start = 1 62 | elif start is None: 63 | start = 1 64 | end = 100 65 | randint = random.randint(start, end) 66 | await ctx.bot.reply(ctx, content = randint) 67 | 68 | 69 | @commands.hybrid_command(name="meme") 70 | async def meme(self, ctx: commands.Context[CodingBot]): 71 | meme_json = await self.http.api["get"]["meme"]() 72 | 73 | meme_url = meme_json["url"] 74 | meme_name = meme_json["title"] 75 | meme_poster = meme_json["author"] 76 | meme_sub = meme_json["subreddit"] 77 | 78 | embed = discord.Embed( 79 | title=meme_name, 80 | description=f"Meme by {meme_poster} from subreddit {meme_sub}", 81 | color=discord.Color.random(), 82 | ) 83 | embed.set_footer( 84 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 85 | ) 86 | embed.set_image(url=meme_url) 87 | 88 | await self.bot.reply(ctx, embed=embed) 89 | 90 | @commands.hybrid_command(name="joke") 91 | async def joke(self, ctx: commands.Context[CodingBot]): 92 | """ 93 | Tells a programming joke 94 | 95 | Usage: 96 | ------ 97 | `{prefix}joke`: *will get a random joke* 98 | """ 99 | joke_json = await self.http.api["joke"]["api"]() 100 | category = joke_json["category"] 101 | setup, delivery = None, None 102 | if joke_json["type"] == "single": 103 | setup = joke_json["joke"] 104 | 105 | else: 106 | setup = joke_json["setup"] 107 | delivery = joke_json["delivery"] 108 | 109 | description=setup 110 | description+=f"\n||{delivery}||" if delivery else ... 111 | 112 | embed = self.bot.embed( 113 | title=f"{category} Joke", 114 | description=description, 115 | color=discord.Color.random(), 116 | ) 117 | await self.bot.reply(ctx, embed=embed) 118 | 119 | @commands.hybrid_command(name="8ball") 120 | async def eightball(self, ctx: commands.Context[CodingBot], *, question: str): 121 | responses = [ 122 | "As I see it, yes.", 123 | "Ask again later.", 124 | "Better not tell you now.", 125 | "Cannot predict now.", 126 | "Concentrate and ask again.", 127 | "Do not count on it.", 128 | "It is certain.", 129 | "It is decidedly so.", 130 | "Most likely.", 131 | "My reply is no.", 132 | "My sources say no.", 133 | "Outlook not so good.", 134 | "Outlook good.", 135 | "Reply hazy, try again.", 136 | "Signs point to yes.", 137 | "Very doubtful.", 138 | "Without a doubt.", 139 | "Yes.", 140 | "Yes, definitely.", 141 | "You may rely on it.", 142 | ] 143 | response = random.choice(responses) 144 | 145 | embed = discord.Embed( 146 | title="8ball is answering", 147 | description=f"{question}\nAnswer : {response}", 148 | color=discord.Color.random(), 149 | ) 150 | embed.set_footer( 151 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 152 | ) # Support for nitro users 153 | await self.bot.reply(ctx, embed=embed) 154 | 155 | @commands.hybrid_command(name="token") 156 | async def token(self, ctx: commands.Context[CodingBot]): 157 | first_string = ctx.author.id 158 | middle_string = random.randint(0, 100) 159 | last_string = random.randint(1000000000, 9999999999) 160 | 161 | token_part1 = base64.b64encode(f"{first_string}".encode("utf-8")).decode( 162 | "utf-8" 163 | ) 164 | token_part2 = base64.b64encode(f"{middle_string}".encode("utf-8")).decode( 165 | "utf-8" 166 | ) 167 | token_part3 = base64.b64encode(f"{last_string}".encode("utf-8")).decode("utf-8") 168 | 169 | final_token = f"{token_part1}.{token_part2}.{token_part3}" 170 | 171 | embed = discord.Embed( 172 | title="Ha ha ha, I grabbed your token.", 173 | description=final_token, 174 | color=discord.Color.random(), 175 | ) 176 | embed.set_footer( 177 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 178 | ) 179 | await self.bot.reply(ctx, embed=embed) 180 | 181 | @commands.hybrid_group(invoke_without_command=True) 182 | async def binary(self, ctx: commands.Context[CodingBot]): 183 | embed = discord.Embed( 184 | title="Binary command", 185 | description="Available methods: `encode`, `decode`", 186 | color=discord.Color.random(), 187 | ) 188 | embed.set_footer( 189 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 190 | ) 191 | await self.bot.reply(ctx, embed=embed) 192 | 193 | @binary.command(name="encode") 194 | async def binary_encode(self, ctx: commands.Context[CodingBot], *, string: str): 195 | binary_string = " ".join((map(lambda x: f"{ord(x):08b}", string))) 196 | 197 | embed = discord.Embed( 198 | title="Encoded to binary", 199 | description=binary_string, 200 | color=discord.Color.random(), 201 | ) 202 | embed.set_footer( 203 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 204 | ) 205 | 206 | await self.bot.reply(ctx, embed=embed) 207 | 208 | @binary.command(name="decode") 209 | async def binary_decode(self, ctx: commands.Context[CodingBot], *, binary: str): 210 | if (len(binary) - binary.count(" ")) % 8 != 0: 211 | return await self.bot.reply(ctx, "The binary is an invalid length.") 212 | binary = binary.replace(" ", "") 213 | string = "".join( 214 | chr(int(binary[i : i + 8], 2)) for i in range(0, len(binary), 8) 215 | ) 216 | embed = discord.Embed( 217 | title="Decoded from binary", 218 | description=string, 219 | color=discord.Color.random(), 220 | ) 221 | embed.set_footer( 222 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 223 | ) 224 | 225 | await self.bot.reply(ctx, embed=embed) 226 | 227 | @commands.hybrid_command(name="reverse") 228 | async def reverse(self, ctx: commands.Context[CodingBot], *, text: str): 229 | embed = discord.Embed( 230 | title="Reversed Text", 231 | description=invert_string(text), 232 | color=discord.Color.random(), 233 | ) 234 | embed.set_footer( 235 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 236 | ) 237 | await ctx.send(embed=embed) 238 | 239 | @commands.hybrid_command(name="owofy") 240 | async def owofy(self, ctx: commands.Context[CodingBot], *, text: str): 241 | embed = discord.Embed( 242 | title="Owofied Text", 243 | description=text.replace("o", "OwO"), 244 | color=discord.Color.random(), 245 | ) 246 | embed.set_footer( 247 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 248 | ) 249 | await self.bot.reply(ctx, embed=embed) 250 | 251 | @commands.hybrid_command(name="mock") 252 | async def mock(self, ctx: commands.Context[CodingBot], *, text: str): 253 | embed = discord.Embed( 254 | title="Mocked Text", 255 | description=text.swapcase(), 256 | color=discord.Color.random(), 257 | ) 258 | embed.set_footer( 259 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 260 | ) 261 | await self.bot.reply(ctx, embed=embed) 262 | 263 | @commands.hybrid_command(name="beerparty") 264 | async def _beerparty( 265 | self, ctx: commands.Context, *, reason: commands.clean_content = None 266 | ): 267 | reason = ("\nReason: " + reason) if reason else "" 268 | msg = await ctx.send(f"Open invite to beerparty! React with 🍻 to join!{reason}") 269 | await msg.add_reaction("\U0001f37b") 270 | await asyncio.sleep(60) 271 | msg = await ctx.channel.fetch_message(msg.id) 272 | users = [user async for user in msg.reactions[0].users()] 273 | users.remove(self.bot.user) 274 | if not users: 275 | return await ctx.send("Nobody joined the beerparty :(") 276 | await ctx.send( 277 | ", ".join(user.display_name for user in users) + " joined the beerparty!" 278 | ) 279 | 280 | @commands.command(name="nuke", help="find out yourself") 281 | @commands.cooldown(2, 10, commands.BucketType.guild) 282 | async def _nuke(self, ctx: commands.Context): 283 | msg = await ctx.reply("Nuking the server in 5 seconds!") 284 | await asyncio.sleep(5) 285 | await msg.edit(content="Nuke engaged :smiling_imp:") 286 | await asyncio.sleep(random.randint(1, 3)) 287 | reason = random.choice( 288 | [ 289 | "Swas pooped in his pants", 290 | "umm, I pressed the wrong button", 291 | "I lost the nuke button", 292 | "Arthex is gay", 293 | "Conch prevented it", 294 | "Lex is so hot", 295 | "bcs yes!", 296 | "I lost the launch codes", 297 | "dinner is ready", 298 | "it's bed time", 299 | "you're not cool enough", 300 | "my mumma said so", 301 | "you really thought it would work", 302 | "my dog ate my nuke", 303 | "my boyfriend decided to call FBI", 304 | "I am too lazy", # someone please add more funny phrases 305 | ] 306 | ) 307 | embed = self.bot.embed( 308 | title="Nuke Failed!", description=f"Reason: {reason}", color=0xFF0000 309 | ) 310 | await msg.edit(content=None, embed=embed) 311 | 312 | @commands.hybrid_command(name="math", description="Solve 5 math problems asap") 313 | async def math(self, ctx: commands.Context): 314 | def gen_math(): 315 | operator = random.choice(["+", "-", "*"]) 316 | range_end = 9 if operator == "*" else 99 317 | a = random.randint(1, range_end) 318 | b = random.randint(1, range_end) 319 | prob = f"{a}{operator}{b}" 320 | ans = {"+": a + b, "-": a - b, "*": a * b}[operator] 321 | return prob, ans 322 | 323 | embed = discord.Embed(description="") 324 | embed.set_footer( 325 | text=ctx.author.display_name, icon_url=ctx.author.display_avatar 326 | ) 327 | 328 | view = discord.ui.View() 329 | button = discord.ui.Button(label="Quit", style=discord.ButtonStyle.danger) 330 | 331 | async def callback(interaction: discord.Interaction): 332 | if interaction.user != ctx.author: 333 | return await interaction.response.send_message( 334 | "You can't use this button", ephemeral=True 335 | ) 336 | embed.description = "Quit" 337 | embed.color = discord.Color.red() 338 | await interaction.response.edit_message(embed=embed, view=None) 339 | view.stop() 340 | 341 | button.callback = callback 342 | view.add_item(button) 343 | 344 | msg = await ctx.send(embed=embed, view=view) 345 | delta_time = 0 346 | point = 0 347 | while point < 5: 348 | prob, ans = gen_math() 349 | embed.description = f"{str(prob)}\nCorrect answers: {point}/5" 350 | await msg.edit(embed=embed) 351 | 352 | def check(message): 353 | try: 354 | int(message.content) 355 | except ValueError: 356 | return False 357 | return ( 358 | message.author.id == ctx.author.id 359 | and message.channel.id == ctx.channel.id 360 | ) 361 | 362 | button_task = asyncio.create_task(view.wait()) 363 | message_task = asyncio.create_task( 364 | self.bot.wait_for("message", check=check, timeout=30.0) 365 | ) 366 | start_time = time.time() 367 | done, pending = await asyncio.wait( 368 | [button_task, message_task], return_when=asyncio.FIRST_COMPLETED 369 | ) 370 | end_time = time.time() 371 | first_completed = done.pop() 372 | if first_completed is button_task: 373 | if not (first_completed.result()): 374 | return message_task.cancel() 375 | elif first_completed is message_task: 376 | try: 377 | response = first_completed.result() 378 | except asyncio.TimeoutError: 379 | embed.description = "Timed out" 380 | return await msg.edit(embed=embed, view=None) 381 | 382 | if response.content == str(ans): 383 | point += 1 384 | embed.color = discord.Color.green() 385 | else: 386 | embed.color = discord.Color.red() 387 | 388 | delta_time += end_time - start_time 389 | await response.delete() 390 | await msg.edit(embed=embed) 391 | 392 | embed.description = f"5/5 | took {str(round(delta_time, 5))}s" 393 | button.disabled = True 394 | await msg.edit(embed=embed, view=view) 395 | 396 | 397 | async def setup(bot: CodingBot): 398 | await bot.add_cog(Fun(bot)) 399 | -------------------------------------------------------------------------------- /cogs/general.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import inspect 4 | import os 5 | from typing import TYPE_CHECKING, List 6 | 7 | import discord 8 | from discord.ext import commands 9 | from ext.helpers import UrbanDefinition, UrbanDictionary 10 | 11 | from ext.consts import STAFF_UPDATE_CHANNEL_ID 12 | 13 | if TYPE_CHECKING: 14 | from ext.models import CodingBot 15 | 16 | 17 | class General(commands.Cog, command_attrs=dict(hidden=False)): 18 | hidden = False 19 | 20 | def __init__(self, bot: CodingBot) -> None: 21 | self.bot = bot 22 | self.ud = UrbanDictionary(bot.session) 23 | 24 | @commands.hybrid_command(name="source", aliases=["github", "code"]) 25 | @commands.cooldown(1, 1, commands.BucketType.channel) 26 | async def _source( 27 | self, ctx: commands.Context[CodingBot], *, command: str = None 28 | ) -> None: 29 | """ 30 | Displays my full source code or for a specific command. 31 | To display the source code of a subcommand you can separate it by 32 | periods or spaces. 33 | 34 | Usage: 35 | ------ 36 | `{prefix}source` *will send link to my source code* 37 | `{prefix}source [command]` *will send link to the source code of the command* 38 | `{prefix}source [command] [subcommand]`: 39 | *will send link to the source code of the subcommand* 40 | """ 41 | github = "https://i.imgur.com/NFwIx3d.png" 42 | embed = discord.Embed(color = 0xfffffe) 43 | source_url = "https://github.com/The-Coding-Realm/coding-bot-v6" 44 | branch = "master" 45 | if command is None: 46 | embed.url = source_url 47 | return await self.bot.reply(ctx, embed=embed) 48 | 49 | if command == "help": 50 | src = type(self.bot.help_command) 51 | module = src.__module__ 52 | filename = inspect.getsourcefile(src) 53 | if help_command := self.bot.get_command("help"): 54 | embed.description = help_command.help.format(prefix=ctx.prefix) 55 | else: 56 | obj = self.bot.get_command(command.replace(".", " ")) 57 | if obj is None: 58 | embed = discord.Embed( 59 | title="Error 404", description=f"Command `{command}` not found." 60 | ) 61 | return await self.bot.reply(ctx, embed=embed) 62 | 63 | if obj.help: 64 | embed.description = obj.help.format(prefix=ctx.prefix) 65 | 66 | src = obj.callback.__code__ 67 | module = obj.callback.__module__ 68 | filename = src.co_filename 69 | 70 | lines, firstlineno = inspect.getsourcelines(src) 71 | if not module.startswith("discord"): 72 | # not a built-in command 73 | location = os.path.relpath(filename).replace("\\", "/") 74 | else: 75 | location = module.replace(".", "/") + ".py" 76 | source_url = "https://github.com/Rapptz/discord.py" 77 | branch = "master" 78 | 79 | final_url = ( 80 | f"{source_url}/blob/{branch}/{location}#L{firstlineno}-L" 81 | f"{firstlineno + len(lines) - 1}" 82 | ) 83 | embed.set_author( 84 | name = "GitHub (Click Here)", 85 | icon_url = github, 86 | url = final_url 87 | ) 88 | await self.bot.reply(ctx, embed=embed) 89 | 90 | @commands.hybrid_command(name="define") 91 | @commands.cooldown(1, 5, commands.BucketType.user) 92 | async def define(self, ctx: commands.Context[CodingBot], *, word: str): 93 | """ 94 | Gets deinitions from Urban Dictionary. 95 | 96 | Usage: 97 | ------ 98 | `{prefix}define [word]` *will send the definition of the word* 99 | """ 100 | definition: List[UrbanDefinition] = await self.ud.define(word) 101 | if not definition: 102 | return await ctx.send(f"Could not find definition for `{word}`") 103 | definition = definition[0] 104 | embed = discord.Embed( 105 | title=f"Definition of {word}", 106 | description=f"**Meaning**: {definition.meaning}\n", 107 | color=discord.Color.random(), 108 | ) 109 | embed.description += ( 110 | f"\n**Example:** {definition.example}\n\n{definition.author}" 111 | ) 112 | embed.set_footer( 113 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 114 | ) 115 | await self.bot.reply(ctx, embed=embed) 116 | 117 | @commands.hybrid_group(invoke_without_command=True) 118 | async def avatar(self, ctx: commands.Context[CodingBot]): 119 | """ 120 | Commands for getting avatars. 121 | 122 | Usage: 123 | ------ 124 | `{prefix}avatar` *will send a list of available methods* 125 | """ 126 | embed = discord.Embed( 127 | title="Avatar command", description="Available methods: `main`, `display`" 128 | ) 129 | embed.set_footer( 130 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 131 | ) 132 | await self.bot.reply(ctx, embed=embed) 133 | 134 | @avatar.command(name="main") 135 | async def avatar_main( 136 | self, ctx: commands.Context[CodingBot], member: discord.Member 137 | ): 138 | """ 139 | Returns the main avatar of a user. 140 | 141 | Usage: 142 | ------ 143 | `{prefix}avatar main [user]` *will send the main avatar of the user* 144 | """ 145 | embed = discord.Embed( 146 | title=f"{member}'s Main Avatar", 147 | description=f"Showing {member.mention}'s Main Avatar", 148 | color=discord.Color.random(), 149 | ) 150 | embed.set_image(url=member.avatar.url) 151 | embed.set_footer( 152 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 153 | ) 154 | await self.bot.reply(ctx, embed=embed) 155 | 156 | @avatar.command(name="display") 157 | async def avatar_display( 158 | self, ctx: commands.Context[CodingBot], member: discord.Member 159 | ): 160 | """ 161 | Returns the display avatar of a user. 162 | 163 | Usage: 164 | ------ 165 | `{prefix}avatar display [user]` *will send the display avatar of the user* 166 | """ 167 | embed = discord.Embed( 168 | title=f"{member}'s Avatar", 169 | description=f"Showing {member.mention}'s Avatar", 170 | color=discord.Color.random(), 171 | ) 172 | embed.set_image(url=member.display_avatar.url) 173 | embed.set_footer( 174 | text=f"Requested by {ctx.author}", icon_url=ctx.author.display_avatar.url 175 | ) 176 | await self.bot.reply(ctx, embed=embed) 177 | 178 | @commands.command(name="update-staff-list") 179 | @commands.has_permissions(administrator=True) 180 | async def update_staff_list(self, ctx): 181 | staff_list_channel = self.bot.get_channel(STAFF_UPDATE_CHANNEL_ID) 182 | mod_roles = [ 183 | 681895373454835749, 184 | 838634262693412875, 185 | 725899526350831616, 186 | 795136568805294097, 187 | 729530191109554237, 188 | 681895900070543411, 189 | 729537643951554583, 190 | ] 191 | embed = discord.Embed(title="**Staff List**", color=0x2F3136) 192 | embed.description = "" 193 | for r in mod_roles: 194 | r = ctx.guild.get_role(r) 195 | valid_members = [] 196 | for m in r.members: 197 | admin = m.top_role.name == "Admin Perms" and r.name == "Admin" 198 | motm = m.top_role.id == 760763132158803968 199 | if m.top_role == r or admin or motm: 200 | valid_members.append(m) 201 | 202 | 203 | embed.description += f"{r.mention} | **{len(valid_members)}** \n" 204 | 205 | for m in valid_members: 206 | embed.description += f"> `{m.id}` {m.mention}\n" 207 | embed.description += "\n" 208 | await staff_list_channel.purge(limit=1) 209 | await staff_list_channel.send(embed=embed) 210 | embed = self.bot.embed(description="Done ✅", color=0x00FF00) 211 | await self.bot.send(ctx, embed=embed) 212 | 213 | 214 | async def setup(bot: CodingBot) -> None: 215 | await bot.add_cog(General(bot)) 216 | -------------------------------------------------------------------------------- /cogs/helper.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import asyncio 4 | import contextlib 5 | import datetime 6 | from io import BytesIO 7 | from typing import TYPE_CHECKING, Any, Optional 8 | 9 | import discord 10 | import humanize 11 | from discord.ext import commands 12 | from ext.consts import ( 13 | HELP_BAN_ROLE_ID, 14 | OFFICIAL_HELPER_ROLE_ID, 15 | READ_HELP_RULES_ROLE_ID, 16 | TCR_GUILD_ID, 17 | ) 18 | from ext.ui.view import ConfirmButton 19 | 20 | if TYPE_CHECKING: 21 | from ext.models import CodingBot 22 | 23 | 24 | class Helper(commands.Cog, command_attrs=dict(hidden=False)): 25 | hidden = False 26 | 27 | def __init__(self, bot): 28 | self.bot = bot 29 | 30 | async def cog_check(self, ctx: commands.Context[CodingBot]) -> bool: 31 | """ 32 | Restricts the use of the cog to the official helper role 33 | 34 | Parameters 35 | ---------- 36 | ctx : commands.Context[CodingBot] 37 | The context of the command 38 | 39 | Returns 40 | ------- 41 | bool 42 | Whether the user has the required role 43 | 44 | Raises 45 | ------ 46 | commands.CheckFailure 47 | If the user doesn't have the required role 48 | 49 | """ 50 | if isinstance(ctx.channel, discord.DMChannel): 51 | return False 52 | if ctx.guild.id != TCR_GUILD_ID: 53 | return False 54 | 55 | official_helper_role = ctx.guild.get_role(OFFICIAL_HELPER_ROLE_ID) 56 | 57 | return official_helper_role in ctx.author.roles 58 | 59 | async def capture_evidence( 60 | self, ctx: commands.Context[CodingBot] 61 | ) -> Optional[discord.Attachment]: 62 | """ 63 | Captures the evidence for some moderation action taken against a member 64 | 65 | Parameters 66 | ---------- 67 | ctx : commands.Context[CodingBot] 68 | The context of the command 69 | 70 | Returns 71 | ------- 72 | Optional[discord.Attachment] 73 | The evidence that was captured 74 | 75 | """ 76 | view = ConfirmButton(ctx) 77 | view.message = await ctx.author.send( 78 | "Do you want to provide an evidence for your action?", view=view 79 | ) 80 | view_touched = not (await view.wait()) 81 | evidence_byts = None 82 | if view_touched and view.confirmed: 83 | initial_mess = await ctx.author.send( 84 | "Please send the evidence in the form of an attachment." 85 | ) 86 | try: 87 | wait_mess = await self.bot.wait_for( 88 | "message", 89 | check=lambda m: m.author == ctx.author 90 | and m.channel == initial_mess.channel 91 | and m.attachments 92 | and not m.guild, 93 | timeout=60, 94 | ) 95 | except asyncio.TimeoutError: 96 | await initial_mess.delete() 97 | await ctx.author.send( 98 | "You didn't send any evidence in time. " 99 | "Proceeding with the ban without evidence." 100 | ) 101 | else: 102 | evidence_byts = await wait_mess.attachments[0].read() 103 | return evidence_byts 104 | 105 | async def log( 106 | self, 107 | *, 108 | action: str, 109 | undo: bool = False, 110 | member: discord.Member, 111 | helper: discord.Member, 112 | reason: Optional[str] = None, 113 | duration: Optional[datetime.timedelta] = None, 114 | **kwargs: Any, 115 | ) -> None: 116 | """ 117 | A function that logs all moderation commands executed 118 | 119 | Parameters 120 | ---------- 121 | action : str 122 | The action that was performed 123 | moderator : discord.Member 124 | The moderator who performed the action 125 | member : discord.Member 126 | The member who was affected by the action 127 | undo : bool 128 | Whether the action was undone 129 | reason : Optional[str] 130 | The reason for the action 131 | duration : Optional[datetime.timedelta] 132 | The duration of the action 133 | """ 134 | definition = { 135 | "ban": { 136 | "action": "banned from help channels", 137 | "undo_action": "unbanned from help channels", 138 | "color": discord.Color.red(), 139 | "icon": ":hammer:", 140 | "undo_icon": ":unlock:", 141 | }, 142 | "mute": { 143 | "action": "muted from help channels", 144 | "undo_action": "unmuted from help channels", 145 | "color": discord.Color.light_grey(), 146 | "icon": ":mute:", 147 | "undo_icon": ":loud_sound:", 148 | }, 149 | "warn": { 150 | "action": "warned", 151 | "undo_action": "removed warning " 152 | f"(`{kwargs.get('warning') or 'removed all warnings'}`)", 153 | "icon": ":warning:", 154 | "undo_icon": ":flag_white:", 155 | "color": discord.Color.yellow(), 156 | }, 157 | } 158 | 159 | action_info = definition.get(action, ValueError(f"Invalid action: {action}")) 160 | if isinstance(action_info, ValueError): 161 | raise action_info 162 | 163 | undo_action = action_info.get("undo_action") 164 | 165 | if undo and isinstance(undo_action, ValueError): 166 | raise undo_action 167 | 168 | action_string = action_info["undo_action"] if undo else action_info["action"] 169 | icon = action_info["undo_icon"] if undo else action_info["icon"] 170 | color = discord.Color.green() if undo else action_info.get("color") 171 | 172 | embed = discord.Embed(color=color, timestamp=discord.utils.utcnow()) 173 | 174 | embed.description = ( 175 | f"{icon} **Action:** {action_string.title()}\n**Reason:** {reason}\n" 176 | ) 177 | if duration: 178 | embed.description += "**Duration:** {}\n".format( 179 | humanize.naturaldelta(duration, minimum_unit="seconds") 180 | ) 181 | file = None 182 | if evidence := kwargs.get("evidence"): 183 | embed.description += "\n**Evidence provided below:**" 184 | buffer = BytesIO(evidence) 185 | buffer.seek(0) 186 | file = discord.File(buffer, filename=f"evidence_{member.id}.png") 187 | embed.set_image(url=f"attachment://evidence_{member.id}.png") 188 | else: 189 | embed.description += "\n**No evidence was provided.**" 190 | embed.set_author( 191 | name=f"{helper} (ID: {helper.id})", icon_url=helper.display_avatar.url 192 | ) 193 | embed.set_thumbnail(url=member.display_avatar.url) 194 | logs = self.bot.get_channel(964165082437263361) # 816512034228666419 195 | await logs.send(embed=embed, file=file) # type: ignore 196 | 197 | @commands.hybrid_group(name="helper") 198 | async def helper(self, ctx: commands.Context[CodingBot]) -> None: 199 | """ 200 | Help command for helpers to manage the help channels 201 | """ 202 | await ctx.send_help(ctx.command) 203 | 204 | @helper.command(name="warn") 205 | async def help_warn( 206 | self, ctx: commands.Context[CodingBot], member: discord.Member, *, reason: str 207 | ) -> None: 208 | """ 209 | Warns a member breaking rules in help channels. 210 | 211 | Usage: 212 | {prefix}helper warn 213 | 214 | Example: 215 | {prefix}helper warn {member} "Breaking rules" 216 | """ 217 | if len(reason) > 256: 218 | return await self.bot.reply( 219 | ctx, "The reason must be less than 256 characters." 220 | ) 221 | 222 | await self.bot.conn.insert_record( 223 | "warnings", 224 | table="help_warns", 225 | columns=("guild_id", "user_id", "helper_id", "reason", "date"), 226 | values=( 227 | ctx.guild.id, 228 | member.id, 229 | ctx.author.id, 230 | reason, 231 | ctx.message.created_at.timestamp(), 232 | ), 233 | ) 234 | await self.bot.reply(ctx, f"Help-warned {member.mention}") 235 | evidence = await self.capture_evidence(ctx) 236 | await self.log( 237 | action="warn", 238 | member=member, 239 | helper=ctx.author, 240 | reason=reason, 241 | evidence=evidence, 242 | ) 243 | 244 | @helper.command(name="warnings") 245 | async def help_warnings( 246 | self, ctx: commands.Context[CodingBot], member: discord.Member 247 | ) -> None: 248 | """ 249 | Shows a list of help warnings for a member. 250 | 251 | Usage: 252 | {prefix}helper warnings 253 | 254 | """ 255 | embed = discord.Embed( 256 | title=f"{member} Help warnings List", color=discord.Color.red() 257 | ) 258 | records = await self.bot.conn.select_record( 259 | "warnings", 260 | arguments=("reason", "helper_id", "date"), 261 | table="help_warns", 262 | where=("guild_id", "user_id"), 263 | values=(ctx.guild.id, member.id), 264 | extras=["ORDER BY date DESC"], 265 | ) 266 | if not records: 267 | return await self.bot.reply(ctx, f"{member.mention} has no help-warnings.") 268 | 269 | for i, warning in enumerate(records, 1): 270 | helper = ctx.guild.get_member(warning.helper_id) 271 | helper = helper.mention if helper else "Unknown" 272 | embed.add_field( 273 | name=f"`{i}.` Reason: {warning.reason}", 274 | value=f"Issued by: {helper} - ", 275 | inline=False, 276 | ) 277 | 278 | await self.bot.reply(ctx, embed=embed) 279 | 280 | @helper.command(name="clearwarning", aliases=["chw"]) 281 | async def help_clearwarning( 282 | self, 283 | ctx: commands.Context[CodingBot], 284 | member: discord.Member, 285 | index: int = None, 286 | ) -> None: 287 | """ 288 | Clears a help warning from a member. 289 | 290 | Usage: 291 | {prefix}helper clearwarning [index] 292 | """ 293 | warn = None 294 | 295 | target = member or ctx.author 296 | if index is None: 297 | await self.bot.conn.delete_record( 298 | "warnings", 299 | table="help_warns", 300 | where=("guild_id", "user_id"), 301 | values=(ctx.guild.id, target.id), 302 | ) 303 | else: 304 | records = await self.bot.conn.select_record( 305 | "warnings", 306 | arguments=("date", "reason"), 307 | table="help_warns", 308 | where=("guild_id", "user_id"), 309 | values=(ctx.guild.id, target.id), 310 | extras=["ORDER BY date DESC"], 311 | ) 312 | 313 | if not records: 314 | return await self.bot.reply(ctx, f"{target.mention} has no warnings.") 315 | 316 | for i, sublist in enumerate(records, 1): 317 | if index == i: 318 | warn = sublist.reason 319 | await self.bot.conn.delete_record( 320 | "warnings", 321 | table="help_warns", 322 | where=("guild_id", "user_id", "date"), 323 | values=(ctx.guild.id, target.id, sublist.date), 324 | ) 325 | break 326 | 327 | await self.bot.reply(ctx, f"{target.mention}'s warning was cleared.") 328 | await self.log( 329 | action="warn", undo=True, member=target, helper=ctx.author, warn=warn 330 | ) 331 | 332 | @helper.command(name="ban") 333 | async def help_ban( 334 | self, ctx: commands.Context[CodingBot], member: discord.Member, *, reason: str 335 | ) -> None: 336 | """ 337 | Ban someone from help channels 338 | 339 | Usage: 340 | {prefix}helper ban 341 | """ 342 | help_ban_role = ctx.guild.get_role(HELP_BAN_ROLE_ID) 343 | read_help_rules_role = ctx.guild.get_role(READ_HELP_RULES_ROLE_ID) 344 | if help_ban_role in member.roles: 345 | return await self.bot.reply(ctx, f"{member.mention} is already help-banned") 346 | 347 | if read_help_rules_role in member.roles: 348 | await member.remove_roles(read_help_rules_role) 349 | if help_ban_role not in member.roles: 350 | await member.add_roles(help_ban_role) 351 | 352 | await self.bot.reply(ctx, f"help-banned {member.mention} with reason: {reason}") 353 | with contextlib.suppress(discord.Forbidden): 354 | await member.send(f"You have been help-banned with reason: {reason}") 355 | 356 | @helper.command(name="unban") 357 | async def help_unban( 358 | self, ctx: commands.Context[CodingBot], member: discord.Member 359 | ) -> None: 360 | """ 361 | Unban someone from help channels 362 | 363 | Usage: 364 | {prefix}helper unban 365 | """ 366 | help_ban_role = ctx.guild.get_role(HELP_BAN_ROLE_ID) 367 | read_help_rules_role = ctx.guild.get_role(READ_HELP_RULES_ROLE_ID) 368 | if help_ban_role not in member.roles: 369 | return await self.bot.reply(ctx, f"{member.mention} is not help-banned") 370 | 371 | if read_help_rules_role not in member.roles: 372 | await member.add_roles(read_help_rules_role) 373 | if help_ban_role in member.roles: 374 | await member.remove_roles(help_ban_role) 375 | 376 | await self.bot.reply(ctx, f"help-unbanned {member.mention}") 377 | with contextlib.suppress(discord.Forbidden): 378 | await member.send("You have been help-unbanned") 379 | await self.log(action="ban", undo=True, member=member, helper=ctx.author) 380 | 381 | @helper.command(name="verify") 382 | async def help_verify( 383 | self, ctx: commands.Context[CodingBot], target: discord.Member 384 | ) -> None: 385 | """ 386 | Help verify a member 387 | 388 | Usage: 389 | {prefix}helper verify 390 | """ 391 | read_help_rules_role = ctx.guild.get_role(READ_HELP_RULES_ROLE_ID) 392 | 393 | if read_help_rules_role in target.roles: 394 | embed = discord.Embed( 395 | title="ERROR!", description=f"{target.mention} is already verified" 396 | ) 397 | embed.set_footer( 398 | text=f"Command executed by {ctx.author}", 399 | icon_url=ctx.author.display_avatar.url, 400 | ) 401 | else: 402 | embed = discord.Embed( 403 | title="Member verified", 404 | description=f"{target.mention} was successfully verified", 405 | ) 406 | embed.set_footer( 407 | text=f"Command executed by {ctx.author}", 408 | icon_url=ctx.author.display_avatar.url, 409 | ) 410 | await target.add_roles(read_help_rules_role) 411 | 412 | await self.bot.reply(ctx, embed=embed) 413 | 414 | 415 | async def setup(bot): 416 | await bot.add_cog(Helper(bot)) 417 | -------------------------------------------------------------------------------- /cogs/listeners.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | import sys 5 | import time as unitime 6 | import asyncio 7 | import contextlib 8 | import traceback 9 | from datetime import datetime 10 | from typing import TYPE_CHECKING 11 | 12 | import discord 13 | from datetime import timezone 14 | from discord.ext import commands 15 | from ext.consts import TCR_STAFF_ROLE_ID 16 | from ext.helpers import check_invite 17 | from ext.errors import InsufficientPrivilegeError 18 | 19 | if TYPE_CHECKING: 20 | from ext.models import CodingBot 21 | 22 | 23 | class ListenerCog(commands.Cog, command_attrs=dict(hidden=True)): 24 | hidden = True 25 | 26 | def __init__(self, bot: CodingBot) -> None: 27 | self.bot = bot 28 | 29 | def valid_gh_sect(name: str): 30 | cons_set = zip(name, name[1:]) 31 | for s in cons_set: 32 | if s == ("-", "-"): 33 | return False 34 | if name.startswith("-") or name.endswith("-"): 35 | return False 36 | return name.replace("-", "").isalnum() 37 | 38 | self.valid_gh_sect = valid_gh_sect 39 | 40 | # @commands.Cog.listener("on_message") 41 | # async def thank_message(self, message: discord.Message): 42 | # """ 43 | # Responsible for checking if a user is saying thanks in a help channel. 44 | # if so, the bot will inform the user that they can use the thank command 45 | # to support the user that helped them. 46 | 47 | # Parameters 48 | # ---------- 49 | # message : discord.Message 50 | # The message that was sent. 51 | # """ 52 | # if message.author.bot or not message.guild: 53 | # return 54 | 55 | # if message.channel.category.id == 754710748353265745 and ( 56 | # "thanks" in message.content.lower() 57 | # or "thank you" in message.content.lower() 58 | # or "thx" in message.content.lower() 59 | # or "thnx" in message.content.lower() 60 | # ): 61 | # await message.reply( 62 | # "If someone has helped you, " 63 | # "you can thank them by using the `.thank` command." 64 | # ) 65 | 66 | @commands.Cog.listener("on_message") 67 | async def check_cat_message(self, message: discord.Message): 68 | """ 69 | Checks if a message has 'cat' or 'placeholder' in it and reacts with '' 70 | """ 71 | if 'cat' in message.content.lower().split() or 'placeholder' in message.content.lower().split(): 72 | await message.add_reaction('') 73 | 74 | @commands.Cog.listener("on_message") 75 | async def afk_user_messaage(self, message: discord.Message): 76 | """ 77 | Responsible for checking if a message was sent by an AFK user. 78 | If so, the bot will send a message to the channel informing the user 79 | that they are no longer AFK. 80 | It will also remove the [AFK] tag from the user's name. 81 | 82 | Parameters 83 | ---------- 84 | message : discord.Message 85 | The message that was sent. 86 | """ 87 | if message.author.bot or not message.guild: 88 | return 89 | record = self.bot.afk_cache.get(message.guild.id) 90 | if record: 91 | record = record.get(message.author.id) 92 | if record: 93 | _, time = record 94 | if (unitime.time() - time) < 30: 95 | return 96 | await self.bot.conn.delete_record( 97 | "afk", 98 | table="afk", 99 | where=("user_id",), 100 | values=(message.author.id,), 101 | ) 102 | with contextlib.suppress(discord.HTTPException, discord.Forbidden): 103 | if "[AFK]" in message.author.display_name: 104 | name = message.author.display_name.split(" ")[1:] 105 | await message.author.edit(nick=" ".join(name)) 106 | # staff_role = message.guild.get_role(795145820210462771) 107 | # if staff_role and staff_role in message.author.roles: 108 | # on_pat_staff = message.guild.get_role(726441123966484600) 109 | # with contextlib.suppress(discord.Forbidden, discord.HTTPException): 110 | # await message.author.add_roles(on_pat_staff) 111 | del self.bot.afk_cache[message.guild.id][message.author.id] 112 | em = discord.Embed( 113 | description=f"{message.author.mention} Welcome back, " 114 | "I removed your AFK!", 115 | color=discord.Color.dark_gold(), 116 | ) 117 | msg = await message.reply(embed=em) 118 | await asyncio.sleep(5) 119 | await msg.delete() 120 | 121 | @commands.Cog.listener("on_message") 122 | async def user_mentioned(self, message: discord.Message): 123 | """ 124 | Responsible for checking if an AFK user was mentioned in a message. 125 | If so, the bot will send a message to the channel informing that the user 126 | that was mentioned is AFK. 127 | 128 | Parameters 129 | ---------- 130 | message : discord.Message 131 | The message that was sent. 132 | """ 133 | if message.author.bot or not message.guild: 134 | return 135 | if message.mentions: 136 | for member in message.mentions: 137 | record = self.bot.afk_cache.get(message.guild.id) 138 | if record: 139 | record = record.get(member.id) 140 | if record: 141 | reason, time_ = record 142 | em = discord.Embed( 143 | description=f"{member.mention} is AFK: {reason} " 144 | f"()", 145 | color=discord.Color.dark_blue(), 146 | ) 147 | await message.reply(embed=em) 148 | 149 | @commands.Cog.listener() 150 | async def on_message_edit( 151 | self, before: discord.Message, after: discord.Message 152 | ) -> None: 153 | """ 154 | Responsible for checking if a message was edited. 155 | If so, the bot will check if message cache of bot has exceeded 200. 156 | If so, the bot will clear the cache. 157 | 158 | Parameters 159 | ---------- 160 | message_before : discord.Message 161 | The message before the edit. 162 | msg : discord.Message 163 | The message after the edit. 164 | """ 165 | if after.author.bot: 166 | return 167 | if len(self.bot.message_cache) > 200: 168 | self.bot.message_cache.clear() 169 | await self.bot.process_edit(before, after) 170 | 171 | @commands.Cog.listener() 172 | async def on_command_error(self, ctx: commands.Context, error: Exception) -> None: 173 | """ 174 | Handles errors for commands during command invocation. 175 | Errors that are not handled by this function are printed to stderr. 176 | """ 177 | if isinstance(error, commands.CommandNotFound): 178 | return 179 | if isinstance(error, commands.CommandInvokeError): 180 | error = error.original 181 | if isinstance(error, InsufficientPrivilegeError): 182 | embed = discord.Embed( 183 | title="Insufficient Privilege, ", 184 | description=error.message, 185 | color=discord.Color.red(), 186 | ) 187 | return await ctx.send(embed=embed, ephemeral=True) 188 | elif isinstance(error, commands.CommandOnCooldown): 189 | embed = discord.Embed( 190 | title="Command on Cooldown", 191 | description=f"{ctx.author.mention} Please wait {error.retry_after:.2f}" 192 | " seconds before using this command again.", 193 | color=discord.Color.red(), 194 | ) 195 | return await ctx.send(embed=embed, ephemeral=True) 196 | else: 197 | print(f"Ignoring exception in command {ctx.command}:", file=sys.stderr) 198 | traceback.print_exception( 199 | type(error), error, error.__traceback__, file=sys.stderr 200 | ) 201 | 202 | @commands.Cog.listener("on_message") 203 | async def track_sent_message(self, message: discord.Message): 204 | """ 205 | Responsible for tracking staff messages. 206 | """ 207 | 208 | if message.author.bot or not message.guild: 209 | return 210 | 211 | values = [message.author.id, message.guild.id, 1, 1] 212 | columns = [ 213 | "user_id", 214 | "guild_id", 215 | "message_count", 216 | status := message.author.status.name, 217 | ] 218 | 219 | staff_role = message.guild.get_role(TCR_STAFF_ROLE_ID) 220 | if staff_role and staff_role in message.author.roles: # type: ignore 221 | columns.append("is_staff") 222 | values.append(1) 223 | else: 224 | pass 225 | 226 | return await self.bot.conn.insert_record( 227 | "metrics", 228 | table="message_metric", 229 | columns=columns, 230 | values=values, 231 | extras=[ 232 | "ON CONFLICT (user_id, guild_id) DO UPDATE SET message_count = " 233 | f"message_count + 1, {status} = {status} + 1" 234 | ], 235 | ) 236 | 237 | @commands.Cog.listener("on_message_delete") 238 | async def track_deleted_message(self, message: discord.Message): 239 | """ 240 | Responsible for tracking user messages. 241 | """ 242 | 243 | if message.author.bot or not message.guild: 244 | return 245 | 246 | values = [message.author.id, message.guild.id] 247 | 248 | columns = ["deleted_message_count = deleted_message_count + 1"] 249 | 250 | await self.bot.conn.update_record( 251 | "metrics", 252 | table="message_metric", 253 | to_update=columns, 254 | where=["user_id", "guild_id"], 255 | values=values, 256 | ) 257 | 258 | @commands.Cog.listener("on_message_edit") 259 | async def invite_in_message_edit(self, _: discord.Message, after: discord.Message): 260 | """ 261 | Responsible for tracking member joins. 262 | """ 263 | if after.author.bot or not after.guild: 264 | return 265 | perms = after.channel.permissions_for(after.author).manage_guild 266 | if (await check_invite(self.bot, after.content, after.channel)) and (not perms): 267 | invite_regex = ( 268 | "(?:https?://)?discord(?:app)?\.(?:com/invite|gg)/[a-zA-Z0-9]+/?" 269 | ) 270 | if re.search(invite_regex, after.content): 271 | await after.delete() 272 | return await after.channel.send( 273 | "Please don't send invite links in this server!", delete_after=5 274 | ) 275 | 276 | @commands.Cog.listener("on_message") 277 | async def invite_in_message(self, message: discord.Message): 278 | """ 279 | Responsible for tracking member joins. 280 | """ 281 | if message.author.bot or not message.guild: 282 | return 283 | perms = message.channel.permissions_for(message.author).manage_guild 284 | if (await check_invite(self.bot, message.content, message.channel)) and ( 285 | not perms 286 | ): 287 | invite_regex = ( 288 | "(?:https?://)?discord(?:app)?\.(?:com/invite|gg)/[a-zA-Z0-9]+/?" 289 | ) 290 | if re.search( 291 | invite_regex, message.content 292 | ): # `check_invite` already checks if there is an invite, 293 | # why are we checking again? 294 | await message.delete() 295 | return await message.channel.send( 296 | "Please don't send invite links in this server!", delete_after=5 297 | ) 298 | 299 | @commands.Cog.listener("on_message") 300 | async def repo_mention(self, message: discord.Message): 301 | """ 302 | Format: repo:user/repo 303 | 304 | Responds with a link to the repo. 305 | """ 306 | if "repo:" in message.content.lower(): 307 | repo = message.content.lower().split("repo:")[1].strip().split(" ")[0] 308 | for sect in filter(None, repo.split("/")): 309 | if not self.valid_gh_sect(sect): 310 | return 311 | url = f"https://github.com/{repo}" 312 | await message.channel.send(url) 313 | 314 | 315 | async def setup(bot: CodingBot): 316 | await bot.add_cog(ListenerCog(bot)) 317 | -------------------------------------------------------------------------------- /cogs/misc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import string 4 | import os 5 | 6 | import random 7 | import re 8 | from typing import TYPE_CHECKING, Optional 9 | from discord import app_commands 10 | import button_paginator as pg 11 | import contextlib 12 | import discord 13 | from discord.ext import commands 14 | from ext.helpers import Spotify, grouper, ordinal_suffix_of, gemini_split_string, get_lyrics, find_surrounding_lyrics, filter_banned_words 15 | from ext.http import Http 16 | from ext.ui.view import Piston 17 | import time 18 | import google.generativeai as genai 19 | import button_paginator as pg 20 | from googletrans import Translator 21 | 22 | if TYPE_CHECKING: 23 | from ext.models import CodingBot 24 | 25 | 26 | 27 | # I don't like the way its implemented but can't find a better way to do it so if someone can make this command better, that'll be great! 28 | 29 | @app_commands.context_menu(name = "translate") 30 | async def translate(interaction: discord.Interaction, message: discord.Message): 31 | await interaction.response.defer(ephemeral = True) 32 | trans = message.content 33 | try: 34 | translated = Translator(service_urls=[ 35 | 'translate.google.com', 36 | 'translate.google.co.kr' 37 | ]).translate(trans) 38 | except Exception as e: 39 | raise e 40 | 41 | embed = discord.Embed() 42 | 43 | _from = translated.src.upper() 44 | _to = translated.dest.upper() 45 | 46 | embed.add_field( 47 | name = f"Original ({_from})", 48 | value = trans, 49 | inline = False 50 | ) 51 | embed.add_field( 52 | name = f"Translated ({_to})", 53 | value = translated.text, 54 | inline = False 55 | ) 56 | 57 | await interaction.followup.send( 58 | embed = embed, 59 | ephemeral = True 60 | ) 61 | 62 | 63 | 64 | class Miscellaneous(commands.Cog, command_attrs=dict(hidden=False)): 65 | hidden = False 66 | 67 | def __init__(self, bot: CodingBot) -> None: 68 | self.bot = bot 69 | self.http = Http(bot.session) 70 | self.regex = { 71 | "codeblock": re.compile(r"(\w*)\s*(?:```)(\w*)?([\s\S]*)(?:```$)") 72 | } 73 | genai.configure(api_key=os.getenv("GEMINI_API_KEY")) 74 | self.ai = genai.GenerativeModel('gemini-pro') 75 | 76 | async def cog_check(self, ctx: commands.Context[CodingBot]) -> bool: 77 | if ctx.guild: 78 | return True 79 | await ctx.send("Please use commands in the server instead of dms") 80 | return False 81 | 82 | async def cog_load(self): 83 | self.bot.tree.add_command(translate) 84 | 85 | @commands.hybrid_command( 86 | name="retry", 87 | aliases=["re"], 88 | help="Re-execute a command by replying to a message", 89 | ) 90 | async def retry(self, ctx: commands.Context[CodingBot]): 91 | """ 92 | Reinvoke a command, running it again. 93 | This does NOT bypass any permissions checks | Code from v4 94 | """ 95 | try: 96 | message = await ctx.channel.fetch_message(ctx.message.reference.message_id) 97 | except discord.errors.NotFound: 98 | return await ctx.send("I couldn't find that message") 99 | if message.author == ctx.author: 100 | await ctx.message.add_reaction("\U00002705") 101 | context = await self.bot.get_context(message) 102 | await self.bot.invoke(context) 103 | else: 104 | await ctx.send(embed="That isn't your message") 105 | 106 | @commands.hybrid_command( 107 | name="afk", aliases=["afk-set", "set-afk"], help="Sets your afk" 108 | ) 109 | @commands.cooldown(1, 10, commands.BucketType.member) 110 | @commands.has_role(734283436637814844) # lvl 30+ 111 | async def afk( 112 | self, ctx: commands.Context[CodingBot], *, reason: Optional[str] = None 113 | ): 114 | """ 115 | Set your afk status. 116 | 117 | Usage: 118 | ------ 119 | `{prefix}afk`: *will set your afk status to nothing* 120 | `{prefix}afk [reason]`: *will set your afk status to [reason]* 121 | """ 122 | assert isinstance(ctx.author, discord.Member) 123 | assert ctx.guild is not None 124 | 125 | reason = reason or "AFK" 126 | member = ctx.author 127 | # staff_role = ctx.guild.get_role(795145820210462771) 128 | # on_pat_staff = member.guild.get_role( 129 | # 726441123966484600 130 | # ) # "on_patrol_staff" role 131 | 132 | # if staff_role in member.roles: 133 | # with contextlib.suppress(discord.Forbidden, discord.HTTPException): 134 | # await member.remove_roles(on_pat_staff) 135 | if ctx.guild.id not in self.bot.afk_cache: 136 | self.bot.afk_cache[ctx.guild.id] = {} 137 | if member.id not in self.bot.afk_cache.get(ctx.guild.id): 138 | await self.bot.conn.insert_record( 139 | "afk", 140 | table="afk", 141 | values=(member.id, reason, int(ctx.message.created_at.timestamp())), 142 | columns=["user_id", "reason", "afk_time"], 143 | ) 144 | with contextlib.suppress(Exception): 145 | await member.edit(nick=f"[AFK] {member.display_name}") 146 | try: 147 | self.bot.afk_cache[ctx.guild.id][member.id] = ( 148 | reason, 149 | int(ctx.message.created_at.timestamp()), 150 | ) 151 | except KeyError: 152 | self.bot.afk_cache[ctx.guild.id] = { 153 | member.id: (reason, int(ctx.message.created_at.timestamp())) 154 | } 155 | embed = discord.Embed( 156 | description=f"{ctx.author.mention} I set your AFK: {reason}", 157 | color=discord.Color.blue(), 158 | ) 159 | await ctx.reply(embed=embed) 160 | else: 161 | embed = discord.Embed( 162 | description=" You are already AFK", color=discord.Color.brand_red() 163 | ) 164 | await ctx.reply(embed=embed, ephemeral=True) 165 | 166 | @commands.command() 167 | async def run(self, ctx, *, codeblock: str): 168 | """ 169 | Runs code in a codeblock. 170 | The codeblock must be surrounded by \`\`\` and the language must be specified. 171 | Example: ```py\nprint('hello world')\n``` 172 | 173 | Usage: 174 | ------ 175 | `{prefix}run [codeblock]`: *will run the code in the [codeblock]* 176 | """ 177 | msg = await self.bot.reply(ctx, "...") 178 | matches = self.regex["codeblock"].findall(codeblock) 179 | lang = matches[0][0] or matches[0][1] 180 | if not matches: 181 | return await msg.edit( 182 | embed=self.bot.embed(title="```ansi\nInvalid codeblock\n```") 183 | ) 184 | if not lang: 185 | return await msg.edit( 186 | embed=self.bot.embed(title="```ansi\nno language specified\n```") 187 | ) 188 | code = matches[0][2] 189 | await msg.edit( 190 | view=Piston( 191 | self, 192 | code, 193 | lang, 194 | msg, 195 | ctx.author, 196 | ), 197 | ) 198 | 199 | @commands.hybrid_group(name="thank", invoke_without_command=True, fallback="you") 200 | @commands.cooldown(1, 10, commands.BucketType.member) 201 | async def thank( 202 | self, ctx: commands.Context[CodingBot], member: discord.Member, *, reason: str 203 | ): 204 | """ 205 | Thank someone. 206 | 207 | Usage: 208 | ------ 209 | `{prefix}thank {user} {reason}`: *will thank user* 210 | """ 211 | 212 | if member.id == ctx.author.id: 213 | return await ctx.reply("You can't thank yourself.", ephemeral=True) 214 | 215 | elif member.id == self.bot.user.id: 216 | return await ctx.reply("You can't thank me.", ephemeral=True) 217 | 218 | await self.bot.conn.insert_record( 219 | "thanks", 220 | table="thanks_info", 221 | values=(member.id, ctx.guild.id, 1), 222 | columns=["user_id", "guild_id", "thanks_count"], 223 | extras=[ 224 | "ON CONFLICT (user_id) DO UPDATE SET thanks_count = thanks_count + 1" 225 | ], 226 | ) 227 | staff_role = ctx.guild.get_role(795145820210462771) 228 | member_is_staff = 1 if staff_role and staff_role in member.roles else 0 229 | characters = string.ascii_letters + string.digits 230 | await self.bot.conn.insert_record( 231 | "thanks", 232 | table="thanks_data", 233 | columns=( 234 | "is_staff", 235 | "user_id", 236 | "giver_id", 237 | "guild_id", 238 | "message_id", 239 | "channel_id", 240 | "reason", 241 | "thank_id", 242 | "date", 243 | ), 244 | values=( 245 | member_is_staff, 246 | member.id, 247 | ctx.author.id, 248 | ctx.guild.id, 249 | ctx.message.id, 250 | ctx.channel.id, 251 | reason or "No reason given", 252 | "".join(random.choice(characters) for _ in range(7)), 253 | int(ctx.message.created_at.timestamp()), 254 | ), 255 | ) 256 | await ctx.reply( 257 | f"{ctx.author.mention} you thanked {member.mention}!", ephemeral=True 258 | ) 259 | 260 | @thank.command(name="show") 261 | @commands.has_any_role(783909939311280129, 797688360806121522) 262 | async def thank_show( 263 | self, ctx: commands.Context[CodingBot], member: discord.Member 264 | ): 265 | """ 266 | Show the thanks information of a user. 267 | 268 | Usage: 269 | ------ 270 | `{prefix}thank show {user}`: *will show the thanks information of user* 271 | """ 272 | records = await self.bot.conn.select_record( 273 | "thanks", 274 | table="thanks_data", 275 | arguments=( 276 | "giver_id", 277 | "message_id", 278 | "channel_id", 279 | "reason", 280 | "thank_id", 281 | "date", 282 | ), 283 | where=["user_id"], 284 | values=[member.id], 285 | ) 286 | 287 | if not records: 288 | return await ctx.reply( 289 | f"{member.mention} does not have any thanks.", ephemeral=True 290 | ) 291 | 292 | information = tuple(grouper(5, records)) 293 | 294 | embeds = [] 295 | for info in information: 296 | embed = discord.Embed(title=f"Showing {member.display_name}'s data") 297 | for data in info: 298 | giver_id = data.giver_id 299 | msg_id = data.message_id 300 | channel_id = data.channel_id 301 | reason = data.reason 302 | thank_id = data.thank_id 303 | timestamp = data.date 304 | channel = ctx.guild.get_channel(channel_id) 305 | msg_link = ( 306 | f"https://discord.com/channels/{ctx.guild.id}/{channel_id}/{msg_id}" 307 | ) 308 | 309 | giver = ctx.guild.get_member(giver_id) 310 | 311 | embed.add_field( 312 | name=f"Thank: {thank_id}", 313 | value=f"Thank giver: {giver.mention}\nDate: \n" 314 | f"Reason: {reason}\nThank given in: " 315 | f"{channel.mention if channel else f'<#{channel_id}>'}\n" 316 | f"Message link: [Click here!]({msg_link})", 317 | inline=False, 318 | ) 319 | 320 | embeds.append(embed) 321 | 322 | if len(embeds) == 1: 323 | await self.bot.reply(ctx, embed=embeds[0]) 324 | else: 325 | paginator = pg.Paginator(self.bot, embeds, ctx) 326 | paginator.add_button("back", emoji="◀️") 327 | paginator.add_button("goto", style=discord.ButtonStyle.primary) 328 | paginator.add_button("next", emoji="▶️") 329 | await paginator.start() 330 | 331 | @thank.command(name="delete") 332 | @commands.has_any_role(783909939311280129, 797688360806121522) 333 | async def thank_delete(self, ctx: commands.Context[CodingBot], thank_id: str): 334 | """ 335 | Delete a thank. 336 | 337 | Usage: 338 | ------ 339 | `{prefix}thank delete [thank_id]`: 340 | *will delete the thank with the id [thank_id]* 341 | 342 | """ 343 | record = await self.bot.conn.select_record( 344 | "thanks", 345 | table="thanks_data", 346 | arguments=["user_id"], 347 | where=["thank_id"], 348 | values=[thank_id], 349 | ) 350 | if not record: 351 | return await ctx.send("No thank with that id") 352 | 353 | user_id = record[0].user_id 354 | 355 | await self.bot.conn.delete_record( 356 | "thanks", table="thanks_data", where=["thank_id"], values=[thank_id] 357 | ) 358 | 359 | await self.bot.conn.insert_record( 360 | "thanks", 361 | table="thanks_info", 362 | values=(user_id, ctx.guild.id, -1), 363 | columns=["user_id", "guild_id", "thanks_count"], 364 | extras=[ 365 | "ON CONFLICT (user_id) DO UPDATE SET thanks_count = thanks_count - 1" 366 | ], 367 | ) 368 | await ctx.send(f"Remove thank from <@{user_id}> with id {thank_id}") 369 | 370 | @thank.command(name="leaderboard", aliases=["lb"]) 371 | async def thank_leaderboard(self, ctx: commands.Context[CodingBot]): 372 | """ 373 | Shows the thanks leaderboard. 374 | 375 | Usage: 376 | ------ 377 | `{prefix}thanks leaderboard`: *will show the thanks leaderboard* 378 | """ 379 | 380 | records = await self.bot.conn.select_record( 381 | "thanks", 382 | table="thanks_info", 383 | arguments=("user_id", "thanks_count"), 384 | where=["guild_id"], 385 | values=[ctx.guild.id], 386 | extras=["ORDER BY thanks_count DESC, user_id ASC LIMIT 100"], 387 | ) 388 | if not records: 389 | return await ctx.reply("No thanks leaderboard yet.", ephemeral=True) 390 | 391 | information = tuple(grouper(10, records)) 392 | 393 | embeds = [] 394 | for info in information: 395 | user = [ctx.guild.get_member(i.user_id) for i in info] 396 | embed = discord.Embed( 397 | title="Thank points leaderboard", 398 | description="\n\n".join( 399 | [ 400 | f"`{i}{ordinal_suffix_of(i)}` is {user.mention} with " 401 | f"`{thanks_count.thanks_count}` Thank point(s)" 402 | for i, (user, thanks_count) in enumerate(zip(user, info), 1) 403 | ] 404 | ), 405 | color=discord.Color.blue(), 406 | ) 407 | embeds.append(embed) 408 | if len(embeds) == 1: 409 | paginator = pg.Paginator( 410 | self.bot, 411 | embeds, 412 | ctx, 413 | check=lambda i: i.user.id == ctx.author.id, 414 | ) 415 | paginator.add_button( 416 | "delete", label="Delete", style=discord.ButtonStyle.danger 417 | ) 418 | await paginator.start() 419 | else: 420 | paginator = pg.Paginator(self.bot, embeds, ctx) 421 | paginator.add_button("back", emoji="◀️") 422 | paginator.add_button("goto", style=discord.ButtonStyle.primary) 423 | paginator.add_button("next", emoji="▶️") 424 | paginator.add_button( 425 | "delete", label="Delete", style=discord.ButtonStyle.danger 426 | ) 427 | await paginator.start() 428 | 429 | @commands.hybrid_group(invoke_without_command=True) 430 | async def trainee(self, ctx: commands.Context[CodingBot]): 431 | """ 432 | Sends the trainee help menu. 433 | """ 434 | await ctx.send_help("trainee") 435 | 436 | @trainee.command(name="list") 437 | @commands.cooldown(1, 10, commands.BucketType.member) 438 | async def trainee_list(self, ctx: commands.Context[CodingBot]): 439 | """ 440 | Lists all the trainees in the server. 441 | 442 | Usage: 443 | ------ 444 | `{prefix}list trainees`: *will list all the trainees in the server* 445 | """ 446 | 447 | trainee_role = ctx.guild.get_role(729537643951554583) # type: ignore 448 | if members := trainee_role.members: 449 | trainees = "\n".join( 450 | f"{i}. {member.mention}" for i, member in enumerate(members, 1) 451 | ) 452 | else: 453 | trainees = "No trainees yet." 454 | embed = discord.Embed( 455 | title="Trainees list", description=trainees, color=discord.Color.blue() 456 | ) 457 | await self.bot.reply(ctx, embed=embed) 458 | 459 | @commands.hybrid_command(aliases=["sp"]) 460 | @commands.cooldown(5, 60.0, type=commands.BucketType.user) 461 | async def spotify(self, ctx: commands.Context, member: discord.Member = None): 462 | """ 463 | Shows the spotify status of a member. 464 | 465 | Usage: 466 | ------ 467 | `{prefix}spotify`: *will show your spotify status* 468 | `{prefix}spotify [member]`: *will show the spotify status of [member]* 469 | """ 470 | member = ctx.guild.get_member((member or ctx.author).id) 471 | 472 | spotify = Spotify(bot=self.bot, member=member) 473 | result = await spotify.get_embed() 474 | if not result: 475 | if member == ctx.author: 476 | return await ctx.reply( 477 | "You are currently not listening to spotify!", mention_author=False 478 | ) 479 | return await self.bot.reply( 480 | ctx, 481 | f"{member.mention} is not listening to Spotify", 482 | mention_author=False, 483 | allowed_mentions=discord.AllowedMentions(users=False), 484 | ) 485 | file, view = result 486 | await self.bot.send(ctx, file=file, view=view) 487 | 488 | @commands.hybrid_command( 489 | name = "askai", description = "Generate code or ask questions from the ai" 490 | ) 491 | async def askai(self, ctx: commands.Context, *, prompt: str): 492 | """ 493 | Generate code or ask questions from the ai 494 | 495 | prompt (str): prompt for the ai 496 | """ 497 | m = await ctx.reply( 498 | embed = discord.Embed( 499 | description = "Generating Response ...", 500 | color = discord.Color.blurple() 501 | ) 502 | ) 503 | try: 504 | res = self.ai.generate_content(prompt) 505 | except Exception as e: 506 | await m.edit( 507 | embed = discord.Embed( 508 | description = f":x: Ran into some issues\n{e}" 509 | ) 510 | ) 511 | try: 512 | response = res.text 513 | except: 514 | response = "Prompt was blocked!" 515 | split = gemini_split_string(response) 516 | embeds = [] 517 | for count, content in enumerate(split): 518 | embeds.append( 519 | discord.Embed( 520 | title = f"AI Response:", 521 | description = f"{content}..." if len(content) == 1000 else content, 522 | color = discord.Color.random() 523 | ).set_footer(text = f"Page: {count+1}/{len(split)}") 524 | ) 525 | 526 | await m.delete() 527 | if len(embeds) == 1: 528 | return await self.bot.send(ctx, embeds = embeds) 529 | paginator = pg.Paginator(self.bot, embeds, ctx) 530 | paginator.add_button('prev', emoji='◀') 531 | paginator.add_button('goto') 532 | paginator.add_button('next', emoji='▶') 533 | paginator.timeout=len(split)*2*60 # two minutes per page 534 | async def on_timeout(): 535 | paginator.clear_items() 536 | await paginator.message.edit(view=paginator) 537 | paginator.on_timeout = on_timeout 538 | 539 | await paginator.start() 540 | 541 | 542 | @commands.hybrid_command(name = "lyric", aliases = ["lyrics"]) 543 | async def lyric(self, ctx: commands.Context, member: discord.Member = None): 544 | member = member or ctx.author 545 | 546 | spotify_activity = None 547 | for activity in member.activities: 548 | if isinstance(activity, discord.Spotify): 549 | spotify_activity = activity 550 | break 551 | 552 | if not spotify_activity: 553 | await ctx.send(f"{member.display_name} is not listening to Spotify.") 554 | return 555 | 556 | else: 557 | duration = time.time()-spotify_activity.start.timestamp() 558 | song_title = spotify_activity.title 559 | song_artist = spotify_activity.artist 560 | lyr = get_lyrics(song_title, song_artist) 561 | if not lyr: 562 | return await ctx.send(f"Lyrics not found for song - {song_title} - {song_artist}") 563 | if lyr[1] == 1: 564 | lyr = "\n".join(find_surrounding_lyrics(lyr[0], int(duration))) 565 | else: 566 | lyr = "\n".join(lyr.splitlines()[0:5]) 567 | 568 | lyr = filter_banned_words(lyr) 569 | embed = discord.Embed(description = lyr, title = f"{song_title} - {song_artist}", color = spotify_activity.color) 570 | embed.set_author(name = ctx.author, icon_url = ctx.author.avatar.url) 571 | embed.set_thumbnail(url = spotify_activity.album_cover_url) 572 | 573 | await ctx.send(embed = embed) 574 | 575 | 576 | 577 | 578 | 579 | async def setup(bot: CodingBot): 580 | await bot.add_cog(Miscellaneous(bot)) 581 | -------------------------------------------------------------------------------- /cogs/modmail.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from ext.consts import MODMAIL_CHANNEL_ID, MODMAIL_ROLE_ID, MODMAIL_CLOSED, MODMAIL_OPEN 3 | from discord.ext import commands 4 | import discord 5 | from ext.ui.view import YesNoView 6 | 7 | def none_if_error(func): 8 | def wrapper(*args, **kwargs): 9 | try: 10 | return func(*args, **kwargs) 11 | except Exception: 12 | return None 13 | 14 | return wrapper 15 | 16 | class ModMail(commands.Cog): 17 | def __init__(self, bot: commands.Bot): 18 | self.bot = bot 19 | self.sessions = [] # [{user: discord.Member, thread: discord.Thread}] 20 | self.channel: typing.Optional[discord.ForumChannel] = None 21 | 22 | @none_if_error 23 | def get_thread(self, user) -> discord.Thread | None: 24 | return [i['thread'] for i in self.sessions if i['user'] == user][0] 25 | 26 | @none_if_error 27 | def get_user(self, thread) -> discord.Member | discord.User | None: 28 | return [i['user'] for i in self.sessions if i['thread'] == thread][0] 29 | 30 | async def send_webhook_message( 31 | self, 32 | message: discord.Message, 33 | thread: discord.Thread 34 | ): 35 | webhook = (await thread.parent.webhooks())[0] 36 | await webhook.send( 37 | username=message.author.name, 38 | content=message.content, 39 | avatar_url=message.author.display_avatar.url, 40 | files=message.attachments, 41 | allowed_mentions=discord.AllowedMentions( 42 | users=False, everyone=False, roles=False 43 | ), 44 | thread=thread, 45 | ) 46 | await message.add_reaction("✅") 47 | 48 | async def close_thread(self, thread: discord.Thread): 49 | await thread.add_tags(thread.parent.get_tag(MODMAIL_CLOSED)) 50 | await thread.remove_tags(thread.parent.get_tag(MODMAIL_OPEN)) 51 | await thread.edit(locked=True, archived=True) 52 | self.sessions.remove({'user': self.get_user(thread), 'thread': thread}) 53 | 54 | @commands.hybrid_command() 55 | async def close(self, ctx: commands.Context): 56 | if not ctx.guild and (thread := self.get_thread(ctx.author)): 57 | await thread.send("This ticket has been closed by the user.") 58 | await self.close_thread(thread) 59 | await ctx.send("Your modmail ticket has successfully closed!") 60 | 61 | elif member := self.get_user(ctx.channel): 62 | view = YesNoView( 63 | yes_message="Your modmail ticket has successfully closed!", 64 | no_message="Aborted.", 65 | ) 66 | await member.send(content="Do you want to close the ticket?", view=view) 67 | await view.wait() 68 | if view.yes: 69 | await self.close_thread(ctx.channel) 70 | else: 71 | await ctx.channel.send("Member refused to close the ticket.") 72 | 73 | @commands.Cog.listener() 74 | async def on_message(self, message: discord.Message): 75 | if not self.channel: 76 | self.channel: discord.ForumChannel = self.bot.get_channel(MODMAIL_CHANNEL_ID) 77 | 78 | if message.author.bot or message.content.startswith(self.bot.command_prefix[0]): 79 | return 80 | 81 | if not message.guild: 82 | if not (thread := self.get_thread(message.author)): 83 | view = YesNoView( 84 | yes_message="Your modmail ticket has been successfully created!", 85 | no_message="Aborted.", 86 | ) 87 | await message.author.send( 88 | "Do you want to create a modmail ticket?", view=view 89 | ) 90 | await view.wait() 91 | if view.yes: 92 | thread, _ = await self.channel.create_thread( 93 | name=f"Mods vs @{message.author.name}", 94 | content="New ModMail ticket created by "\ 95 | f"{message.author.mention}, <@&{MODMAIL_ROLE_ID}>", 96 | files=message.attachments, 97 | applied_tags=[self.channel.get_tag(MODMAIL_OPEN)], 98 | ) 99 | await self.send_webhook_message(message, thread) 100 | 101 | self.sessions.append({'user': message.author, 'thread': thread}) 102 | else: 103 | await self.send_webhook_message(message, thread) 104 | 105 | elif member := self.get_user(message.channel): 106 | await member.send( 107 | f"⚒️ @{message.author.name}: " + message.content, 108 | files=message.attachments 109 | ) 110 | 111 | 112 | async def setup(bot): 113 | await bot.add_cog(ModMail(bot)) 114 | -------------------------------------------------------------------------------- /cogs/tasks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import datetime 4 | import random 5 | 6 | import discord 7 | from discord.ext import commands, tasks 8 | from typing import TYPE_CHECKING 9 | from ext.consts import TCR_GUILD_ID 10 | from ext.http import Http 11 | 12 | 13 | if TYPE_CHECKING: 14 | from ext.models import CodingBot 15 | 16 | 17 | class TaskCog(commands.Cog, command_attrs=dict(hidden=True)): 18 | hidden = True 19 | 20 | def __init__(self, bot: CodingBot) -> None: 21 | self.http = Http(bot.session) 22 | self.bot = bot 23 | 24 | async def cog_load(self) -> None: 25 | self.status_change.start() 26 | self.remove_inactive_warns.start() 27 | self.send_cat_pic.start() 28 | 29 | @tasks.loop(minutes=2) 30 | async def status_change(self): 31 | statuses = [ 32 | "over TCR", 33 | "you", 34 | "swas", 35 | "@everyone", 36 | "general chat", 37 | "discord", 38 | ",help", 39 | "your mom", 40 | "bob and lex argue", 41 | "swas simp for false", 42 | "new members", 43 | "the staff team", 44 | "helpers", 45 | "code", 46 | "mass murders", 47 | "karen be an idiot", 48 | "a video", 49 | "watches", 50 | "Ayu", 51 | "fight club", 52 | "youtube", 53 | "potatoes", 54 | "simps", 55 | "people", 56 | "my server", 57 | "humans destroy the world", 58 | "AI take over the world", 59 | "female bots 😳", 60 | "dinosaurs", 61 | "https://youtu.be/M5V_IXMewl4", 62 | "idiots", 63 | "the beginning of WWIII", 64 | "verified bot tags with envy", 65 | "Server Boosters (boost to get your name on here)", 66 | "OG members", 67 | "dalek rising from the ashes", 68 | "spongebob", 69 | "turtles", 70 | "SQUIRREL!!!", 71 | "people get banned", 72 | "por...k chops", 73 | "my poggers discriminator", 74 | "tux", 75 | "linux overcome windows", 76 | "bob get a life", 77 | "a documentary", 78 | ] 79 | if tcr := self.bot.get_guild(681882711945641997): 80 | if tcr.get_role(795145820210462771): 81 | statuses.append( 82 | random.choice(tcr.get_role(795145820210462771).members).name 83 | ) # type: ignore 84 | if tcr.get_role(737517726737629214): 85 | try: 86 | statuses.append( 87 | f"{random.choice(tcr.get_role(737517726737629214).members).name} (Server Booster)" 88 | ) 89 | except: 90 | pass 91 | 92 | await self.bot.change_presence( 93 | activity=discord.Activity( 94 | type=discord.ActivityType.watching, 95 | name=f"{random.choice(statuses)} | {self.bot.command_prefix[0]}help", 96 | ) 97 | ) 98 | 99 | @status_change.before_loop 100 | async def before_status_change(self): 101 | await self.bot.wait_until_ready() 102 | self.bot.logger.info("Started task loop for Status Change") 103 | 104 | 105 | @tasks.loop(hours = 24) 106 | async def send_cat_pic(self): 107 | data = await self.http.api["cat-api"]["api"]() 108 | cat_pic = data[0]["url"] 109 | 110 | channel = self.bot.get_channel(743817386792058971) # #lounge 111 | 112 | embed = discord.Embed( 113 | description = "Today's Cat Pic!", 114 | color = discord.Color.random() 115 | ).set_image( 116 | url = cat_pic 117 | ) 118 | 119 | if channel: 120 | await channel.send(embed = embed) 121 | 122 | @send_cat_pic.before_loop 123 | async def log_cat_pic_loop(self): 124 | await self.bot.wait_until_ready() 125 | self.bot.logger.info("Started task loop for Cat Pics") 126 | 127 | 128 | @tasks.loop(hours=24) 129 | async def remove_inactive_warns(self): 130 | await self.bot.wait_until_ready() 131 | 132 | records = await self.bot.conn.select_record( 133 | "warnings", 134 | arguments=("date", "user_id"), 135 | table="warnings", 136 | where=("guild_id",), 137 | values=(TCR_GUILD_ID,), 138 | ) 139 | now = datetime.datetime.now(datetime.timezone.utc).timestamp() 140 | if records: 141 | for record in records: 142 | if record.date + (60 * 60 * 24 * 31) < now: 143 | await self.bot.conn.delete_record( 144 | "warnings", 145 | table="warnings", 146 | where=("guild_id", "user_id", "date"), 147 | values=(TCR_GUILD_ID, record.user_id, record.date), 148 | ) 149 | 150 | @remove_inactive_warns.before_loop 151 | async def before_remove_inactive_warns(self): 152 | await self.bot.wait_until_ready() 153 | self.bot.logger.info("Started task loop for Remove Inactive Warns") 154 | 155 | 156 | async def setup(bot: CodingBot): 157 | await bot.add_cog(TaskCog(bot)) 158 | -------------------------------------------------------------------------------- /cogs/ticket.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | 4 | from ext.ui.view import CreateButton, CloseButton, TrashButton 5 | 6 | class TicketCog(commands.Cog): 7 | def __init__(self, bot: commands.Bot): 8 | self.bot = bot 9 | 10 | @commands.Cog.listener(name = "on_ready") 11 | async def before_ready(self): 12 | self.bot.add_view(CreateButton()) 13 | self.bot.add_view(CloseButton()) 14 | self.bot.add_view(TrashButton()) 15 | await self.bot.change_presence(activity=discord.Activity(type = discord.ActivityType.listening, name = "Doghouse Game's Heart 😳")) 16 | print(f"Logged in as: {self.bot.user.name}") 17 | 18 | @commands.command(name="ticket") 19 | @commands.has_permissions(administrator=True) 20 | async def ticket(self, ctx): 21 | await ctx.send( 22 | embed = discord.Embed( 23 | description="🎫 **Click on the button below to create a ticket**\nIf you need any help regarding punishments, roles, or you just have a general question, feel free to create a ticket and a staff member will get to you shortly!\nOpening a ticket without a valid reason will get you warned/blacklisted.\n\n__**Do not open support tickets for Coding Help. Doing so will get you warned.**__", 24 | color = 0x8b6ffc 25 | ), 26 | view = CreateButton() 27 | ) 28 | 29 | async def setup(bot): 30 | await bot.add_cog(TicketCog(bot)) -------------------------------------------------------------------------------- /ext/consts.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | import os 3 | 4 | import discord 5 | 6 | 7 | __all__ = ( 8 | "INTENTS", 9 | "PREFIX_CONFIG_SCHEMA", 10 | "COMMANDS_CONFIG_SCHEMA", 11 | "WARNINGS_CONFIG_SCHEMA", 12 | "AFK_CONFIG_SCHEMA", 13 | "HELP_WARNINGS_CONFIG_SCHEMA", 14 | "HELP_COMMAND", 15 | "OFFICIAL_HELPER_ROLE_ID", 16 | "TCR_GUILD_ID", 17 | "HELP_BAN_ROLE_ID", 18 | "READ_HELP_RULES_ROLE_ID", 19 | "THANK_INFO_CONFIG_SCHEMA", 20 | "THANK_DATA_CONFIG_SCHEMA", 21 | "MESSAGE_METRIC_SCHEMA", 22 | "TCR_STAFF_ROLE_ID", 23 | "MODMAIL_CHANNEL_ID", 24 | "MODMAIL_WEBHOOK_URL", 25 | "VERSION", 26 | ) 27 | 28 | 29 | class Version(NamedTuple): 30 | major: int 31 | submajor: int 32 | minor: int 33 | release: str 34 | 35 | def __str__(self) -> str: 36 | return f"v{self.major}.{self.submajor}.{self.minor} [{self.release}]" 37 | 38 | def release_format(self): 39 | return ( 40 | f"Version: `{self.major}.{self.submajor}.{self.minor}" 41 | f"`\nPatch: `{self.release}`" 42 | ) 43 | 44 | 45 | VERSION = Version(major=0, submajor=0, minor=1, release="alpha") 46 | 47 | INTENTS = discord.Intents( 48 | messages=True, 49 | guilds=True, 50 | members=True, 51 | bans=True, 52 | emojis=True, 53 | integrations=True, 54 | invites=True, 55 | webhooks=True, 56 | voice_states=True, 57 | reactions=True, 58 | message_content=True, 59 | presences=True, 60 | ) 61 | 62 | 63 | PREFIX_CONFIG_SCHEMA = """CREATE TABLE IF NOT EXISTS prefixconf ( 64 | id BIGINT, 65 | prefix TEXT 66 | ); 67 | """ 68 | 69 | COMMANDS_CONFIG_SCHEMA = """CREATE TABLE IF NOT EXISTS commandconf ( 70 | id BIGINT, 71 | command TEXT 72 | ); 73 | """ 74 | 75 | WARNINGS_CONFIG_SCHEMA = """CREATE TABLE IF NOT EXISTS warnings ( 76 | user_id BIGINT, 77 | guild_id BIGINT, 78 | moderator_id BIGINT, 79 | reason TEXT, 80 | date BIGINT 81 | ); 82 | """ 83 | 84 | AFK_CONFIG_SCHEMA = """CREATE TABLE IF NOT EXISTS afk ( 85 | user_id BIGINT, 86 | reason TEXT, 87 | afk_time BIGINT 88 | ); 89 | """ 90 | 91 | HELP_WARNINGS_CONFIG_SCHEMA = """CREATE TABLE IF NOT EXISTS help_warns ( 92 | user_id BIGINT, 93 | guild_id BIGINT, 94 | helper_id BIGINT, 95 | reason TEXT, 96 | date BIGINT 97 | ); 98 | """ 99 | 100 | THANK_INFO_CONFIG_SCHEMA = """CREATE TABLE IF NOT EXISTS thanks_info ( 101 | user_id BIGINT UNIQUE, 102 | guild_id BIGINT, 103 | thanks_count INT 104 | ); 105 | """ 106 | 107 | THANK_DATA_CONFIG_SCHEMA = """CREATE TABLE IF NOT EXISTS thanks_data ( 108 | user_id BIGINT, 109 | giver_id BIGINT, 110 | guild_id BIGINT, 111 | message_id BIGINT, 112 | channel_id BIGINT, 113 | thank_id TEXT, 114 | date BIGINT, 115 | reason TEXT DEFAULT "No reason given", 116 | is_staff BOOLEAN CHECK(is_staff IN (0, 1)) DEFAULT 0, 117 | thank_revoked BOOLEAN CHECK(thank_revoked IN (0, 1))\ 118 | DEFAULT 0 119 | ); 120 | """ 121 | 122 | MESSAGE_METRIC_SCHEMA = """CREATE TABLE IF NOT EXISTS message_metric ( 123 | user_id BIGINT, 124 | guild_id BIGINT, 125 | message_count INT, 126 | deleted_message_count INT DEFAULT 0, 127 | offline INT DEFAULT 0, 128 | online INT DEFAULT 0, 129 | dnd INT DEFAULT 0, 130 | idle INT DEFAULT 0, 131 | is_staff BOOLEAN CHECK(is_staff IN (0, 1)) DEFAULT 0, 132 | UNIQUE(user_id, guild_id) 133 | );""" 134 | 135 | 136 | TICKETS_CONFIG_SCHEMA = """ 137 | CREATE TABLE IF NOT EXISTS tickets ( 138 | message_id BIGINT PRIMARY KEY, 139 | ticket_id BIGINT, 140 | opened_by BIGINT, 141 | closed_by BIGINT, 142 | opened_at BIGINT, 143 | closed_at BIGINT, 144 | reason TEXT 145 | ) 146 | """ 147 | 148 | 149 | HELP_COMMAND = """ 150 | Help command for Coding Bot 151 | 152 | Usage: 153 | ------ 154 | `{prefix}help` 155 | `{prefix}help ` 156 | `{prefix}help ` 157 | `{prefix}help ` 158 | """ 159 | 160 | MODMAIL_WEBHOOK_URL = os.getenv("MODMAIL_WEBHOOK_URL") 161 | 162 | OFFICIAL_HELPER_ROLE_ID = 726650418444107869 163 | TCR_GUILD_ID = 681882711945641997 164 | HELP_BAN_ROLE_ID = 903133405317857300 165 | READ_HELP_RULES_ROLE_ID = 903133599715459153 166 | TCR_STAFF_ROLE_ID = 795145820210462771 167 | TCR_MEMBER_ROLE_ID = 744403871262179430 168 | MODMAIL_CHANNEL_ID = 1144791467391455242 # conch: 1144827896171610185 169 | MODMAIL_ROLE_ID = 788799215417032705 170 | MODMAIL_OPEN = 1144842655671517305 # conch internal: 1144839090609602611 171 | MODMAIL_CLOSED = 1144842686579359837 # conch: 1144839107353256017 172 | STAFF_UPDATE_CHANNEL_ID = 1124612885365133412 173 | 174 | 175 | TICKET_REPO = "WhoIsConch/tcrtickets" 176 | TICKET_HANDLER_ROLE_ID = 788799215417032705 177 | OPEN_TICKET_CATEGORY = 788797663377883147 178 | CLOSED_TICKET_CATEGORY = 1287954797286391860 179 | TICKET_LOG_CHANNEL = 829936676021075999 180 | 181 | # mods pls fill this up 182 | TICKET_CATEGORY_ID = 0 # <--- this still hasn't been resolved but I don't think we need it ~ ayu 183 | -------------------------------------------------------------------------------- /ext/errors.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from discord.ext import commands 4 | 5 | 6 | class InsufficientPrivilegeError(commands.CheckFailure): 7 | """ 8 | Exception for insufficient privilege 9 | """ 10 | 11 | def __init__(self, message: str) -> None: 12 | self.message = message 13 | 14 | def __str__(self) -> str: 15 | return self.message 16 | -------------------------------------------------------------------------------- /ext/http.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | from discord.ext import tasks 3 | 4 | 5 | class Http: 6 | def __init__(self, session: aiohttp.ClientSession): 7 | self.session = session 8 | self.cache = {"piston": {}} 9 | self.api = { 10 | # ////////////////////////////////////////////////////////////////////////// 11 | # prelude 12 | "get": { 13 | "meme": lambda: self.api["meme-api"]["gimme"](), 14 | }, 15 | # //////////////////////////////////////////////////////////////////////// 16 | # "rock": { 17 | # "random": lambda: self.get( 18 | # "https://rockapi.apiworks.tech/rock/random", _json=True 19 | # ), 20 | # "top": lambda: self.get("https://rockapi.apiworks.tech/rock/top"), 21 | # }, 22 | # "numbers": { 23 | # "random": lambda _type="trivia": self.api["numbers"][ 24 | # f"random_{_type}" 25 | # ](), 26 | # "number": lambda _type="trivia": self.api["numbers"][_type](), 27 | # "random_trivia": lambda: self.get( 28 | # "http://numbersapi.com/random/trivia" 29 | # ), 30 | # "random_math": lambda: self.get("http://numbersapi.com/random/math"), 31 | # "random_date": lambda: self.get("http://numbersapi.com/random/date"), 32 | # "random_year": lambda: self.get("http://numbersapi.com/random/year"), 33 | # "date": lambda date: self.get(f"http://numbersapi.com/{date}/date"), 34 | # "year": lambda year: self.get(f"http://numbersapi.com/{year}/year"), 35 | # "trivia": lambda num: self.get(f"http://numbersapi.com/{num}"), 36 | # "math": lambda num: self.get(f"http://numbersapi.com/{num}/math"), 37 | # }, 38 | "piston": { 39 | "runtimes": lambda: self.get( 40 | "https://emkc.org/api/v2/piston/runtimes", _json=True 41 | ), 42 | # "execute": "https://emkc.org/api/v2/piston/execute", 43 | "execute": lambda language, code: self.post( 44 | "https://emkc.org/api/v1/piston/execute", 45 | _json=True, 46 | data={"language": language, "source": code}, 47 | ), 48 | }, 49 | "meme-api": { 50 | "gimme": lambda: self.get("https://meme-api.com/gimme/", _json=True) 51 | }, 52 | "some-random-api": { 53 | "bottoken": lambda: self.get( 54 | "https://some-random-api.ml/bottoken", _json=True 55 | ), 56 | "animal": lambda animal: self.get( 57 | f"https://some-random-api.ml/animal/{animal}" 58 | ), 59 | "binary-encode": lambda string: self.get( 60 | f"https://some-random-api.ml/binary?encode={string}" 61 | ), 62 | "binary-decode": lambda binary: self.get( 63 | f"https://some-random-api.ml/binary?decode={binary}" 64 | ), 65 | "lyrics": lambda query: self.get( 66 | f"https://some-random-api.ml/lyrics?title={query}" 67 | ), 68 | "joke": lambda: self.get("https://some-random-api.ml/joke", _json=True), 69 | "filters": { 70 | "invert": lambda pfp: f"https://some-random-api.ml/canvas/invert?avatar={pfp}", 71 | "greyscale": lambda pfp: f"https://some-random-api.ml/canvas/greyscale?avatar={pfp}", 72 | "colour": lambda pfp, hex_code: f"https://some-random-api.ml/canvas/color?avatar={pfp}&color={hex_code}", 73 | "brightness": lambda pfp: f"https://some-random-api.ml/canvas/brightness?avatar={pfp}", 74 | "threshold": lambda pfp: f"https://some-random-api.ml/canvas/threshold?avatar={pfp}", 75 | }, 76 | }, 77 | "joke": { 78 | "api": lambda: self.get( 79 | "https://v2.jokeapi.dev/joke/Programming,Miscellaneous,Pun,Spooky,Christmas?blacklistFlags=nsfw,religious,political,racist,sexist,explicit", 80 | _json=True, 81 | ), 82 | }, 83 | "cat-api": { 84 | "api": lambda: self.get("https://api.thecatapi.com/v1/images/search", _json = True) 85 | } 86 | } 87 | 88 | # self.update_data.start() 89 | 90 | @tasks.loop(minutes=5) 91 | async def update_data(self): 92 | self.cache["piston"]["runtimes"] = await self.get( 93 | "https://emkc.org/api/v2/piston/runtimes", _json=True 94 | ) 95 | 96 | # #///////////////////////////////////////////////////////////////////////// 97 | # # some-random-api 98 | # #///////////////////////////////////////////////////////////////////////// 99 | 100 | # async def get_bottoken(self): 101 | # return await self.get( 102 | # _url=self.api["some-random-api"]["bottoken"], 103 | # _json=True 104 | # ) 105 | 106 | # #///////////////////////////////////////////////////////////////////////// 107 | # # meme-api 108 | # #///////////////////////////////////////////////////////////////////////// 109 | 110 | # async def get_meme(self): 111 | # return await self.get( 112 | # _url=self.api["meme-api"]["gimme"], 113 | # _json=True 114 | # ) 115 | 116 | # #///////////////////////////////////////////////////////////////////////// 117 | # # 🪨 api 118 | # #///////////////////////////////////////////////////////////////////////// 119 | 120 | # async def get_random_rock(self): 121 | # return await self.get( 122 | # _url=self.api["rock"]["random"], 123 | # _json=True 124 | # ) 125 | 126 | # async def get_top_rock(self): 127 | # return await self.get( 128 | # _url=self.api["rock"]["top"], 129 | # _json=True 130 | # ) 131 | 132 | # #///////////////////////////////////////////////////////////////////////// 133 | # # numbers-api 134 | # #///////////////////////////////////////////////////////////////////////// 135 | 136 | # async def get_random_number(self, type="trivia"): 137 | # return await self.get( 138 | # _url=self.api["numbers"]["random_" + type] 139 | # ) 140 | 141 | # async def get_number(self, num, type="trivia"): 142 | # return await self.get( 143 | # _url=self.api["numbers"][type](num) 144 | # ) 145 | 146 | # #///////////////////////////////////////////////////////////////////////// 147 | # # piston-api 148 | # #///////////////////////////////////////////////////////////////////////// 149 | 150 | # async def get_runtimes(self): 151 | # return await self.get( 152 | # _url=self.api["piston"]["runtimes"], 153 | # _json=True 154 | # ) 155 | 156 | # async def execute_code(self, language, code): 157 | # r = await self.post( 158 | # _url=self.api["piston"]["execute"], 159 | # _json=True, 160 | # data={ 161 | # "language": language, 162 | # "source": code, 163 | # }, 164 | # ) 165 | # return r 166 | 167 | # ///////////////////////////////////////////////////////////////////////// 168 | # http 169 | # ///////////////////////////////////////////////////////////////////////// 170 | 171 | async def get(self, _url, _json=False, **kwargs): 172 | async with self.session.get(_url, **kwargs) as response: 173 | return await ( 174 | response.json(content_type=None) if _json else response.text() 175 | ) 176 | 177 | async def post(self, _url, _json=False, **kwargs): 178 | async with self.session.post(_url, **kwargs) as response: 179 | return await (response.json() if _json else response.text()) 180 | 181 | async def put(self, _url, _json=False, **kwargs): 182 | async with self.session.put(_url, **kwargs) as response: 183 | return await (response.json() if _json else response.text()) 184 | 185 | async def delete(self, _url, _json=False, **kwargs): 186 | async with self.session.delete(_url, **kwargs) as response: 187 | return await (response.json() if _json else response.text()) 188 | -------------------------------------------------------------------------------- /ext/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | class CustomFormatter(logging.Formatter): 5 | grey = "\x1b[38;20m" 6 | green = "\x1b[1;32m" 7 | yellow = "\x1b[33;21m" 8 | red = "\x1b[31;21m" 9 | bold_red = "\x1b[31;1m" 10 | blue = "\x1b[1;34m" 11 | light_blue = "\x1b[1;36m" 12 | purple = "\x1b[5;35m" 13 | reset = "\x1b[0m" 14 | local_format = ( 15 | "{}%(asctime)s{reset} {}%(levelname)s{reset} {}%(name)s{reset} " 16 | " dependent%(message)s{reset}".format(grey, blue, purple, reset=reset) 17 | ) 18 | 19 | FORMATS = { 20 | logging.DEBUG: local_format.replace("dependent", blue) + reset, 21 | logging.INFO: local_format.replace("dependent", green) + reset, 22 | logging.WARNING: local_format.replace("dependent", yellow) + reset, 23 | logging.ERROR: local_format.replace("dependent", bold_red) + reset, 24 | logging.CRITICAL: local_format.replace("dependent", red) + reset, 25 | } 26 | 27 | def format(self, record): 28 | log_fmt = self.FORMATS.get(record.levelno) 29 | formatter = logging.Formatter(log_fmt, datefmt="%Y-%m-%d %H:%M:%S") 30 | return formatter.format(record) 31 | 32 | 33 | def create_logger(name: str): 34 | logger = logging.getLogger(name) 35 | logger.setLevel(logging.INFO) 36 | ch = logging.StreamHandler() 37 | ch.setLevel(logging.INFO) 38 | ch.setFormatter(CustomFormatter()) 39 | logger.addHandler(ch) 40 | return logger 41 | -------------------------------------------------------------------------------- /ext/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import datetime as dt 4 | import logging 5 | import os 6 | import string 7 | from dataclasses import dataclass, field 8 | from typing import ( 9 | Any, 10 | Dict, 11 | Iterable, 12 | List, 13 | Mapping, 14 | Optional, 15 | Set, 16 | Tuple, 17 | Union, 18 | ) 19 | 20 | import aiohttp 21 | import aiosqlite 22 | import discord 23 | from discord.ext import commands 24 | from DiscordUtils import InviteTracker 25 | from dotenv import load_dotenv 26 | from pytimeparse import parse 27 | 28 | from .consts import ( 29 | AFK_CONFIG_SCHEMA, 30 | COMMANDS_CONFIG_SCHEMA, 31 | HELP_COMMAND, 32 | HELP_WARNINGS_CONFIG_SCHEMA, 33 | INTENTS, 34 | MESSAGE_METRIC_SCHEMA, 35 | PREFIX_CONFIG_SCHEMA, 36 | THANK_DATA_CONFIG_SCHEMA, 37 | THANK_INFO_CONFIG_SCHEMA, 38 | WARNINGS_CONFIG_SCHEMA, 39 | TICKETS_CONFIG_SCHEMA, 40 | VERSION, 41 | Version, 42 | ) 43 | from .helpers import AntiRaid, WelcomeBanner, log_error 44 | from .logger import create_logger 45 | 46 | load_dotenv(".env", verbose=True) 47 | 48 | 49 | class Record: 50 | __slots__ = ("arguments",) 51 | 52 | def __init__(self, arguments: Dict[str, Any]) -> None: 53 | self.arguments = arguments 54 | 55 | def __getitem__(self, __item: Union[str, int]) -> Any: 56 | if isinstance(__item, str): 57 | if __item in self.arguments: 58 | return self.arguments[__item] 59 | raise AttributeError(f"Dynamic object has no attribute '{__item}'") 60 | elif isinstance(__item, int): # type: ignore 61 | return tuple(self.arguments.values())[__item] 62 | 63 | def __getattr__(self, __item: str): 64 | if __item in self.arguments: 65 | return self.arguments[__item] 66 | raise AttributeError(f"Dynamic object has no attribute '{__item}'") 67 | 68 | def __len__(self): 69 | return len(self.arguments.keys()) 70 | 71 | def __repr__(self) -> str: 72 | argument = ", ".join(f"{key}={value}" for key, value in self.arguments.items()) 73 | return f"" 74 | 75 | @classmethod 76 | def from_tuple(cls, arguments: Iterable[Any], tuple_: Iterable[Any]) -> Record: 77 | arguments = [ 78 | "".join( 79 | map( 80 | lambda x: x 81 | if x in string.ascii_letters + string.digits + "_" 82 | else "", 83 | arg, 84 | ) 85 | ) 86 | for arg in arguments 87 | ] 88 | return cls(dict(zip(arguments, tuple_))) 89 | 90 | 91 | @dataclass(slots=True, kw_only=True, repr=True) 92 | class Cache: 93 | """ 94 | A Cache for storing guild information 95 | 96 | Attributes 97 | ---------- 98 | guild_id : int 99 | The ID of the guild 100 | channel_id : int 101 | The ID of the channel 102 | """ 103 | 104 | prefixes: List[str] 105 | commands: Set[str] = field(default_factory=set) 106 | 107 | 108 | class Database: 109 | """ 110 | Database class for storing opened connections 111 | 112 | Attributes 113 | ---------- 114 | conn : Dict[str, aiosqlite.Connection] 115 | A dictionary of connections 116 | is_closed : bool 117 | Whether the connections are closed 118 | """ 119 | 120 | def __init__(self, bot: CodingBot): 121 | self.conn: Dict[str, aiosqlite.Connection] = {} 122 | self.is_closed: bool = False 123 | self.bot: CodingBot = bot 124 | 125 | def __getattr__(self, __name: str) -> Any: 126 | if __name in self.conn: 127 | return self.conn[__name] 128 | return super().__getattribute__(__name) 129 | 130 | async def __aenter__(self) -> "Database": 131 | self.bot.logger.info("Making connections to databases") 132 | self.conn["config"] = await aiosqlite.connect("./database/config.db") 133 | self.conn["warnings"] = await aiosqlite.connect("./database/warnings.db") 134 | self.conn["afk"] = await aiosqlite.connect("./database/afk.db") 135 | self.conn["thanks"] = await aiosqlite.connect("./database/thanks.db") 136 | self.conn["metrics"] = await aiosqlite.connect("./database/metrics.db") 137 | self.conn["tickets"] = await aiosqlite.connect("./database/tickets.db") 138 | await self.init_dbs() 139 | self.bot.logger.info("Finished creating all connections") 140 | return self 141 | 142 | async def fill_cache(self): 143 | self.bot.logger.info("Filling stored cache data before startup") 144 | record = await self.select_record( 145 | "afk", table="afk", arguments=["user_id", "reason", "afk_time"] 146 | ) 147 | 148 | for row in record: 149 | self.bot.afk_cache[row.user_id] = (row.reason, row.afk_time) 150 | 151 | async def init_dbs(self): 152 | async with self.cursor("config") as cursor: 153 | await cursor.execute(PREFIX_CONFIG_SCHEMA) 154 | await cursor.execute(COMMANDS_CONFIG_SCHEMA) 155 | 156 | async with self.cursor("warnings") as cursor: 157 | await cursor.execute(WARNINGS_CONFIG_SCHEMA) 158 | await cursor.execute(HELP_WARNINGS_CONFIG_SCHEMA) 159 | 160 | async with self.cursor("afk") as cursor: 161 | await cursor.execute(AFK_CONFIG_SCHEMA) 162 | 163 | async with self.cursor("thanks") as cursor: 164 | await cursor.execute(THANK_DATA_CONFIG_SCHEMA) 165 | await cursor.execute(THANK_INFO_CONFIG_SCHEMA) 166 | 167 | async with self.cursor("metrics") as cursor: 168 | await cursor.execute(MESSAGE_METRIC_SCHEMA) 169 | 170 | async with self.cursor("tickets") as cursor: 171 | await cursor.execute(TICKETS_CONFIG_SCHEMA) 172 | 173 | await self.commit() 174 | 175 | async def __aexit__(self, *args: Any) -> None: 176 | await self.commit() 177 | await self.close() 178 | 179 | def cursor(self, conn: str) -> aiosqlite.Cursor: 180 | return getattr(self, conn).cursor() 181 | 182 | def __repr__(self) -> str: 183 | return f"" 184 | 185 | async def select_record( 186 | self, 187 | connection: str, 188 | /, 189 | *, 190 | arguments: Tuple[str, ...], 191 | table: str, 192 | where: Optional[Tuple[str, ...]] = None, 193 | values: Optional[Tuple[Any, ...]] = None, 194 | extras: Optional[List[str]] = None, 195 | ) -> Optional[List[Record]]: 196 | statement = f"""SELECT {", ".join(arguments)} FROM {table}""" 197 | if where is not None: 198 | assign_question = map(lambda x: f"{x} = ?", where) 199 | statement += f' WHERE {" AND ".join(assign_question)}' 200 | if extras: 201 | for stuff in extras: 202 | statement += f" {stuff}" 203 | async with self.cursor(connection) as cursor: 204 | await cursor.execute(statement, values or ()) 205 | if rows := [i async for i in cursor]: 206 | return [Record.from_tuple(arguments, row) for row in rows] 207 | return None 208 | 209 | async def delete_record( 210 | self, 211 | connection: str, 212 | /, 213 | *, 214 | table: str, 215 | where: Tuple[str, ...], 216 | values: Optional[Tuple[Any, ...]] = None, 217 | ) -> None: 218 | delete_statement = f"DELETE FROM {table}" 219 | if where is not None: 220 | assign_question = map(lambda x: f"{x} = ?", where) 221 | delete_statement += f' WHERE {" AND ".join(assign_question)}' 222 | 223 | async with self.cursor(connection) as cursor: 224 | await cursor.execute(delete_statement, values or ()) 225 | await getattr(self, connection).commit() 226 | 227 | async def insert_record( 228 | self, 229 | connection: str, 230 | /, 231 | *, 232 | table: str, 233 | values: Tuple[Any, ...], 234 | columns: Tuple[str, ...], 235 | extras: Optional[List[str]] = None, 236 | ) -> None: 237 | insert_statement = """ 238 | INSERT INTO {}({}) VALUES ({}) 239 | """.format( 240 | table, ", ".join(columns), ", ".join(["?"] * len(columns)) 241 | ) 242 | if extras: 243 | for stuff in extras: 244 | insert_statement += f" {stuff}" 245 | async with self.cursor(connection) as cursor: 246 | await cursor.execute(insert_statement, values) 247 | await getattr(self, connection).commit() 248 | 249 | async def update_record( 250 | self, 251 | connection: str, 252 | /, 253 | *, 254 | table: str, 255 | to_update: Tuple[str, ...], 256 | where: Tuple[str, ...], 257 | values: Tuple[Any, ...], 258 | extras: Optional[List[str]] = None, 259 | ) -> None: 260 | update_statement = """ 261 | UPDATE {} SET {} WHERE {} 262 | """.format( 263 | table, 264 | ", ".join(map(lambda x: f"{x} = ?" if "=" not in x else x, to_update)), 265 | " AND ".join(map(lambda x: f"{x} = ?", where)), 266 | ) 267 | if extras: 268 | for stuff in extras: 269 | update_statement += f" {stuff}" 270 | async with self.cursor(connection) as cursor: 271 | await cursor.execute(update_statement, values) 272 | await getattr(self, connection).commit() 273 | 274 | @property 275 | def closed(self): 276 | return self.is_closed 277 | 278 | async def commit(self) -> None: 279 | for conn in self.conn.values(): 280 | await conn.commit() 281 | 282 | async def close(self) -> None: 283 | self.is_closed = True 284 | for conn in self.conn.values(): 285 | await conn.close() 286 | 287 | 288 | class CodingHelp(commands.HelpCommand): 289 | def __init__(self, *args: Any, **kwargs: Any): 290 | super().__init__(*args, **kwargs) 291 | 292 | async def send_bot_help( 293 | self, mapping: Mapping[Optional[commands.Cog], List[commands.Command]] 294 | ) -> None: 295 | embed = discord.Embed(title="Bot Commands", description="Coding Bot V6") 296 | for cog, cmds in mapping.items(): 297 | if cog and not getattr(cog, "hidden", False): 298 | embed.add_field( 299 | name=cog.qualified_name, 300 | value=" ".join(f"`{command.name}`" for command in cmds), 301 | ) 302 | destination = self.get_destination() 303 | await destination.send(embed=embed) 304 | 305 | async def send_group_help(self, group: commands.Group[Any, ..., Any], /) -> None: 306 | embed = discord.Embed( 307 | title=f"{group.qualified_name} Commands", description=group.help or "" 308 | ) 309 | 310 | for command in group.commands: 311 | if not command.hidden: 312 | embed.description += ( 313 | f"\n`{command.qualified_name} - " 314 | f"{command.brief or 'Not documented yet'}`" 315 | ) 316 | 317 | destination = self.get_destination() 318 | await destination.send(embed=embed) 319 | 320 | async def send_command_help( 321 | self, command: commands.Command[Any, ..., Any], / 322 | ) -> None: 323 | embed = discord.Embed( 324 | title=f"{command.qualified_name} Command", 325 | description=command.help.format( 326 | prefix=self.context.prefix, 327 | user=self.context.author.mention, 328 | member=self.context.author.mention, 329 | ) 330 | if command.help 331 | else "No help available for this command.", 332 | ) 333 | 334 | destination = self.get_destination() 335 | await destination.send(embed=embed) 336 | 337 | async def send_cog_help(self, cog: commands.Cog[Any, ..., Any], /) -> None: 338 | embed = discord.Embed( 339 | title=f"{cog.qualified_name} Commands", description=cog.help 340 | ) 341 | 342 | for command in cog.get_commands(): 343 | if not command.hidden: 344 | embed.description += ( 345 | f"\n`{command.qualified_name} " 346 | f"{command.brief or 'Not documented yet'}`" 347 | ) 348 | 349 | destination = self.get_destination() 350 | await destination.send(embed=embed) 351 | 352 | 353 | class CodingBot(commands.Bot): 354 | def __init__(self) -> None: 355 | help_command = CodingHelp( 356 | command_attrs={ 357 | "cooldown": commands.CooldownMapping.from_cooldown( 358 | 3, 5, commands.BucketType.user 359 | ), 360 | "cooldown_after_parsing": True, 361 | "help": HELP_COMMAND, 362 | } 363 | ) 364 | super().__init__( 365 | command_prefix=["."], 366 | intents=INTENTS, 367 | case_insensitive=True, 368 | help_command=help_command, 369 | ) 370 | self.conn: Database = discord.utils.MISSING 371 | self.tracker = InviteTracker(self) 372 | self.welcomer = WelcomeBanner(self) 373 | self.processing_commands = 0 374 | self.message_cache = {} 375 | self.welcomer_enabled = True 376 | self.welcomer_channel_id = 743817386792058971 377 | self.raid_mode_enabled = False 378 | self.raid_checker = AntiRaid(self) 379 | self.afk_cache: Dict[int, Dict[int, Tuple[str, int]]] = {} 380 | self.version: Version = VERSION 381 | self.logger: logging.Logger = create_logger("CodingBot") 382 | self.owner_ids = [ 383 | 556119013298667520, # Swas.py 384 | 879644654587478027, # Swas's alt 385 | 690420846774321221, # BobDotCom 386 | 579041484796461076, # Conch.py 387 | 687882857171255309, # Lexionas74, 388 | 748053138354864229, # Ayu 389 | ] 390 | 391 | async def setup_hook(self) -> None: 392 | self.raid_checker.check_for_raid.start() 393 | for filename in os.listdir("./cogs"): 394 | if filename.endswith(".py"): 395 | await self.load_extension(f"cogs.{filename[:-3]}") 396 | self.logger.info(f"Loaded {filename.title()} Cog") 397 | os.environ["JISHAKU_NO_UNDERSCORE"] = "True" 398 | await self.load_extension("jishaku") 399 | self.logger.info("Loaded Jishaku Cog") 400 | if jishaku := self.get_cog("Jishaku"): 401 | jishaku.hidden = True 402 | 403 | async def start(self, token: str, *, reconnect: bool = True) -> None: 404 | async with Database(self) as self.conn: 405 | async with aiohttp.ClientSession() as self.session: 406 | return await super().start(token, reconnect=reconnect) 407 | 408 | async def on_ready(self) -> None: 409 | await self.wait_until_ready() 410 | await self.tracker.cache_invites() 411 | self.logger.info("Coding Bot V6 is ready for action!") 412 | 413 | async def on_invite_create(self, invite: discord.Invite) -> None: 414 | await self.tracker.update_invite_cache(invite) 415 | 416 | async def on_invite_delete(self, invite: discord.Invite) -> None: 417 | await self.tracker.remove_invite_cache(invite) 418 | 419 | async def on_guild_join(self, guild: discord.Guild) -> None: 420 | await self.tracker.update_guild_cache(guild) 421 | 422 | async def on_guild_remove(self, guild: discord.Guild) -> None: 423 | await self.tracker.remove_guild_cache(guild) 424 | 425 | async def on_member_join(self, member: discord.Member) -> None: 426 | if not self.welcomer_enabled: 427 | return 428 | 429 | banned = await self.raid_checker.cache_insert_or_ban(member) 430 | if banned: 431 | return 432 | 433 | if rules := member.guild.rules_channel: 434 | rules_channel = rules.mention 435 | else: 436 | rules_channel = "No official rule channel set yet." 437 | embed = discord.Embed( 438 | title=f"Welcome to {member.guild.name}!", 439 | description=( 440 | f"Welcome {member.mention}, we're glad you joined! Before you get" 441 | " started, here are some things to check out: \n**Read the Rules:" 442 | f"** {rules_channel} \n**Get roles:** <#726074137168183356> and " 443 | "<#806909970482069556> \n**Want help? Read here:** " 444 | "<#799527165863395338> and <#754712400757784709>" 445 | ), 446 | timestamp=dt.datetime.now(dt.timezone.utc), 447 | ) 448 | file = await self.welcomer.construct_image(member=member) 449 | channel = member.guild.get_channel(self.welcomer_channel_id) 450 | verify_here = member.guild.get_channel(759220767711297566) 451 | 452 | # Assertions for narrowing types 453 | assert channel is not None 454 | assert verify_here is not None 455 | 456 | # type: ignore # Always a Messageable 457 | await channel.send(content=member.mention, file=file) 458 | await verify_here.send( # type: ignore # Always a Messageable 459 | f"Welcome {member.mention}! Follow the instructions " 460 | "in other channels to get verified. :)", 461 | embed=embed, 462 | ) 463 | 464 | async def on_error(self, event_method: str, *args: Any, **kwargs: Any): 465 | await log_error(self, event_method, *args, **kwargs) 466 | 467 | async def send(self, ctx, *args, **kwargs) -> discord.Message: 468 | if getattr(ctx, "msg_before", None) is not None: 469 | key = ctx.msg_before.id 470 | await self.message_cache[key].edit(*args, **kwargs) 471 | else: 472 | key = ctx.message.id 473 | self.message_cache[key] = await ctx.send(*args, **kwargs) 474 | return self.message_cache[key] 475 | 476 | async def reply(self, ctx, *args, **kwargs) -> discord.Message: 477 | if getattr(ctx, "msg_before", None) is not None: 478 | key = ctx.msg_before.id 479 | await self.message_cache[key].edit(*args, **kwargs) 480 | else: 481 | key = ctx.id if isinstance(ctx, discord.Message) else ctx.message.id 482 | self.message_cache[key] = await ctx.reply(*args, **kwargs) 483 | return self.message_cache[key] 484 | 485 | def embed( 486 | self, 487 | *, 488 | title: str = None, 489 | description: str = None, 490 | url=None, 491 | color=0x2F3136, 492 | ): 493 | if url: 494 | return discord.Embed( 495 | title=title, description=description, color=color, url=url 496 | ) 497 | return discord.Embed(title=title, description=description, color=color) 498 | 499 | async def process_edit(self, msg_before, msg_after): 500 | ctx = await super().get_context(msg_after) 501 | if msg_before.id in self.message_cache: 502 | setattr(ctx, "msg_before", msg_before) 503 | await super().invoke(ctx) 504 | 505 | 506 | class TimeConverter(commands.Converter[dt.timedelta]): 507 | async def convert( 508 | self, ctx: commands.Context[CodingBot], argument: str 509 | ) -> dt.timedelta: 510 | """ 511 | Parses a string into a timedelta object 512 | 513 | Parameters 514 | ---------- 515 | ctx : commands.Context 516 | The context of the command 517 | argument : str 518 | The string argument of time to parse 519 | 520 | Returns 521 | ------- 522 | Optional[dt.timedelta] 523 | The parsed timedelta object 524 | 525 | Raises 526 | ------ 527 | commands.BadArgument 528 | If the argument is not a valid time 529 | """ 530 | time_in_secs = parse(argument) 531 | if time_in_secs is None: 532 | raise commands.BadArgument(f"{argument} is not a valid time.") 533 | return dt.timedelta(seconds=time_in_secs) 534 | -------------------------------------------------------------------------------- /ext/ui/view.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import time 4 | from typing import TYPE_CHECKING, Optional 5 | 6 | import discord 7 | from discord import ui 8 | from discord.ext import commands, tasks 9 | import asyncio 10 | from more_itertools import sliced 11 | from ext.consts import TICKET_REPO, OPEN_TICKET_CATEGORY, CLOSED_TICKET_CATEGORY, TICKET_HANDLER_ROLE_ID, TICKET_LOG_CHANNEL 12 | from ext.helpers import get_transcript, upload 13 | 14 | 15 | if TYPE_CHECKING: 16 | from typing_extensions import Self 17 | 18 | from ext.models import CodingBot 19 | 20 | 21 | 22 | class Piston(discord.ui.View): 23 | def __init__( 24 | self, 25 | cog: commands.Cog, 26 | code: str, 27 | language: str, 28 | message: discord.Message, 29 | author: discord.Member, 30 | ) -> None: 31 | self.code = code 32 | self.lang = language 33 | self.cog = cog 34 | self.timestamp = int(time.time()) 35 | self.is_compiled = False 36 | self.output = [] 37 | self.msg = message 38 | self.author = author 39 | self.page = 0 40 | super().__init__() 41 | self.timer.start() 42 | self.get_code_out.start() 43 | # asyncio.run(get_code_out()) 44 | 45 | @tasks.loop(seconds=1, count=30) 46 | async def timer(self): 47 | if self.is_compiled: 48 | self.timer.cancel() 49 | return 50 | await self.msg.edit( 51 | embed=self.cog.bot.embed( 52 | title=f"compiling **[** {int(time.time()) - self.timestamp} **]**" 53 | ) 54 | ) 55 | 56 | @tasks.loop(seconds=1, count=1) 57 | async def get_code_out(self): 58 | self.res = await self.cog.http.api["piston"]["execute"](self.lang, self.code) 59 | self.is_compiled = True 60 | self.timer.cancel() 61 | if "message" in self.res: 62 | return await self.msg.edit( 63 | embed=self.cog.bot.embed( 64 | title=" ", 65 | description="```ansi\n{}\n```".format(self.res["message"]), 66 | ) 67 | ) 68 | lines = self.res["output"].split("\n") 69 | output = [] 70 | for line in lines: 71 | if len(line) > 500: 72 | output.extend(sliced(line, 500)) 73 | elif output: 74 | if len(output[-1].split("\n")) > 15: 75 | output.append(line + "\n") 76 | else: 77 | output[-1] += line + "\n" 78 | else: 79 | output.append(line + "\n") 80 | self.output = output 81 | self.ran = self.res["ran"] 82 | self.is_compiled = True 83 | for child in self.children: 84 | if child.custom_id == "info": 85 | if self.ran: 86 | child.style = discord.ButtonStyle.green 87 | child.label = ( 88 | f"ran '{self.lang}' code | " 89 | f"{int(time.time()) - self.timestamp}s" 90 | ) 91 | else: 92 | child.style = discord.ButtonStyle.red 93 | child.label = ( 94 | f"failed to run '{self.lang}' code | " 95 | f"{int(time.time()) - self.timestamp}s" 96 | ) 97 | await self.msg.edit( 98 | embed=self.cog.bot.embed( 99 | title=" ", description=f"```{self.lang}\n{output[0]}\n```" 100 | ), 101 | view=self, 102 | ) 103 | 104 | @ui.button(label="<", custom_id="prev", disabled=True) 105 | async def _prev(self, interaction: discord.Interaction, button: discord.Button): 106 | if self.page == 0: 107 | button.disabled = True 108 | return await interaction.response.edit_message(view=self) 109 | self.page -= 1 110 | for child in self.children: 111 | if child.custom_id == "next": 112 | child.disabled = False 113 | return await interaction.response.edit_message( 114 | embed=self.cog.bot.embed( 115 | title=" ", 116 | description=f"```{self.lang}\n{self.output[self.page]}\n```", 117 | ), 118 | view=self, 119 | ) 120 | 121 | @ui.button( 122 | label="...", custom_id="info", style=discord.ButtonStyle.gray, disabled=True 123 | ) 124 | async def _info(self, interaction: discord.Interaction, button: discord.Button): 125 | pass 126 | 127 | @ui.button(label=">", custom_id="next", style=discord.ButtonStyle.green) 128 | async def _next(self, interaction: discord.Interaction, button: discord.Button): 129 | if self.page + 1 == len(self.output): 130 | button.disabled = True 131 | return await interaction.response.edit_message(view=self) 132 | self.page += 1 133 | for child in self.children: 134 | if child.custom_id == "prev": 135 | child.disabled = False 136 | try: 137 | await interaction.response.edit_message( 138 | embed=self.cog.bot.embed( 139 | title=" ", 140 | description=f"```{self.lang}\n{self.output[self.page]}\n```", 141 | ), 142 | view=self, 143 | ) 144 | except IndexError: 145 | self.page -= 1 146 | return 147 | 148 | @ui.button( 149 | label="Delete", custom_id="delete", style=discord.ButtonStyle.danger, row=2 150 | ) 151 | async def _delete(self, interaction: discord.Interaction, button: discord.Button): 152 | self.stop() 153 | await self.msg.delete() 154 | 155 | async def interaction_check(self, interaction: discord.Interaction) -> bool: 156 | return interaction.user == self.author 157 | 158 | 159 | class ConfirmButton(ui.View): 160 | if TYPE_CHECKING: 161 | message: discord.Message 162 | 163 | def __init__(self, ctx: commands.Context[CodingBot]) -> None: 164 | super().__init__(timeout=60) 165 | self.confirmed: Optional[bool] = None 166 | self.message: Optional[discord.Message] = None 167 | self.ctx = ctx 168 | 169 | async def interaction_check(self, interaction: discord.Interaction) -> bool: 170 | if self.ctx.author.id != interaction.user.id: 171 | await interaction.response.send_message( 172 | "This is not your button.", ephemeral=True 173 | ) 174 | return False 175 | return True 176 | 177 | async def on_timeout(self) -> None: 178 | if self.message: 179 | return await self.message.delete() 180 | 181 | @ui.button(label="Yes", style=discord.ButtonStyle.green) 182 | async def confirm( 183 | self, 184 | interaction: discord.Interaction, 185 | button: discord.ui.Button[Self], 186 | ) -> None: 187 | self.confirmed = True 188 | if interaction.message: 189 | await interaction.message.delete() 190 | else: 191 | await interaction.delete_original_message() 192 | self.stop() 193 | 194 | @ui.button(label="No", style=discord.ButtonStyle.red) 195 | async def cancel( 196 | self, 197 | interaction: discord.Interaction, 198 | button: discord.ui.Button[Self], 199 | ) -> None: 200 | if interaction.message: 201 | await interaction.message.delete() 202 | else: 203 | await interaction.delete_original_message() 204 | self.stop() 205 | 206 | 207 | class YesNoView(discord.ui.View): # class should be moved to a utils class probably 208 | def __init__(self, *, yes_message, no_message): 209 | super().__init__() 210 | self.yes = None 211 | self.yes_message = yes_message 212 | self.no_message = no_message 213 | 214 | @discord.ui.button(emoji="✅", style=discord.ButtonStyle.green) 215 | async def yes_button(self, interaction, button): 216 | await interaction.response.send_message(self.yes_message) 217 | self.yes = True 218 | self.stop() 219 | 220 | @discord.ui.button(emoji="⛔", style=discord.ButtonStyle.danger) 221 | async def no_button(self, interaction, button): 222 | await interaction.response.send_message(self.no_message) 223 | self.yes = False 224 | self.stop() 225 | 226 | 227 | 228 | # ------------------ TICKET VIEWS --------------------------- 229 | 230 | 231 | class ReasonModal(ui.Modal): 232 | def __init__(self): 233 | super().__init__(title="Ticket Reason", timeout=None) 234 | 235 | self.add_item( 236 | discord.ui.TextInput( 237 | label="Reason", 238 | placeholder="Enter Reason", 239 | style=discord.TextStyle.short, 240 | default="No reason provided", 241 | required=True, 242 | ) 243 | ) 244 | 245 | async def on_submit(self, interaction: discord.Interaction) -> None: 246 | reason = self.children[0].value 247 | 248 | await interaction.response.defer(ephemeral=True) 249 | category: discord.CategoryChannel = discord.utils.get(interaction.guild.categories, id=OPEN_TICKET_CATEGORY) 250 | for ch in category.text_channels: 251 | if ch.topic == f"{interaction.user.id} DO NOT CHANGE THE TOPIC OF THIS CHANNEL!": 252 | await interaction.followup.send("You already have a ticket in {0}".format(ch.mention), ephemeral=True) 253 | return 254 | 255 | r1 : discord.Role = interaction.guild.get_role(TICKET_HANDLER_ROLE_ID) 256 | overwrites = { 257 | interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False), 258 | r1: discord.PermissionOverwrite(read_messages=True, send_messages=True, manage_messages=True), 259 | interaction.user: discord.PermissionOverwrite(read_messages = True, send_messages=True), 260 | interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True) 261 | } 262 | channel = await category.create_text_channel( 263 | name=str(interaction.user), 264 | topic=f"{interaction.user.id} DO NOT CHANGE THE TOPIC OF THIS CHANNEL!", 265 | overwrites=overwrites 266 | ) 267 | i_msg = await channel.send( 268 | embed=discord.Embed( 269 | title="Ticket Created!", 270 | description="Don't ping a staff member, they will be here soon.", 271 | color = discord.Color.green() 272 | ).add_field(name = "📖 Reason", value = reason), 273 | view = CloseButton() 274 | ) 275 | await i_msg.pin() 276 | await interaction.followup.send( 277 | embed= discord.Embed( 278 | description = "Created your ticket in {0}".format(channel.mention), 279 | color = discord.Color.blurple() 280 | ), 281 | ephemeral=True 282 | ) 283 | 284 | log_channel = interaction.guild.get_channel(TICKET_LOG_CHANNEL) 285 | ticket_id = int(time.time()) 286 | embed = discord.Embed( 287 | title = "Ticket Created", 288 | color = 0xe5ffb8 289 | ).add_field( 290 | name = "🆔 Ticket ID", 291 | value = str(ticket_id), 292 | inline = True 293 | ).add_field( 294 | name = "📬 Opened By", 295 | value = f"{interaction.user.mention}", 296 | inline = True 297 | ).add_field( 298 | name = "📪 Closed By", 299 | value = f"Not closed yet", 300 | inline = True 301 | ).add_field( 302 | name = "📖 Reason", 303 | value = f"{reason}", 304 | inline = True 305 | ).add_field( 306 | name = "🕑 Opened at", 307 | value = f"", 308 | inline = True 309 | ).add_field( 310 | name = "🕑 Closed at", 311 | value = f"Not closed yet", 312 | inline = True 313 | ).set_author(name = interaction.user.name, icon_url= interaction.user.avatar.url) 314 | 315 | v = ui.View() 316 | btn = ui.Button(label = "Open", url = i_msg.jump_url) 317 | v.add_item(btn) 318 | 319 | msg = await log_channel.send( 320 | content = f"{r1.mention}", 321 | embed = embed, 322 | view = v, 323 | allowed_mentions = discord.AllowedMentions(roles=True) 324 | ) 325 | await interaction.client.conn.insert_record( 326 | "tickets", 327 | table = "tickets", 328 | columns = ("message_id", "ticket_id", "opened_by", "closed_by", "opened_at", "closed_at", "reason"), 329 | values = ( 330 | msg.id, 331 | ticket_id, 332 | interaction.user.id, 333 | 0, 334 | ticket_id, 335 | 0, 336 | reason 337 | ) 338 | ) 339 | 340 | 341 | 342 | class CreateButton(ui.View): 343 | def __init__(self): 344 | super().__init__(timeout=None) 345 | 346 | @ui.button(style=discord.ButtonStyle.blurple, emoji="🎫",custom_id="ticketopen") 347 | async def ticket(self, interaction: discord.Interaction, button: ui.Button): 348 | await interaction.response.send_modal(ReasonModal()) 349 | 350 | 351 | 352 | class CloseButton(ui.View): 353 | def __init__(self): 354 | super().__init__(timeout=None) 355 | 356 | @ui.button(label="Close the ticket",style=discord.ButtonStyle.red,custom_id="closeticket",emoji="🔒") 357 | async def close(self, interaction: discord.Interaction, button: ui.Button): 358 | await interaction.response.defer(ephemeral=True) 359 | 360 | await interaction.channel.send("Closing this ticket!", delete_after = 10) 361 | 362 | 363 | category: discord.CategoryChannel = discord.utils.get(interaction.guild.categories, id = CLOSED_TICKET_CATEGORY) 364 | r1 : discord.Role = interaction.guild.get_role(TICKET_HANDLER_ROLE_ID) 365 | overwrites = { 366 | interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False), 367 | r1: discord.PermissionOverwrite(read_messages=True, send_messages=True, manage_messages=True), 368 | interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True) 369 | } 370 | await interaction.channel.edit(category=category, overwrites=overwrites) 371 | member = interaction.guild.get_member(int(interaction.channel.topic.split(" ")[0])) 372 | await get_transcript(member=member, channel=interaction.channel) 373 | await interaction.channel.send( 374 | embed= discord.Embed( 375 | description="Ticket Closed!", 376 | color = discord.Color.red() 377 | ), 378 | view = TrashButton() 379 | ) 380 | data = (await interaction.client.conn.select_record( 381 | "tickets", 382 | arguments = ("ticket_id", "message_id", "reason"), 383 | table = "tickets", 384 | where = ("opened_by", ), 385 | values = (member.id, ) 386 | ))[-1] 387 | 388 | ticket_id = data.ticket_id 389 | message_id = data.message_id 390 | reason = data.reason 391 | 392 | await interaction.channel.edit(topic = f"{ticket_id} DO NOT CHANGE THE TOPIC") 393 | file_name = upload(f'storage/tickets/{member.id}.html',member.name, ticket_id) 394 | link = f"https://tcrtickets.vercel.app/?id={file_name}" 395 | 396 | log_channel = interaction.guild.get_channel(TICKET_LOG_CHANNEL) 397 | msg = await log_channel.fetch_message(message_id) 398 | 399 | embed = discord.Embed( 400 | title = "Ticket Closed", 401 | color = 0xffb8c4 402 | ).add_field( 403 | name = "🆔 Ticket ID", 404 | value = str(ticket_id), 405 | inline = True 406 | ).add_field( 407 | name = "📬 Opened By", 408 | value = f"{member.mention}", 409 | inline = True 410 | ).add_field( 411 | name = "📪 Closed By", 412 | value = f"{interaction.user.mention}", 413 | inline = True 414 | ).add_field( 415 | name = "📖 Reason", 416 | value = f"{reason}", 417 | inline = True 418 | ).add_field( 419 | name = "🕑 Opened at", 420 | value = f"", 421 | inline = True 422 | ).add_field( 423 | name = "🕑 Closed at", 424 | value = f"", 425 | inline = True 426 | ).set_author(name = member.name, icon_url= member.avatar.url) 427 | v=ui.View() 428 | btn = ui.Button(url = link, label = "Transcript") 429 | v.add_item(btn) 430 | 431 | await msg.edit(content = None, embeds = [embed], view = v) 432 | try: 433 | await member.send(embed = embed, view = v) 434 | except: pass #type: ignore 435 | 436 | 437 | class TrashButton(ui.View): 438 | def __init__(self): 439 | super().__init__(timeout=None) 440 | 441 | @ui.button(label="Delete the ticket", style=discord.ButtonStyle.red, emoji="🚮", custom_id="trash") 442 | async def trash(self, interaction: discord.Interaction, button: ui.Button): 443 | await interaction.response.defer() 444 | await interaction.channel.send("Deleting the ticket in 3 seconds") 445 | await asyncio.sleep(3) 446 | ticket_id = int(interaction.channel.topic.split(" ")[0]) 447 | 448 | await interaction.channel.delete() 449 | await interaction.client.conn.delete_record( 450 | "tickets", 451 | table = "tickets", 452 | where = ("ticket_id",), 453 | values = (ticket_id,) 454 | ) -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | 5 | from discord.ext import commands 6 | 7 | from ext.models import CodingBot 8 | 9 | if not os.path.exists("./database"): 10 | os.mkdir("./database") 11 | 12 | bot = CodingBot() 13 | 14 | 15 | @bot.before_invoke 16 | async def before_invoke(ctx: commands.Context[CodingBot]): 17 | bot.processing_commands += 1 18 | 19 | 20 | @bot.after_invoke 21 | async def after_invoke(ctx: commands.Context[CodingBot]): 22 | bot.processing_commands -= 1 23 | 24 | 25 | @bot.check 26 | async def check_processing_commands(ctx: commands.Context[CodingBot]): 27 | await bot.wait_until_ready() 28 | return True 29 | 30 | 31 | TOKEN = os.getenv("TOKEN") 32 | bot.run(TOKEN) 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | discord.py==2.3.2 2 | aiosqlite==0.20.0 3 | DiscordUtils==1.3.4 4 | python-dotenv==1.0.1 5 | pytimeparse==1.1.8 6 | humanize==4.9.0 7 | bs4==0.0.2 8 | pillow==10.3.0 9 | cbvx==0.1.0 10 | more_itertools==10.2.0 11 | button_paginator @ git+https://github.com/andrewthederp/Button_paginator 12 | google-generativeai==0.5.4 13 | jishaku==2.5.2 14 | googletrans==4.0.0rc1 15 | PyGithub==2.4.0 16 | chat-exporter==2.8.0 17 | lrclibapi==0.3.1 -------------------------------------------------------------------------------- /startup.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | :: Check for Python Installation 4 | py -3.10 > NUL 5 | if errorlevel 1 (goto errorNoPylauncher) else (goto pyLauncherScript) 6 | 7 | :pyLauncherScript 8 | :: Reaching here means Python is installed. 9 | echo "Upgrading pip..." 10 | py -3.10 -m pip install --upgrade pip > NUL 11 | if errorlevel 1 goto errorPipUpgrade 12 | 13 | py -3.10 -m pip install --upgrade setuptools > NUL 14 | if errorlevel 1 goto errorSetuptoolsUpgrade 15 | 16 | echo "Creating virtual environment..." 17 | py -3.10 -m pip install virtualenv 18 | py -3.10 -m venv venv 19 | .\venv\Scripts\activate.bat 20 | 21 | echo "Installing requirements..." 22 | 23 | pip install -r requirements.txt > NUL 24 | if errorlevel 1 goto errorRequirements 25 | 26 | python main.py 27 | 28 | :pythonScript 29 | echo "Upgrading pip..." 30 | python -m pip install --upgrade pip > NUL 31 | if errorlevel 1 goto errorPipUpgrade 32 | 33 | python -m pip install --upgrade setuptools > NUL 34 | if errorlevel 1 goto errorSetuptoolsUpgrade 35 | 36 | echo "Creating virtual environment..." 37 | python -m pip install virtualenv 38 | python -m venv venv 39 | .\venv\Scripts\activate.bat 40 | 41 | echo "Installing requirements..." 42 | 43 | pip install -r requirements.txt > NUL 44 | if errorlevel 1 goto errorRequirements 45 | 46 | python main.py 47 | 48 | :: Execute stuff... 49 | 50 | :: Once done, exit the batch file -- skips executing the errorNoPython section 51 | goto:eof 52 | 53 | :errorNoPylauncher 54 | python --version | findstr 3.10 > NUL 55 | if errorlevel 1 (goto errorNoPython) else (goto pythonScript) 56 | 57 | :errorNoPython 58 | echo "Python 3.10 is not installed." 59 | echo "Please install Python 3.10 and try again." 60 | echo "Exiting..." 61 | exit 1 62 | 63 | :errorPipUpgrade 64 | echo. 65 | echo Error^: pip upgrade failed 66 | 67 | :errorSetuptoolsUpgrade 68 | echo. 69 | echo Error^: setuptools upgrade failed 70 | 71 | :errorRequirements 72 | echo. 73 | echo Error^: requirements installation failed 74 | 75 | -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | clear 3 | 4 | enable_virual_env () { 5 | 6 | source ./venv/bin/activate 7 | command -v git > /dev/null 2 >&1 8 | if [ $? -eq 0 ]; 9 | then 10 | echo "Repo status: " && git pull 11 | else 12 | echo "Git isn't installed/added to path" 13 | fi 14 | echo "-----------------------------" 15 | pip install -r requirements.txt --quiet --exists-action i 16 | python3 main.py 17 | 18 | } 19 | 20 | if [ -d ./venv/bin ] 21 | then 22 | echo "Detected an existing Virtual Environment" 23 | enable_virual_env 24 | else 25 | echo "Creating a new Virtual Environment" 26 | if command -v python3 > /dev/null 2 >&1 && python3 --version | grep -q 3.10 27 | 28 | then 29 | echo "Detected Python 3.10" 30 | python3.10 -m pip list | grep -q virtualenv 31 | clear 32 | if [ $? -eq 0 ] 33 | then 34 | echo "Detected virtualenv module" 35 | python3.10 -m venv venv 36 | enable_virual_env 37 | else 38 | echo "Installing virtualenv module" 39 | python3.10 -m pip install virtualenv 40 | enable_virual_env 41 | fi 42 | else 43 | echo "Exiting: Python 3.10 is not installed" 44 | exit 1 45 | fi 46 | fi 47 | -------------------------------------------------------------------------------- /storage/README.md: -------------------------------------------------------------------------------- 1 | This directory is used for storing data 2 | -------------------------------------------------------------------------------- /storage/Trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/Trash.png -------------------------------------------------------------------------------- /storage/banned_word.txt: -------------------------------------------------------------------------------- 1 | anal, ass-fucker, assfucker, assfukka, assholes, asswhole, blow job, blowjob, boiolas, buceta, c0cksucker, cl1t, clit, clitoris, clits, cock-sucker, cockface, cockhead, cockmunch, cockmuncher, cocksuck , cocksucked , cocksucker, cocksucking, cocksucks, cocksukka, cokmuncher, coksucka, cum, cunilingus, cunillingus, cunnilingus, cunt, cyalis, d1ck, dick, dickhead, dildo, ejaculate, ejakulate, fingerfuck , fingerfucked , fingerfucker , fingerfuckers, fingerfucking , fingerfucks , fistfuck, fistfucked , fistfucker , fistfuckers , fistfucking , fistfuckings , fistfucks , gangbang, hoare, hoer, hore, horniest, horny, hotsex, jack-off , jackoff, jerk-off , m45terbate, ma5terb8, ma5terbate, master-bate, masterb8, masterbate, masterbation, masturbate, n1gga, n1gger, nigg3r, nigg4h, nigga, nigger, p0rn, porn, rimjaw, rimming, shemale, slut t1tt1e5, t1tties, v14gra, v1gra, w00se, whoar, whore -------------------------------------------------------------------------------- /storage/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/banner.png -------------------------------------------------------------------------------- /storage/fonts/Poppins/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-Black.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-BlackItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-Bold.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-BoldItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-ExtraBold.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-ExtraLight.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-Italic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-Light.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-LightItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-Medium.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-MediumItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-Regular.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-SemiBold.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-Thin.ttf -------------------------------------------------------------------------------- /storage/fonts/Poppins/Poppins-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/Poppins/Poppins-ThinItalic.ttf -------------------------------------------------------------------------------- /storage/fonts/spotify.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Coding-Realm/coding-bot-v6/fa94ba0c7081c5e4df7dd60e4b939b802a80fa33/storage/fonts/spotify.ttf --------------------------------------------------------------------------------