├── .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 |
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[1;31m{}\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
--------------------------------------------------------------------------------