├── .env.sample
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── build.yml
│ ├── codeql-analysis.yml
│ └── ruff.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── cogs
├── chatbot.py
├── config.py
├── countries.py
├── crime.py
├── discordtogether.py
├── docs
│ ├── __init__.py
│ ├── cog.py
│ ├── exts.py
│ ├── fuzzy.py
│ ├── menus.py
│ └── pagination.py
├── economy.py
├── event.py
├── feedback.py
├── fun
│ ├── __init__.py
│ ├── cog.py
│ └── meme.py
├── games
│ ├── __init__.py
│ ├── cog.py
│ └── views
│ │ ├── blackjack.py
│ │ ├── casino.py
│ │ ├── dice.py
│ │ ├── minesweeper.py
│ │ ├── rr.py
│ │ ├── snake.py
│ │ ├── speed_test.py
│ │ ├── sudoku
│ │ ├── generator.py
│ │ └── sudoku.py
│ │ └── wordle.py
├── gifs.py
├── help
│ ├── __init__.py
│ ├── cog.py
│ └── view.py
├── images.py
├── jesterinfo.py
├── levels.py
├── love.py
├── misc.py
├── moderation.py
├── music
│ ├── __init__.py
│ ├── cog.py
│ └── dcutils.py
├── random.py
├── snipe.py
├── staff.py
├── trivia.py
├── urbandictionary.py
└── utils
│ ├── __init__.py
│ ├── calculator.py
│ └── cog.py
├── core
├── __init__.py
├── bot.py
├── constants.py
├── context.py
├── database.py
├── errors.py
├── paginator.py
└── utils
│ ├── __init__.py
│ ├── checks.py
│ ├── comedy.py
│ ├── commands
│ └── eval.py
│ ├── font
│ └── ABeeZee-Regular.otf
│ ├── get_prefix.py
│ └── utils.py
├── main.py
├── pyproject.toml
├── requirements.txt
├── resources
├── anagram.json
├── celebrities.json
├── colors.json
├── foods.json
├── hangman_words.txt
├── save_the_planet.json
├── seasonal
│ ├── april_fools_videos.json
│ └── date_ideas.json
├── snake
│ └── snake_names.json
├── snake_facts.json
├── tags
│ ├── args-kwargs.md
│ ├── async-await.md
│ ├── blocking.md
│ ├── botvar.md
│ ├── class.md
│ ├── classmethod.md
│ ├── codeblock.md
│ ├── comparison.md
│ ├── contribute.md
│ ├── customchecks.md
│ ├── customcooldown.md
│ ├── customhelp.md
│ ├── decorators.md
│ ├── defaultdict.md
│ ├── dict-get.md
│ ├── dictcomps.md
│ ├── docstring.md
│ ├── dotenv.md
│ ├── dunder-methods.md
│ ├── empty-json.md
│ ├── enumerate.md
│ ├── environments.md
│ ├── except.md
│ ├── exit().md
│ ├── f-strings.md
│ ├── faq.md
│ ├── floats.md
│ ├── foo.md
│ ├── for-else.md
│ ├── functions-are-objects.md
│ ├── global.md
│ ├── guilds.md
│ ├── identity.md
│ ├── if-name-main.md
│ ├── indent.md
│ ├── inline.md
│ ├── intents.md
│ ├── iterate-dict.md
│ ├── kindling-projects.md
│ ├── listcomps.md
│ ├── local-file.md
│ ├── microsoft-build-tools.md
│ ├── modmail.md
│ ├── mutability.md
│ ├── mutable-default-args.md
│ ├── names.md
│ ├── off-topic-names.md
│ ├── open.md
│ ├── or-gotcha.md
│ ├── ot.md
│ ├── param-arg.md
│ ├── paste.md
│ ├── pathlib.md
│ ├── pep8.md
│ ├── positional-keyword.md
│ ├── precedence.md
│ ├── quotes.md
│ ├── range-len.md
│ ├── relative-path.md
│ ├── repl.md
│ ├── resources.md
│ ├── return.md
│ ├── round.md
│ ├── scope.md
│ ├── seek.md
│ ├── self.md
│ ├── site.md
│ ├── sql-fstring.md
│ ├── star-imports.md
│ ├── str-join.md
│ ├── string-formatting.md
│ ├── tools.md
│ ├── traceback.md
│ ├── venv.md
│ ├── voice-verification.md
│ ├── windows-path.md
│ ├── with.md
│ ├── xy-problem.md
│ ├── ytdl.md
│ └── zip.md
└── topic.yaml
├── schema.sql
└── scripts
└── create_json_files.py
/.env.sample:
--------------------------------------------------------------------------------
1 | #bot
2 | BOT_TOKEN = ...
3 | WEATHER_KEY = ...
4 | COORDS_KEY = ...
5 | CHATBOT_KEY = ...
6 | RAPID_API_KEY = ...
7 | GOOGLE_KEY = ...
8 |
9 |
10 | #reddit
11 | CLIENT_ID = ...
12 | CLIENT_SECRET = ...
13 | REDDIT_USERNAME = ...
14 | PASSWORD = ...
15 | USER_AGENT = ...
16 |
17 |
18 | #disnake-debug
19 | EMBED_COLOUR = 0x0000ff
20 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
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/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: "run"
2 |
3 | on:
4 | push
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-python@v4
12 | with:
13 | python-version: 3.11
14 | - name: Install dependencies
15 | run: |
16 | python -m pip install -r requirements.txt
17 | python -m pip install disnake-docs disnake-debug
18 | python -m pip install pytest
19 | - name: Run main file
20 | run: python main.py
21 | - name: Run tests
22 | run: pytest tests/
23 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '35 15 * * 6'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37 | # Learn more:
38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39 |
40 | steps:
41 | - name: Checkout repository
42 | uses: actions/checkout@v2
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/.github/workflows/ruff.yml:
--------------------------------------------------------------------------------
1 | # https://beta.ruff.rs
2 | name: ruff
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 | jobs:
11 | ruff:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - run: pip install --user ruff
16 | - run: ruff --format=github .
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | *.db
3 | *.json
4 | .env
5 | dicts/
6 | images/
7 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v4.4.0
4 | hooks:
5 | - id: check-executables-have-shebangs
6 | - id: check-toml
7 | - id: check-yaml
8 | - id: end-of-file-fixer
9 | types: [python]
10 | - id: trailing-whitespace
11 | - id: requirements-txt-fixer
12 |
13 | - repo: https://github.com/MarcoGorelli/auto-walrus
14 | rev: v0.2.2
15 | hooks:
16 | - id: auto-walrus
17 |
18 | - repo: https://github.com/charliermarsh/ruff-pre-commit
19 | rev: v0.0.270
20 | hooks:
21 | - id: ruff
22 |
23 | - repo: https://github.com/psf/black
24 | rev: 23.3.0
25 | hooks:
26 | - id: black
27 |
28 | - repo: https://github.com/codespell-project/codespell
29 | rev: v2.2.4
30 | hooks:
31 | - id: codespell
32 | additional_dependencies:
33 | - tomli
34 |
35 | - repo: https://github.com/tox-dev/pyproject-fmt
36 | rev: "0.11.2"
37 | hooks:
38 | - id: pyproject-fmt
39 |
40 | - repo: https://github.com/abravalheri/validate-pyproject
41 | rev: v0.13
42 | hooks:
43 | - id: validate-pyproject
44 |
45 | - repo: https://github.com/pre-commit/mirrors-mypy
46 | rev: v1.3.0
47 | hooks:
48 | - id: mypy
49 | args:
50 | - --ignore-missing-imports
51 | - --install-types # See mirrors-mypy README.md
52 | - --non-interactive
53 | additional_dependencies: [types-requests]
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | .
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | to contribute to this repository you need to first join the [discord](https://discord.gg/6CAw2xzGK8)
4 |
5 | #### thanks for contributing
6 |
7 | if you do decide to contribute we cant express our gratitude enough
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020-present Caeden
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a
6 | copy of this software and associated documentation files (the "Software"),
7 | to deal in the Software without restriction, including without limitation
8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 | and/or sell copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JesterBot
2 | 
3 | 
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
JesterBot
12 |
13 |
14 | A very powerful, multipurpose and fully customizable discord bot!
15 |
16 | Explore website »
17 |
18 |
19 | Discord Server
20 | ·
21 | Invite JesterBot
22 |
23 |
24 |
25 | ## Can I self-host JesterBot?
26 |
27 | We would rather prefer you not running a direct cloned instance of JesterBot. It would be a ton better to just [`Invite`](https://discord.com/oauth2/authorize?self.bot_id=828363172717133874&scope=bot&permissions=8589934591) the bot.The source here is only for educational purpose and to maintain transparency on how we collect, use your data and off-course on how JesterBot's primary features work. We also do not take any responsibility if the code malfunctions on your side.
28 |
29 | If you decide to edit, compile or use this code in any way. Kindly respect the [`LICENSE`](https://github.com/CaedenPH/JesterBot/blob/main/LICENSE).
30 |
31 |
32 |
33 | ## How do I contribute?
34 |
35 | If you are looking forward to contribute to the project, we welcome you with open arms. kindly open an issue first for discussion.
36 | It's also a good option to join the [`Support Server`](https://discord.gg/2654CuU3ZU) and get into touch with any member of staff
37 |
38 | There are also some easter eggs in the code, let's see if you can find some :wink:
39 |
40 | ## About
41 | Coded in discord.py, JesterBot has easy to use commands and an always active developer this bot is reliable and stable. There is a fully functioning economy system with options to buy certain perks - such as customisable roles. JesterBot specialises in ease of access and certain commands - such as `j.verify` support this.
42 |
43 |
44 | ### Credits
45 | Make sure to type `j.credits` to get the devs of JesterBot!
46 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | -------------------------------------------------- | ------------------ |
10 | | 3.3.4(we do not talk about the previous verions) | :white_check_mark: |
11 |
12 |
13 | ## Reporting a Vulnerability
14 |
15 | id you find a vulnerability please do share with us as we will try out hardest to fix it!
16 |
17 | you can file a bug report we will try to answer as soon as possible
18 |
--------------------------------------------------------------------------------
/cogs/chatbot.py:
--------------------------------------------------------------------------------
1 | import typing
2 |
3 | import disnake
4 | from disnake.ext import commands
5 |
6 | from core import Context, JesterBot
7 | from core.constants import CHATBOT_KEY, RAPID_API_KEY
8 |
9 |
10 | class ChatBot(commands.Cog):
11 | def __init__(self, bot: JesterBot):
12 | self.bot: JesterBot = bot
13 |
14 | async def find_or_insert_channel(self, channel_id: int, **kwargs) -> bool:
15 | insert = kwargs.get("insert", False)
16 | remove = kwargs.get("remove", False)
17 |
18 | result = await self.bot.db.fetchone(
19 | "Select * from chatbot where channel_id = ?", (channel_id,)
20 | )
21 | if not result:
22 | if not insert:
23 | return False
24 | await self.bot.db.execute("Insert into chatbot values (?)", (channel_id,))
25 | if remove:
26 | await self.bot.db.execute(
27 | "Delete from chatbot where channel_id = ?", (channel_id,)
28 | )
29 | return True
30 |
31 | async def get_response(self, message: str, user_id: int) -> typing.Optional[str]:
32 | async with self.bot.client.get(
33 | url="https://random-stuff-api.p.rapidapi.com/ai",
34 | headers={
35 | "authorization": CHATBOT_KEY,
36 | "x-rapidapi-key": RAPID_API_KEY,
37 | "x-rapidapi-host": "random-stuff-api.p.rapidapi.com",
38 | },
39 | params={"msg": message, "id": user_id},
40 | ) as response:
41 | json = await response.json()
42 |
43 | if "error" in json:
44 | return None
45 | return json["AIResponse"]
46 |
47 | @commands.command(aliases=["setup", "setup_ai"])
48 | async def setup_chatbot(self, ctx: Context, channel: disnake.TextChannel) -> None:
49 | await ctx.em(f"Setting up my ai capabilites in {channel.name}...")
50 |
51 | await self.find_or_insert_channel(channel.id, insert=True)
52 | await channel.send(embed=disnake.Embed(description="Chatbot is setup!"))
53 |
54 | await ctx.em("All set up!")
55 |
56 | @commands.command(aliases=["remove"])
57 | async def remove_ai(self, ctx: Context, channel: disnake.TextChannel) -> None:
58 | await ctx.em(f"Removing my ai capabilites in {channel.name}...")
59 |
60 | await self.find_or_insert_channel(channel.id, remove=True)
61 | await channel.send(embed=disnake.Embed(description="Chatbot is removed!"))
62 |
63 | await ctx.em("All removed!")
64 |
65 | @commands.command(aliases=["ai"])
66 | async def chatbot(self, ctx: Context, *, message: str) -> None:
67 | response = await self.get_response(message, ctx.author.id)
68 | await ctx.reply(response)
69 |
70 | @commands.Cog.listener("on_message")
71 | async def chatbot_on_message(self, message: disnake.Message) -> None:
72 | active_channel = await self.find_or_insert_channel(message.channel.id)
73 | if not active_channel:
74 | return
75 | if message.author.bot:
76 | return
77 |
78 | response = await self.get_response(message.content, message.author.id)
79 | await message.channel.send(response)
80 |
81 |
82 | def setup(bot: JesterBot) -> None:
83 | bot.add_cog(ChatBot(bot))
84 |
--------------------------------------------------------------------------------
/cogs/discordtogether.py:
--------------------------------------------------------------------------------
1 | import discord_together
2 | from disnake.ext import commands
3 |
4 | from core import Context, JesterBot
5 | from core.constants import BOT_TOKEN
6 |
7 |
8 | class DiscordTogether(commands.Cog):
9 | def __init__(self, bot: JesterBot):
10 | self.bot = bot
11 | self.together_control = None
12 |
13 | @commands.Cog.listener()
14 | async def on_ready(self):
15 | self.together_control = await discord_together.DiscordTogether(BOT_TOKEN)
16 |
17 | @commands.command(aliases=["yt_together"])
18 | @commands.cooldown(1, 60, commands.BucketType.guild)
19 | async def youtube_together(self, ctx: Context):
20 | if ctx.author.voice is None:
21 | return await ctx.em("You need to be in a voice channel!")
22 | link = await self.together_control.create_link(
23 | ctx.author.voice.channel.id, "youtube"
24 | )
25 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
26 |
27 | @commands.command()
28 | @commands.cooldown(1, 60, commands.BucketType.guild)
29 | async def poker_together(self, ctx: Context):
30 | if ctx.author.voice is None:
31 | return await ctx.em("You need to be in a voice channel!")
32 | link = await self.together_control.create_link(
33 | ctx.author.voice.channel.id, "poker"
34 | )
35 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
36 |
37 | @commands.command()
38 | @commands.cooldown(1, 60, commands.BucketType.guild)
39 | async def chess_together(self, ctx: Context):
40 | if ctx.author.voice is None:
41 | return await ctx.em("You need to be in a voice channel!")
42 | link = await self.together_control.create_link(
43 | ctx.author.voice.channel.id, "chess"
44 | )
45 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
46 |
47 | @commands.command()
48 | @commands.cooldown(1, 60, commands.BucketType.guild)
49 | async def betrayal_together(self, ctx: Context):
50 | if ctx.author.voice is None:
51 | return await ctx.em("You need to be in a voice channel!")
52 | link = await self.together_control.create_link(
53 | ctx.author.voice.channel.id, "betrayal"
54 | )
55 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
56 |
57 | @commands.command()
58 | @commands.cooldown(1, 60, commands.BucketType.guild)
59 | async def fishing_together(self, ctx: Context):
60 | if ctx.author.voice is None:
61 | return await ctx.em("You need to be in a voice channel!")
62 | link = await self.together_control.create_link(
63 | ctx.author.voice.channel.id, "fishing"
64 | )
65 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
66 |
67 | @commands.command()
68 | @commands.cooldown(1, 60, commands.BucketType.guild)
69 | async def awkword(self, ctx: Context):
70 | if ctx.author.voice is None:
71 | return await ctx.em("You need to be in a voice channel!")
72 | link = await self.together_control.create_link(
73 | ctx.author.voice.channel.id, "awkword"
74 | )
75 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
76 |
77 | @commands.command()
78 | @commands.cooldown(1, 60, commands.BucketType.guild)
79 | async def spellcast(self, ctx: Context):
80 | if ctx.author.voice is None:
81 | return await ctx.em("You need to be in a voice channel!")
82 | link = await self.together_control.create_link(
83 | ctx.author.voice.channel.id, "spellcast"
84 | )
85 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
86 |
87 | @commands.command()
88 | @commands.cooldown(1, 60, commands.BucketType.guild)
89 | async def doodle_crew(self, ctx: Context):
90 | if ctx.author.voice is None:
91 | return await ctx.em("You need to be in a voice channel!")
92 | link = await self.together_control.create_link(
93 | ctx.author.voice.channel.id, "doodle-crew"
94 | )
95 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
96 |
97 | @commands.command()
98 | @commands.cooldown(1, 60, commands.BucketType.guild)
99 | async def word_snack(self, ctx: Context):
100 | if ctx.author.voice is None:
101 | return await ctx.em("You need to be in a voice channel!")
102 | link = await self.together_control.create_link(
103 | ctx.author.voice.channel.id, "word-snack"
104 | )
105 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
106 |
107 | @commands.command()
108 | @commands.cooldown(1, 60, commands.BucketType.guild)
109 | async def letter_tile(self, ctx: Context):
110 | if ctx.author.voice is None:
111 | return await ctx.em("You need to be in a voice channel!")
112 | link = await self.together_control.create_link(
113 | ctx.author.voice.channel.id, "letter-tile"
114 | )
115 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
116 |
117 | @commands.command()
118 | @commands.cooldown(1, 60, commands.BucketType.guild)
119 | async def checkers(self, ctx: Context):
120 | if ctx.author.voice is None:
121 | return await ctx.em("You need to be in a voice channel!")
122 | link = await self.together_control.create_link(
123 | ctx.author.voice.channel.id, "checkers"
124 | )
125 | await ctx.reply(f"Click the blue link\n{link}", delete_after=60)
126 |
127 |
128 | def setup(bot):
129 | bot.add_cog(DiscordTogether(bot))
130 |
--------------------------------------------------------------------------------
/cogs/docs/__init__.py:
--------------------------------------------------------------------------------
1 | from .exts import *
2 | from .fuzzy import *
3 | from .pagination import Paginator
4 | from .cog import setup
5 |
--------------------------------------------------------------------------------
/cogs/docs/cog.py:
--------------------------------------------------------------------------------
1 | import itertools
2 | import os
3 |
4 | import disnake
5 | from disnake.ext import commands
6 | from docs import cog
7 | from fuzzywuzzy import fuzz
8 |
9 | from core import Context, JesterBot
10 | from core.constants import ZEN_OF_PYTHON
11 |
12 | from . import RTFM, Colours, Paginator
13 |
14 |
15 | class Docs(cog.Docs, RTFM):
16 | BASE_PYPI_URL = "https://pypi.org"
17 | URL = f"{BASE_PYPI_URL}/pypi/{{package}}/json"
18 | PYPI_ICON = "https://cdn.discordapp.com/emojis/766274397257334814.png"
19 | PYPI_COLOURS = itertools.cycle((Colours.yellow, Colours.blue, Colours.white))
20 | TAGS = [k[:-3] for k in os.listdir("./resources/tags")]
21 |
22 | def __init__(self, bot: JesterBot):
23 | super().__init__(bot)
24 | self.bot = bot
25 | self.items = (
26 | ("disnake", "https://disnake.readthedocs.io/en/latest/"),
27 | ("python", "https://docs.python.org/3/"),
28 | ("aiohttp", "https://aiohttp.readthedocs.io/en/stable/"),
29 | )
30 |
31 | def get_tag_embed(self, author: disnake.Member, tag: str):
32 | tag = max(
33 | [(_tag, fuzz.ratio(tag, _tag)) for _tag in self.TAGS], key=lambda m: m[1]
34 | )[0]
35 |
36 | return (
37 | disnake.Embed(
38 | title=tag.capitalize(),
39 | description=open(f"./resources/tags/{tag}.md", encoding="utf-8").read(),
40 | )
41 | .set_author(name=author.name, icon_url=author.display_avatar.url)
42 | .set_footer(text="Use tag_list command to see all tags")
43 | )
44 |
45 | @commands.command()
46 | async def rtfm(self, ctx: Context, query):
47 | await self.do_rtfm(ctx, "latest", query)
48 |
49 | @commands.command()
50 | async def pypi(self, ctx: Context, package):
51 | embed = disnake.Embed(title="", description="").set_thumbnail(
52 | url=self.PYPI_ICON
53 | )
54 | async with self.bot.client.get(self.URL.format(package=package)) as response:
55 | if response.status == 404:
56 | embed.description = "Package could not be found."
57 | elif response.status == 200 and response.content_type == "application/json":
58 | response_json = await response.json()
59 |
60 | info = response_json["info"]
61 | embed.title = f"{info['name']} v{info['version']}"
62 | embed.url = info["package_url"]
63 | embed.colour = next(self.PYPI_COLOURS)
64 |
65 | summary = disnake.utils.escape_markdown(info["summary"])
66 |
67 | if summary and not summary.isspace():
68 | embed.description = summary
69 | else:
70 | embed.description = "No summary provided."
71 |
72 | else:
73 | embed.description = (
74 | "There was an error when fetching your PyPi package."
75 | )
76 | await ctx.reply(embed=embed)
77 |
78 | @commands.command(aliases=["tags"])
79 | async def tag(self, ctx: Context, tag: str = None) -> None:
80 | if not tag:
81 | return await self.tag_list(ctx)
82 |
83 | tag = tag.lower()
84 | if tag == "list":
85 | return await self.tag_list(ctx)
86 |
87 | embed = self.get_tag_embed(ctx.author, tag)
88 | await ctx.reply(embed=embed)
89 |
90 | @commands.command(aliases=["tags_list"])
91 | async def tag_list(self, ctx: Context) -> None:
92 | lines = sorted([f"» `{name}`" for name in self.TAGS])
93 | paginator = Paginator(
94 | ctx, lines, per_page=10, title=f"All tags (`{len(lines)}` total)"
95 | )
96 | await paginator.start()
97 |
98 | @commands.command()
99 | async def zen(self, ctx: Context, search: int = None) -> None:
100 | embed = disnake.Embed(title="The Zen of Python", description=ZEN_OF_PYTHON)
101 |
102 | if not search:
103 | return await ctx.reply(embed=embed)
104 |
105 | lines = ZEN_OF_PYTHON.splitlines()
106 | if len(lines) > search:
107 | return await ctx.reply(
108 | embed=disnake.Embed(
109 | title=f"The Zen of Python - Line {search}",
110 | description=lines[search],
111 | )
112 | )
113 |
114 | await ctx.reply(
115 | embed=embed.set_footer(text=f"lines {search} is not in the zen lines!")
116 | )
117 |
118 |
119 | def setup(bot: JesterBot) -> None:
120 | bot.add_cog(Docs(bot))
121 |
--------------------------------------------------------------------------------
/cogs/docs/exts.py:
--------------------------------------------------------------------------------
1 | import io
2 | import os
3 | import re
4 | import zlib
5 |
6 | import disnake
7 |
8 | from . import fuzzy
9 |
10 |
11 | class Colours:
12 | white = 0xFFFFFF
13 | blue = 0x0279FD
14 | bright_green = 0x01D277
15 | dark_green = 0x1F8B4C
16 | orange = 0xE67E22
17 | pink = 0xCF84E0
18 | purple = 0xB734EB
19 | soft_green = 0x68C290
20 | soft_orange = 0xF9CB54
21 | soft_red = 0xCD6D6D
22 | yellow = 0xF9F586
23 | python_blue = 0x4B8BBE
24 | python_yellow = 0xFFD43B
25 | grass_green = 0x66FF00
26 | gold = 0xE6C200
27 |
28 |
29 | class SphinxObjectFileReader:
30 | BUFSIZE = 16 * 1024
31 |
32 | def __init__(self, buffer):
33 | self.stream = io.BytesIO(buffer)
34 |
35 | def readline(self):
36 | return self.stream.readline().decode("utf-8")
37 |
38 | def skipline(self):
39 | self.stream.readline()
40 |
41 | def read_compressed_chunks(self):
42 | decompressor = zlib.decompressobj()
43 | while True:
44 | chunk = self.stream.read(self.BUFSIZE)
45 | if len(chunk) == 0:
46 | break
47 | yield decompressor.decompress(chunk)
48 | yield decompressor.flush()
49 |
50 | def read_compressed_lines(self):
51 | buf = b""
52 | for chunk in self.read_compressed_chunks():
53 | buf += chunk
54 | pos = buf.find(b"\n")
55 | while pos != -1:
56 | yield buf[:pos].decode("utf-8")
57 | buf = buf[pos + 1 :]
58 | pos = buf.find(b"\n")
59 |
60 |
61 | class RTFM:
62 | def parse_object_inv(self, stream, url):
63 | result = {}
64 | inv_version = stream.readline().rstrip()
65 |
66 | if inv_version != "# Sphinx inventory version 2":
67 | raise RuntimeError("Invalid objects.inv file version.")
68 |
69 | projname = stream.readline().rstrip()[11:]
70 | version = stream.readline().rstrip()[11:] # noqa
71 |
72 | line = stream.readline()
73 | if "zlib" not in line:
74 | raise RuntimeError("Invalid objects.inv file, not z-lib compatible.")
75 |
76 | entry_regex = re.compile(r"(?x)(.+?)\s+(\S*:\S*)\s+(-?\d+)\s+(\S+)\s+(.*)")
77 | for line in stream.read_compressed_lines():
78 | match = entry_regex.match(line.rstrip())
79 | if not match:
80 | continue
81 |
82 | (name, directive, prio, location, dispname) = match.groups()
83 | (domain, _, subdirective) = directive.partition(":")
84 | if directive == "py:module" and name in result:
85 | continue
86 |
87 | if directive == "std:doc":
88 | subdirective = "label"
89 |
90 | if location.endswith("$"):
91 | location = location[:-1] + name
92 |
93 | key = name if dispname == "-" else dispname
94 | prefix = f"{subdirective}:" if domain == "std" else ""
95 |
96 | if projname == "disnake":
97 | key = key.replace("disnake.ext.commands.", "").replace("disnake.", "")
98 |
99 | result[f"{prefix}{key}"] = os.path.join(url, location)
100 |
101 | return result
102 |
103 | async def build_rtfm_lookup_table(self, page_types):
104 | cache = {}
105 | for key, page in page_types.items():
106 | cache[key] = {}
107 | async with self.bot.client.get(page + "/objects.inv") as resp:
108 | if resp.status != 200:
109 | raise RuntimeError(
110 | "Cannot build rtfm lookup table, try again later."
111 | )
112 |
113 | stream = SphinxObjectFileReader(await resp.read())
114 | cache[key] = self.parse_object_inv(stream, page)
115 |
116 | self._rtfm_cache = cache
117 |
118 | async def do_rtfm(self, ctx, key, obj):
119 | page_types = {
120 | "latest": "https://disnake.readthedocs.io/en/latest",
121 | "python": "https://docs.python.org/3",
122 | }
123 |
124 | if not hasattr(self, "_rtfm_cache"):
125 | await self.build_rtfm_lookup_table(page_types)
126 |
127 | obj = re.sub(r"^(?:disnake\.(?:ext\.)?)?(?:commands\.)?(.+)", r"\1", obj)
128 |
129 | if key.startswith("latest"):
130 | q = obj.lower()
131 | for name in dir(disnake.abc.Messageable):
132 | if name[0] == "_":
133 | continue
134 | if q == name:
135 | obj = f"abc.Messageable.{name}"
136 | break
137 |
138 | cache = list(self._rtfm_cache[key].items())
139 | matches = fuzzy.finder(obj, cache, key=lambda t: t[0], lazy=False)[:8]
140 |
141 | e = disnake.Embed(colour=disnake.Colour.blurple())
142 | if len(matches) == 0:
143 | return await ctx.em("Could not find anything. Sorry.")
144 |
145 | e.description = "\n".join(f"[`{key}`]({url})" for key, url in matches)
146 | await ctx.reply(embed=e)
147 |
--------------------------------------------------------------------------------
/cogs/docs/fuzzy.py:
--------------------------------------------------------------------------------
1 | import heapq
2 | import re
3 | from difflib import SequenceMatcher
4 |
5 |
6 | def ratio(a, b):
7 | m = SequenceMatcher(None, a, b)
8 | return int(round(100 * m.ratio()))
9 |
10 |
11 | def quick_ratio(a, b):
12 | m = SequenceMatcher(None, a, b)
13 | return int(round(100 * m.quick_ratio()))
14 |
15 |
16 | def partial_ratio(a, b):
17 | (short, long) = (a, b) if len(a) <= len(b) else (b, a)
18 | m = SequenceMatcher(None, short, long)
19 |
20 | blocks = m.get_matching_blocks()
21 |
22 | scores = []
23 | for i, j, n in blocks:
24 | start = max(j - i, 0)
25 | end = start + len(short)
26 | o = SequenceMatcher(None, short, long[start:end])
27 | r = o.ratio()
28 |
29 | if 100 * r > 99:
30 | return 100
31 | scores.append(r)
32 |
33 | return int(round(100 * max(scores)))
34 |
35 |
36 | _word_regex = re.compile(r"\W", re.IGNORECASE)
37 |
38 |
39 | def _sort_tokens(a):
40 | a = _word_regex.sub(" ", a).lower().strip()
41 | return " ".join(sorted(a.split()))
42 |
43 |
44 | def token_sort_ratio(a, b):
45 | a = _sort_tokens(a)
46 | b = _sort_tokens(b)
47 | return ratio(a, b)
48 |
49 |
50 | def quick_token_sort_ratio(a, b):
51 | a = _sort_tokens(a)
52 | b = _sort_tokens(b)
53 | return quick_ratio(a, b)
54 |
55 |
56 | def partial_token_sort_ratio(a, b):
57 | a = _sort_tokens(a)
58 | b = _sort_tokens(b)
59 | return partial_ratio(a, b)
60 |
61 |
62 | def _extraction_generator(query, choices, scorer=quick_ratio, score_cutoff=0):
63 | try:
64 | for key, value in choices.items():
65 | score = scorer(query, key)
66 | if score >= score_cutoff:
67 | yield (key, score, value)
68 | except AttributeError:
69 | for choice in choices:
70 | score = scorer(query, choice)
71 | if score >= score_cutoff:
72 | yield (choice, score)
73 |
74 |
75 | def extract(query, choices, *, scorer=quick_ratio, score_cutoff=0, limit=10):
76 | it = _extraction_generator(query, choices, scorer, score_cutoff)
77 |
78 | def key(t):
79 | return t[1] # noqa
80 |
81 | if limit is not None:
82 | return heapq.nlargest(limit, it, key=key)
83 | return sorted(it, key=key, reverse=True)
84 |
85 |
86 | def extract_one(query, choices, *, scorer=quick_ratio, score_cutoff=0):
87 | it = _extraction_generator(query, choices, scorer, score_cutoff)
88 |
89 | def key(t):
90 | return t[1] # noqa
91 |
92 | try:
93 | return max(it, key=key)
94 | except Exception:
95 | return None
96 |
97 |
98 | def extract_or_exact(query, choices, *, limit=None, scorer=quick_ratio, score_cutoff=0):
99 | matches = extract(
100 | query, choices, scorer=scorer, score_cutoff=score_cutoff, limit=limit
101 | )
102 | if len(matches) == 0:
103 | return []
104 |
105 | if len(matches) == 1:
106 | return matches
107 |
108 | top = matches[0][1]
109 | second = matches[1][1]
110 |
111 | if top == 100 or top > (second + 30):
112 | return [matches[0]]
113 |
114 | return matches
115 |
116 |
117 | def extract_matches(query, choices, *, scorer=quick_ratio, score_cutoff=0):
118 | matches = extract(
119 | query, choices, scorer=scorer, score_cutoff=score_cutoff, limit=None
120 | )
121 | if len(matches) == 0:
122 | return []
123 |
124 | top_score = matches[0][1]
125 | to_return = []
126 | index = 0
127 | while True:
128 | try:
129 | match = matches[index]
130 | except IndexError:
131 | break
132 | else:
133 | index += 1
134 |
135 | if match[1] != top_score:
136 | break
137 |
138 | to_return.append(match)
139 | return to_return
140 |
141 |
142 | def finder(text, collection, *, key=None, lazy=True):
143 | suggestions = []
144 | text = str(text)
145 | pat = ".*?".join(map(re.escape, text))
146 | regex = re.compile(pat, flags=re.IGNORECASE)
147 | for item in collection:
148 | to_search = key(item) if key else item
149 | r = regex.search(to_search)
150 | if r:
151 | suggestions.append((len(r.group()), r.start(), item))
152 |
153 | def sort_key(tup):
154 | if key:
155 | return (tup[0], tup[1], key(tup[2]))
156 | return tup
157 |
158 | if lazy:
159 | return (z for _, _, z in sorted(suggestions, key=sort_key))
160 | else:
161 | return [z for _, _, z in sorted(suggestions, key=sort_key)]
162 |
163 |
164 | def find(text, collection, *, key=None):
165 | try:
166 | return finder(text, collection, key=key, lazy=False)[0]
167 | except IndexError:
168 | return None
169 |
--------------------------------------------------------------------------------
/cogs/feedback.py:
--------------------------------------------------------------------------------
1 | import json
2 | from typing import Union
3 |
4 | from disnake.ext import commands
5 |
6 | from core import Context
7 | from core.utils import send_embed, update_json
8 |
9 | with open("./dicts/Feedback.json", "r") as k:
10 | data = json.load(k)
11 |
12 |
13 | class Feedback(commands.Cog):
14 | def __init__(self, bot):
15 | self.bot = bot
16 |
17 | @commands.command()
18 | async def feedback(self, ctx: Context, *, feedback):
19 | with open("./dicts/Feedback.json", "r+") as k:
20 | data = json.load(k)
21 | data["feedback"]["message"].append(feedback)
22 | data["feedback"]["author"].append(ctx.author.name)
23 | data["feedback"]["id"].append(ctx.author.id)
24 | update_json(k, data)
25 |
26 | await send_embed(ctx, "", "Sent!")
27 |
28 | @commands.command()
29 | async def viewfeedback(
30 | self,
31 | ctx: Context,
32 | distance: Union[
33 | int,
34 | str,
35 | ] = 0,
36 | author_from: Union[
37 | int,
38 | str,
39 | ] = None,
40 | ):
41 | await send_embed(
42 | ctx,
43 | "",
44 | "**Error: **distance must be an integer (an index placevalue) if no `author_from` is given!",
45 | ) if hasattr(distance, "upper") and author_from is None else await send_embed(
46 | ctx,
47 | "",
48 | f'*`{distance}`*: "{data["feedback"]["message"][distance]}", submitted by **{data["feedback"]["author"][distance]}**'
49 | if distance != 0
50 | else f"**You can index off of the index on the left side. Type `{ctx.prefix}viewfeedback `\n**\n"
51 | + "\n".join(
52 | [
53 | f"`{num}:` {data['feedback']['message'][k]}**, said by {data['feedback']['author'][k]}**"
54 | for num, k in enumerate(range(len(data["feedback"]["message"])))
55 | if num <= 9
56 | ]
57 | ),
58 | a="Feedback",
59 | i_u=ctx.author.display_avatar.url,
60 | ) if author_from is None else await send_embed(
61 | ctx,
62 | "",
63 | "\n".join(
64 | [
65 | f"`{num}`: {data['feedback']['message'][num]}"
66 | for num, k in enumerate(data["feedback"]["author"])
67 | if k == author_from
68 | ]
69 | ),
70 | a=f"Feedback from {author_from}",
71 | i_u=ctx.author.display_avatar.url,
72 | f=f"All these messges are from {author_from}",
73 | ) if hasattr(
74 | author_from, "upper"
75 | ) else await send_embed(
76 | ctx,
77 | "",
78 | "\n".join(
79 | [
80 | f"`{num}`: {data['feedback']['message'][num]}"
81 | for num, k in enumerate(data["feedback"]["id"])
82 | if k == author_from
83 | ]
84 | ),
85 | a=f"Feedback from {await self.bot.fetch_user(author_from)}",
86 | i_u=ctx.author.display_avatar.url,
87 | f=f"All these messages are from {await self.bot.fetch_user(author_from)}",
88 | )
89 |
90 | @commands.command(aliases=["dial", "call", "assistance"])
91 | async def support(self, ctx: Context):
92 | f = open("./dicts/Dial.json", "r+")
93 | data = json.load(f)
94 | if str(ctx.channel.id) in data:
95 | return await send_embed(
96 | ctx, "", "This is already engaged in a support dial!"
97 | )
98 | data[str(ctx.channel.id)] = True
99 | update_json(f, data)
100 | chan = self.bot.get_channel(866598271991545886)
101 | await send_embed(
102 | ctx,
103 | "",
104 | "You are now connected to **JesterBot offical Support Dial**. Type `j.closesupport` to end your ticket",
105 | )
106 | await send_embed(
107 | chan,
108 | "",
109 | f"You are now connected to **{ctx.author}** in **{ctx.guild}** with channel id of **{ctx.channel.id}**",
110 | )
111 | await chan.send("<@521226389559443461> - <@298043305927639041>")
112 |
113 | @commands.command(
114 | aliases=["closedial", "endsupport", "enddial", "dialend", "hangup"]
115 | )
116 | async def closesupport(self, ctx: Context, chan=""):
117 | c = self.bot.get_channel(866598271991545886)
118 | f = open("./dicts/Dial.json", "r+")
119 | data = json.load(f)
120 | if chan:
121 | if ctx.author.id in [521226389559443461, 298043305927639041]:
122 | if chan in data:
123 | del data[chan]
124 | update_json(f, data)
125 | return await ctx.reply("**Ended**")
126 | if str(ctx.channel.id) not in data:
127 | return await send_embed(ctx, "", "You are not engaged in a support dial!")
128 | del data[str(ctx.channel.id)]
129 | update_json(f, data)
130 | await send_embed(
131 | ctx, "", "The ticket has been closed! We hope your problem got solved!"
132 | )
133 | await send_embed(c, "", f"{ctx.author} ended a call at {ctx.channel.id}")
134 |
135 | @commands.Cog.listener("on_message")
136 | async def dial(self, message):
137 | if message.author == self.bot.user:
138 | return
139 | f = open("./dicts/Dial.json", "r+")
140 | data = json.load(f)
141 | chan = self.bot.get_channel(866598271991545886)
142 | if message.channel == chan:
143 | for k in data:
144 | newchan = self.bot.get_channel(int(k))
145 | return await newchan.send(f"**{message.author}:** {message.content}")
146 | if str(message.channel.id) in data:
147 | return await chan.send(f"**{message.author}:** {message.content}")
148 |
149 |
150 | def setup(bot):
151 | bot.add_cog(Feedback(bot))
152 |
--------------------------------------------------------------------------------
/cogs/fun/__init__.py:
--------------------------------------------------------------------------------
1 | from .meme import Meme
2 | from .cog import setup
3 |
--------------------------------------------------------------------------------
/cogs/fun/meme.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | from disnake import ButtonStyle, Embed, MessageInteraction
4 | from disnake.ui import Button, View, button
5 |
6 | from core import Context
7 | from core.utils.utils import get_colour
8 |
9 |
10 | class Meme(View):
11 | def __init__(self, ctx: Context):
12 | super().__init__(timeout=180)
13 |
14 | self.ctx = ctx
15 |
16 | async def on_timeout(self) -> None:
17 | for child in self.children:
18 | self.remove_item(child)
19 | self.stop()
20 |
21 | async def interaction_check(self, interaction: MessageInteraction) -> bool:
22 | return (
23 | interaction.author == self.ctx.author
24 | and interaction.channel == self.ctx.channel
25 | )
26 |
27 | @button(label="Next", style=ButtonStyle.green, emoji="⏭️")
28 | async def meme(self, button: Button, interaction: MessageInteraction) -> None:
29 | post = random.choice(self.ctx.bot.meme_list)
30 | embed = (
31 | Embed(title=post.title, Colour=get_colour())
32 | .set_image(url=post.url)
33 | .set_footer(
34 | text=f"Requested by {interaction.author.name}",
35 | icon_url=interaction.author.display_avatar.url,
36 | )
37 | )
38 | await interaction.response.defer()
39 | await interaction.edit_original_message(embed=embed, view=self)
40 |
41 | @button(label="Exit", style=ButtonStyle.red, emoji="⏹️")
42 | async def exit(self, button: Button, interaction: MessageInteraction) -> None:
43 | await interaction.response.defer()
44 | await interaction.edit_original_message(view=None)
45 | self.stop()
46 |
--------------------------------------------------------------------------------
/cogs/games/__init__.py:
--------------------------------------------------------------------------------
1 | from .views.blackjack import BlackJack
2 | from .views.casino import Casino
3 | from .views.dice import Dice
4 | from .views.minesweeper import MineSweeper
5 | from .views.rr import RussianRoulette
6 | from .views.snake import Snake
7 | from .views.speed_test import SpeedTest
8 | from .views.sudoku.sudoku import Sudoku
9 | from .views.wordle import Wordle
10 | from .cog import setup
11 |
--------------------------------------------------------------------------------
/cogs/games/views/casino.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import random
3 |
4 | from disnake import ButtonStyle, Embed, Member, MessageInteraction
5 | from disnake.ui import Button, View, button
6 |
7 |
8 | class Casino(View):
9 | def __init__(self, author: Member) -> None:
10 | self.author = author
11 | self.defualtstring = [
12 | "Casino Machine $",
13 | "Get Three numbers in a row for a PRIZE",
14 | ]
15 | super().__init__(timeout=60.0)
16 | self.retry.disabled = True
17 |
18 | async def on_timeout(self) -> None:
19 | for child in self.children:
20 | self.remove_item(child)
21 | self.stop()
22 |
23 | async def interaction_check(self, interaction: MessageInteraction) -> bool:
24 | if interaction.author != self.author:
25 | return False
26 | return True
27 |
28 | @button(label="Play", style=ButtonStyle.green, emoji="▶️")
29 | async def play(self, button: Button, interaction: MessageInteraction) -> None:
30 | self.exit.disabled = True
31 | self.play.disabled = True
32 | intsthink = Embed(
33 | title=self.defualtstring[0], description="```...```"
34 | ).set_footer(text=self.defualtstring[1])
35 |
36 | await interaction.response.edit_message(embed=intsthink, view=self)
37 |
38 | r_ints = (random.randint(1, 9), random.randint(1, 9), random.randint(1, 9))
39 | (result, ints) = ([], None)
40 |
41 | for i in r_ints:
42 | result.append(str(i))
43 | ints = Embed(
44 | title=self.defualtstring[0], description=f"```{''.join(result)}```"
45 | ).set_footer(text=self.defualtstring[1])
46 | await interaction.edit_original_message(embed=ints, view=self)
47 | await asyncio.sleep(0.2)
48 |
49 | self.retry.disabled = False
50 | self.exit.disabled = False
51 | await interaction.edit_original_message(embed=ints, view=self)
52 |
53 | if len(set(r_ints)) == 1:
54 | awinningembed = Embed(
55 | title="WINNER",
56 | description=f"{interaction.author.mention} has won {random.randint(1, 1000)}$",
57 | )
58 | self.stop()
59 | return await interaction.send(embed=awinningembed)
60 |
61 | @button(label="Retry", style=ButtonStyle.green, emoji="🔄")
62 | async def retry(self, button: Button, interaction: MessageInteraction) -> None:
63 | intsthink1 = Embed(
64 | title=self.defualtstring[0], description="```...```"
65 | ).set_footer(text=self.defualtstring[1])
66 | self.exit.disabled = True
67 | await interaction.response.edit_message(embed=intsthink1, view=self)
68 |
69 | r_ints = (random.randint(1, 9), random.randint(1, 9), random.randint(1, 9))
70 |
71 | (result, ints) = ([], None)
72 | for i in r_ints:
73 | result.append(str(i))
74 | ints = Embed(
75 | title=self.defualtstring[0], description=f"```{''.join(result)}```"
76 | ).set_footer(text=self.defualtstring[1])
77 | await interaction.edit_original_message(embed=ints, view=self)
78 | await asyncio.sleep(0.2)
79 |
80 | self.retry.disabled = False
81 | self.exit.disabled = False
82 | await interaction.edit_original_message(embed=ints, view=self)
83 |
84 | if len(set(r_ints)) == 1:
85 | bwinningembed = Embed(
86 | title="WINNER",
87 | description=f"{interaction.author.mention} has won {random.randint(1, 1000)}$",
88 | )
89 | self.stop()
90 | return await interaction.send(embed=bwinningembed)
91 |
92 | @button(label="Exit", style=ButtonStyle.red, emoji="⏹️")
93 | async def exit(self, button: Button, interaction: MessageInteraction) -> None:
94 | await interaction.response.defer()
95 | await interaction.edit_original_message(view=None)
96 | self.stop()
97 |
--------------------------------------------------------------------------------
/cogs/games/views/dice.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | from disnake import ButtonStyle, Embed, MessageInteraction
4 | from disnake.ui import Button, View, button
5 |
6 | from core.utils import get_colour
7 |
8 |
9 | class Dice(View):
10 | def __init__(self, ctx):
11 | super().__init__(timeout=180)
12 |
13 | self.ctx = ctx
14 | self.role_count: int = 0
15 |
16 | async def on_timeout(self) -> None:
17 | for child in self.children:
18 | self.remove_item(child)
19 | self.stop()
20 |
21 | async def interaction_check(self, interaction: MessageInteraction) -> bool:
22 | return (
23 | interaction.author == self.ctx.author
24 | and interaction.channel == self.ctx.channel
25 | )
26 |
27 | @button(label="Roll", style=ButtonStyle.green, emoji="▶️")
28 | async def roll(self, button: Button, interaction: MessageInteraction) -> None:
29 | self.role_count += 1
30 | random_number = random.randint(0, 5)
31 | emoji = {
32 | 0: "<:dice1:932735376089559090>",
33 | 1: "<:dice2:932735375649157122>",
34 | 2: "<:dice3:932735376236363866>",
35 | 3: "<:dice4:932735376160862278>",
36 | 4: "<:dice5:932735376118923264>",
37 | 5: "<:dice6:932735376274120765>",
38 | }
39 | embed = Embed(
40 | title="<:dicetitle:932727881069641858> Dice <:dicetitle:932727881069641858>",
41 | description=f"You rolled a: {emoji[random_number]} `{random_number}` {emoji[random_number]}",
42 | Colour=get_colour(),
43 | ).set_footer(
44 | text=f"{interaction.author.name} is playing with some dice | Role count: {self.role_count}",
45 | icon_url=(interaction.author.display_avatar.url),
46 | )
47 | await interaction.response.defer()
48 | await interaction.edit_original_message(embed=embed, view=self)
49 |
50 | @button(label="Stop", style=ButtonStyle.red, emoji="⏹️")
51 | async def Stop(self, button: Button, interaction: MessageInteraction) -> None:
52 | await interaction.response.defer()
53 | await interaction.edit_original_message(view=None)
54 | self.stop()
55 |
--------------------------------------------------------------------------------
/cogs/games/views/rr.py:
--------------------------------------------------------------------------------
1 | import random
2 |
3 | from disnake import ButtonStyle, Embed, MessageInteraction
4 | from disnake.ui import Button, View, button
5 |
6 |
7 | class RussianRoulette(View):
8 | def __init__(self, ctx):
9 | super().__init__(timeout=180)
10 |
11 | self.ctx = ctx
12 |
13 | async def on_timeout(self) -> None:
14 | for child in self.children:
15 | self.remove_item(child)
16 | self.stop()
17 |
18 | async def interaction_check(self, interaction: MessageInteraction) -> bool:
19 | return (
20 | interaction.author == self.ctx.author
21 | and interaction.channel == self.ctx.channel
22 | )
23 |
24 | @button(label="Play", style=ButtonStyle.green, emoji="▶️")
25 | async def play(self, button: Button, interaction: MessageInteraction) -> None:
26 | random_choice = random.choice(
27 | ["🌹 / **You lived**", "<:gun:931861130488467456> / **You died**"]
28 | )
29 | embed_colour = {
30 | "🌹 / **You lived**": 0x32CD32,
31 | "<:gun:931861130488467456> / **You died**": 0x8B0000,
32 | }
33 |
34 | footer_text = random.choice(
35 | [
36 | "loves to play this game",
37 | "must like excitement",
38 | "is definitely a risk taker",
39 | "definitely hates life",
40 | "plays this game 24/7",
41 | "has issues",
42 | "probably needs some help",
43 | ]
44 | )
45 |
46 | embed = Embed(
47 | description=random_choice, colour=embed_colour[random_choice]
48 | ).set_footer(
49 | text=f"{interaction.author.name} {footer_text}",
50 | icon_url=(interaction.author.display_avatar.url),
51 | )
52 |
53 | await interaction.response.defer()
54 | await interaction.edit_original_message(embed=embed, view=self)
55 |
56 | @button(label="Exit", style=ButtonStyle.red, emoji="⏹️")
57 | async def exit(self, button: Button, interaction: MessageInteraction) -> None:
58 | await interaction.response.defer()
59 | await interaction.edit_original_message(view=None)
60 | self.stop()
61 |
--------------------------------------------------------------------------------
/cogs/gifs.py:
--------------------------------------------------------------------------------
1 | from random import choice, randint
2 |
3 | import disnake
4 | from disnake.ext import commands
5 |
6 | from core import Context
7 | from core.utils import get_colour, send_embed
8 | from core.utils.comedy import fact, joke, pickup, quote
9 |
10 |
11 | class JesterJokes(commands.Cog):
12 | def __init__(self, bot):
13 | self.bot = bot
14 |
15 | @commands.command(
16 | aliases=["ranfact", "r", "randomfact"],
17 | description="""Returns a random fact [ranfact, rf]""",
18 | )
19 | async def fact(self, ctx: Context):
20 | fact_string = await fact()
21 | await send_embed(ctx, "Fact", fact_string)
22 |
23 | @commands.command(description="""Returns a random quote""")
24 | async def quote(self, ctx: Context):
25 | quote_string = await quote(self.bot)
26 | await send_embed(ctx, "Quote", quote_string)
27 |
28 | @commands.command(
29 | aliases=["pl", "pickup", "pickline"],
30 | description="""Returns a random Pickup Line.""",
31 | )
32 | async def pickup_line(self, ctx: Context):
33 | pickup_line = await pickup()
34 | await send_embed(ctx, "Pickup line", pickup_line)
35 |
36 | @commands.command(
37 | aliases=["insultme", "Mean", "Insult_Me"],
38 | description="The specified member gets insulted",
39 | )
40 | async def insult(self, ctx: Context, user: disnake.Member = None):
41 | m = self.bot.get_user(828363172717133874)
42 | if user is None:
43 | user = self.bot.get_user(ctx.author.id)
44 |
45 | if user == m:
46 | embed = disnake.Embed(colour=get_colour())
47 | user = self.bot.get_user(ctx.author.id)
48 | embed = disnake.Embed(title="You shmuck...I am god")
49 | await ctx.reply(embed=embed)
50 | else:
51 | async with self.bot.client.get(
52 | url="https://insult.mattbas.org/api/insult.json"
53 | ) as response:
54 | fox = await response.json()
55 | foxupdate = fox["insult"]
56 | embed = disnake.Embed(
57 | description=f"{user.mention} {foxupdate}", colour=get_colour()
58 | )
59 | await ctx.reply(embed=embed)
60 |
61 | @commands.command(
62 | aliases=["dis", "Diss"], description="The specified member gets dissed"
63 | )
64 | async def disthem(self, ctx: Context, user: disnake.Member = None):
65 | if user is None:
66 | user = self.bot.get_user(ctx.author.id)
67 | async with self.bot.client.get(
68 | url="https://evilinsult.com/generate_insult.php?lang=en&type=json"
69 | ) as response:
70 | fox = await response.json()
71 | foxupdate = fox["insult"]
72 |
73 | embed = disnake.Embed(
74 | description=f"{user.mention} {foxupdate}", colour=get_colour()
75 | )
76 | await ctx.reply(embed=embed)
77 |
78 | @commands.command(
79 | aliases=["Chuck_norris", "Chucky", "norris"],
80 | description="Sends a random chuck norris joke/fact",
81 | )
82 | async def chuck(self, ctx: Context, user: disnake.Member = None):
83 | if user is None:
84 | user = self.bot.get_user(ctx.author.id)
85 | async with self.bot.client.get(
86 | url="https://api.chucknorris.io/jokes/random"
87 | ) as response:
88 | fox = await response.json()
89 | foxupdate = fox["value"]
90 |
91 | embed = disnake.Embed(description=f"{foxupdate}", colour=get_colour())
92 | await ctx.reply(embed=embed)
93 |
94 | @commands.command(
95 | aliases=["adj", "random_adj", "randadj", "Rand_Adj", "random_adjective"],
96 | description="Sends a random adjective",
97 | )
98 | async def adjective(self, ctx: Context):
99 | async with self.bot.client.get(
100 | url="https://raw.githubusercontent.com/dariusk/corpora/master/data/words/adjs.json"
101 | ) as response:
102 | fox = await response.json()
103 | foxupdate = fox["adjs"]
104 |
105 | embed = disnake.Embed(
106 | title=f"{foxupdate[randint(1, 950)]}", colour=get_colour()
107 | )
108 |
109 | await ctx.reply(embed=embed)
110 |
111 | @commands.command(
112 | aliases=["smck", "slap", "BitchSlap", "Hit", "Spank"],
113 | description="The specified member gets slapped - Sends a random giffy",
114 | )
115 | async def smack(self, ctx: Context, user: disnake.Member = None):
116 | if user is None:
117 | user = self.bot.get_user(ctx.author.id)
118 |
119 | url = [
120 | "https://media.giphy.com/media/l5JdQaCUXTGy7AIGHq/giphy.gif",
121 | "https://media.giphy.com/media/11N2zX8Swp3csg/giphy.gif",
122 | "https://media.giphy.com/media/xUA7b9Wc1uaT52QfO8/giphy.gif",
123 | "https://media.giphy.com/media/3oEduOWVxygmeDIKPu/giphy.gif",
124 | "https://media.giphy.com/media/Qumf2QovTD4QxHPjy5/giphy.gif",
125 | "https://media.giphy.com/media/uqSU9IEYEKAbS/giphy.gif",
126 | "https://media.giphy.com/media/lX03hULhgCYQ8/giphy.gif",
127 | "https://media.giphy.com/media/mEtSQlxqBtWWA/giphy.gif",
128 | "https://media.giphy.com/media/gSIz6gGLhguOY/giphy.gif",
129 | "https://media.giphy.com/media/uG3lKkAuh53wc/giphy.gif",
130 | "https://media.giphy.com/media/P1EomtpqQW34c/giphy.gif",
131 | "https://media.giphy.com/media/vxvNnIYFcYqEE/giphy.gif",
132 | "",
133 | ]
134 |
135 | embed = disnake.Embed(
136 | description=f"{user.mention} got smacked", colour=get_colour()
137 | )
138 | embed.set_image(url=choice(url))
139 | await ctx.reply(embed=embed)
140 |
141 | @commands.command(aliases=["jokes"], description="Sends a random joke")
142 | async def joke(self, ctx: Context):
143 | await send_embed(ctx, "Joke", await joke())
144 |
145 |
146 | def setup(bot):
147 | bot.add_cog(JesterJokes(bot))
148 |
--------------------------------------------------------------------------------
/cogs/help/__init__.py:
--------------------------------------------------------------------------------
1 | from .view import DropdownView
2 | from .cog import setup
3 |
--------------------------------------------------------------------------------
/cogs/help/view.py:
--------------------------------------------------------------------------------
1 | import disnake
2 | from disnake.ext import commands
3 |
4 | from core.constants import COG_EMOJIS, HOME
5 |
6 |
7 | class Dropdown(disnake.ui.Select):
8 | def __init__(self, data, ctx: commands.Context, utils):
9 | self.data = data
10 | self.ctx = ctx
11 | self.utils = utils
12 |
13 | options = [
14 | disnake.SelectOption(
15 | label="Home", description="Return to the main help panel", emoji=HOME
16 | )
17 | ]
18 | for key in data:
19 | options.append(
20 | disnake.SelectOption(
21 | label=key,
22 | description=f"{len([k for k in ctx.bot.get_cog(key).walk_commands() if not k.hidden])} commands",
23 | emoji=ctx.bot.get_emoji(COG_EMOJIS[key]),
24 | )
25 | )
26 |
27 | super().__init__(
28 | placeholder="Choose a category.",
29 | min_values=1,
30 | max_values=1,
31 | options=options,
32 | )
33 |
34 | async def callback(self, interaction: disnake.MessageInteraction):
35 | label = interaction.values[0]
36 |
37 | if label not in self.data:
38 | embed = await self.utils.main_help_embed(self.ctx)
39 | return await interaction.response.edit_message(embed=embed, view=self.view)
40 |
41 | cog = self.ctx.bot.get_cog(label)
42 | embed = await self.utils.specific_cog(cog, self.ctx)
43 |
44 | if interaction.author == self.ctx.author:
45 | return await interaction.response.edit_message(embed=embed, view=self.view)
46 | await interaction.response.send_message(embed=embed, ephemeral=True)
47 |
48 |
49 | class DropdownView(disnake.ui.View):
50 | def __init__(self, data, ctx: commands.Context, utils):
51 | super().__init__(timeout=None)
52 | self.add_item(Dropdown(data, ctx, utils))
53 |
--------------------------------------------------------------------------------
/cogs/music/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import setup
2 |
--------------------------------------------------------------------------------
/cogs/music/cog.py:
--------------------------------------------------------------------------------
1 | import disnake
2 | from disnake.ext import commands
3 |
4 | from core.context import Context
5 | from core.utils import get_colour, send_embed
6 |
7 | from . import dcutils
8 |
9 |
10 | async def embed2(ctx, description) -> disnake.Message:
11 | embed = disnake.Embed(description=description, colour=get_colour())
12 | embed.set_footer(text="Type j.help Music to get all the music commands.")
13 | embed.set_author(name="Music", icon_url=ctx.author.display_avatar.url)
14 |
15 | return await ctx.reply(embed=embed)
16 |
17 |
18 | class Music(commands.Cog):
19 | def __init__(self, client):
20 | self.client = client
21 | self.music = dcutils.Music()
22 |
23 | @commands.command()
24 | async def lyrics(self, ctx: Context, *, song):
25 | async with self.bot.client.get(
26 | url=f"https://some-random-api.ml/lyrics?title={song}"
27 | ) as response:
28 | fox = await response.json()
29 | await send_embed(ctx, f'Lyrics of {fox["title"]}', fox["lyrics"])
30 |
31 | @commands.command()
32 | async def join(self, ctx: Context):
33 | if not ctx.author.voice:
34 | return await embed2(ctx, "You are not in a music channel.")
35 |
36 | voice_channel = ctx.author.voice.channel
37 | if not ctx.voice_client:
38 | await voice_channel.connect()
39 | return await embed2(ctx, "Joined")
40 | else:
41 | if ctx.voice_client.channel == voice_channel:
42 | return await embed2(ctx, "I am already here.")
43 | await ctx.voice_client.move_to(voice_channel)
44 |
45 | @commands.command()
46 | async def leave(self, ctx: Context):
47 | if ctx.voice_client:
48 | await ctx.voice_client.disconnect()
49 | return await ctx.message.add_reaction("🎵")
50 | await embed2(ctx, "I am not in a music channel.")
51 |
52 | @commands.command()
53 | async def play(self, ctx: Context, *, url):
54 | if not ctx.author.voice:
55 | return await embed2(ctx, "You are not in a music channel.")
56 |
57 | voice_channel = ctx.author.voice.channel
58 | if not ctx.voice_client:
59 | await voice_channel.connect()
60 |
61 | player = self.music.get_player(guild_id=ctx.guild.id)
62 | if not player:
63 | player = self.music.create_player(ctx, ffmpeg_error_betterfix=True)
64 | if not ctx.voice_client.is_playing():
65 | await player.queue(url, search=True)
66 | song = await player.play()
67 | await embed2(
68 | ctx,
69 | f"**Playing:** {song.name} \n**Duration**: {round(song.duration / 60)} minutes",
70 | )
71 | else:
72 | song = await player.queue(url, search=True)
73 | await embed2(ctx, f"**Queued:** {song.name}")
74 |
75 | @commands.command()
76 | async def pause(self, ctx: Context):
77 | player = self.music.get_player(guild_id=ctx.guild.id)
78 | if not player:
79 | return await embed2(ctx, "You are not playing any music.")
80 |
81 | song = await player.pause()
82 | await embed2(ctx, f"**Paused:** {song.name}")
83 |
84 | @commands.command()
85 | async def resume(self, ctx: Context):
86 | player = self.music.get_player(guild_id=ctx.guild.id)
87 | song = await player.resume()
88 | await embed2(ctx, f"**Resumed:** {song.name}")
89 |
90 | @commands.command()
91 | async def stop(self, ctx: Context):
92 | player = self.music.get_player(guild_id=ctx.guild.id)
93 | if not player:
94 | return await embed2(ctx, "You are not playing any music.")
95 |
96 | await player.stop()
97 | await embed2(ctx, "Stopped")
98 |
99 | @commands.command()
100 | async def loop(self, ctx: Context):
101 | player = self.music.get_player(guild_id=ctx.guild.id)
102 | if not player:
103 | return await embed2(ctx, "You are not playing any music.")
104 |
105 | song = await player.toggle_song_loop()
106 | if song.is_looping:
107 | await embed2(ctx, f"**Enabled loop for:** {song.name}")
108 | else:
109 | await embed2(ctx, f"**Disabled loop for:** {song.name}")
110 |
111 | @commands.command()
112 | async def queue(self, ctx: Context):
113 | x = []
114 | z = 0
115 | player = self.music.get_player(guild_id=ctx.guild.id)
116 |
117 | for song in player.current_queue():
118 | if z == 0:
119 | x.append(f"**{z}**: {song.name} - {round(song.duration / 60)}m")
120 | else:
121 | x.append(f"\n**{z}**: {song.name}- {round(song.duration / 60)}m")
122 | z += 1
123 |
124 | await embed2(ctx, f"{', '.join(x)}")
125 |
126 | @commands.command()
127 | async def nowplaying(self, ctx: Context):
128 | player = self.music.get_player(guild_id=ctx.guild.id)
129 | song = player.now_playing()
130 | await embed2(ctx, song.name)
131 |
132 | @commands.command()
133 | async def skip(self, ctx: Context):
134 | z = 0
135 |
136 | player = self.music.get_player(guild_id=ctx.guild.id)
137 | for song in player.current_queue():
138 | if z == 0:
139 | song1 = song.name
140 | elif z == 1:
141 | song2 = song.name
142 | z += 1
143 | await embed2(ctx, f"**Skipped from:** *{song1}* **To:** \n*{song2}*")
144 |
145 | @commands.command()
146 | async def volume(self, ctx: Context, vol: str):
147 | player = self.music.get_player(guild_id=ctx.guild.id)
148 | (song, volume) = await player.change_volume(
149 | float(vol) / 100
150 | ) # volume should be a float between 0 to 1
151 | await embed2(ctx, f"**Changed volume for:** *{song.name}* **to {volume*100}**%")
152 |
153 | @commands.command()
154 | async def remove_song(self, ctx: Context, index):
155 | player = self.music.get_player(guild_id=ctx.guild.id)
156 | song = await player.remove_from_queue(int(index))
157 | await embed2(ctx, f"**Removed:** {song.name} from queue")
158 |
159 |
160 | def setup(client):
161 | client.add_cog(Music(client))
162 |
--------------------------------------------------------------------------------
/cogs/snipe.py:
--------------------------------------------------------------------------------
1 | import random
2 | import time
3 | import typing as t
4 | from datetime import datetime
5 |
6 | import disnake
7 | from disnake.ext import commands
8 |
9 | from core import Context, JesterBot
10 | from core.constants import CLOSE
11 | from core.utils import get_colour, send_embed
12 |
13 |
14 | class Snipe(commands.Cog):
15 | def __init__(self, bot: JesterBot):
16 | self.bot = bot
17 |
18 | async def getch_user(self, user_id: int) -> t.Optional[disnake.User]:
19 | user = self.bot.get_user(user_id)
20 | if user:
21 | return user
22 |
23 | try:
24 | user = await self.bot.fetch_user(user_id)
25 | except disnake.HTTPException:
26 | pass
27 | return user or None
28 |
29 | @commands.Cog.listener()
30 | async def on_message_delete(self, message: disnake.Message):
31 | if message.author.bot:
32 | return
33 |
34 | await self.bot.db.update(
35 | "INSERT INTO snipe VALUES (?, ?, ?, ?, ?)",
36 | (
37 | message.id,
38 | message.channel.id,
39 | message.author.id,
40 | message.content or message.attachments[0].url,
41 | time.time(),
42 | ),
43 | )
44 |
45 | @commands.Cog.listener()
46 | async def on_message_edit(self, before: disnake.Message, after: disnake.Message):
47 | if before.author.bot:
48 | return
49 | if before.content == after.content:
50 | return
51 |
52 | await self.bot.db.update(
53 | "INSERT INTO edit_snipe VALUES (?, ?, ?, ?, ?, ?)",
54 | (
55 | random.randint(100000000, 100000000000),
56 | before.channel.id,
57 | before.author.id,
58 | before.content or before.attachments[0].url,
59 | after.content or after.attachments[0].url,
60 | time.time(),
61 | ),
62 | )
63 |
64 | @commands.command()
65 | async def snipe(self, ctx: Context, amount: int = 0):
66 | results = (
67 | await self.bot.db.fetchall(
68 | "SELECT * FROM snipe WHERE channel_id = ?", (ctx.channel.id,)
69 | )
70 | )[::-1]
71 | if not results:
72 | m = await send_embed(
73 | ctx, "Snipe", f"> No deleted messages found in {ctx.channel.mention}."
74 | )
75 | return await m.add_reaction(CLOSE)
76 | if amount >= len(results):
77 | m = await send_embed(
78 | ctx, "Snipe", f"> I found no messages {amount} deletes ago."
79 | )
80 | return await m.add_reaction(CLOSE)
81 |
82 | result = results[amount]
83 | user = await self.getch_user(result[2])
84 |
85 | embed = disnake.Embed(
86 | description=f"""
87 | Message in: {ctx.channel.mention}
88 | Message from: {user.mention}
89 | Message deleted at: {disnake.utils.format_dt(datetime.fromtimestamp(result[4]))}
90 | ```yaml
91 | {result[3]}
92 | ```
93 | """,
94 | colour=get_colour(),
95 | ).set_thumbnail(url=user.display_avatar.url or ctx.author.default_avatar)
96 | await ctx.send(embed=embed)
97 |
98 | @commands.command()
99 | async def aim(self, ctx: Context, user: disnake.Member = None):
100 | if user is None:
101 | user = ctx.author
102 |
103 | results = (
104 | await self.bot.db.fetchall(
105 | "SELECT * FROM snipe WHERE channel_id = ? and user_id = ?",
106 | (ctx.channel.id, user.id),
107 | )
108 | )[::-1]
109 | if not results:
110 | m = await send_embed(
111 | ctx,
112 | "Snipe",
113 | f"> No deleted messages from {user.mention} found in {ctx.channel.mention}.",
114 | )
115 | return await m.add_reaction(CLOSE)
116 |
117 | result = results[0]
118 | user = await self.getch_user(result[2])
119 |
120 | embed = disnake.Embed(
121 | description=f"""
122 | Message in: {ctx.channel.mention}
123 | Message from: {user.mention}
124 | Message deleted at: {disnake.utils.format_dt(datetime.fromtimestamp(result[4]))}
125 | ```yaml
126 | {result[3]}
127 | ```
128 | """,
129 | colour=get_colour(),
130 | ).set_thumbnail(url=user.display_avatar.url or ctx.author.default_avatar)
131 | await ctx.send(embed=embed)
132 |
133 | @commands.command(aliases=["esnipe"])
134 | async def editsnipe(self, ctx: Context, amount: int = 0):
135 | results = (
136 | await self.bot.db.fetchall(
137 | "SELECT * FROM edit_snipe WHERE channel_id = ?", (ctx.channel.id,)
138 | )
139 | )[::-1]
140 | if not results:
141 | m = await send_embed(
142 | ctx,
143 | "Edit Snipe",
144 | f"> No edited messages found in {ctx.channel.mention}.",
145 | )
146 | return await m.add_reaction(CLOSE)
147 | if amount >= len(results):
148 | m = await send_embed(
149 | ctx, "Edit Snipe", f"> I found no messages {amount} edits ago."
150 | )
151 | return await m.add_reaction(CLOSE)
152 |
153 | result = results[amount]
154 | user = await self.getch_user(result[2])
155 |
156 | embed = disnake.Embed(
157 | description=f"""
158 | Message in: {ctx.channel.mention}
159 | Message from: {user.mention}
160 | Message edited at: {disnake.utils.format_dt(datetime.fromtimestamp(result[5]))}
161 | ```yaml
162 | {result[3]}
163 | ```
164 | changed to:
165 | ```yaml
166 | {result[4]}
167 | ```
168 | """,
169 | colour=get_colour(),
170 | ).set_thumbnail(url=user.display_avatar.url or ctx.author.default_avatar)
171 | await ctx.send(embed=embed)
172 |
173 | @commands.command(aliases=["eaim"])
174 | async def editaim(self, ctx: Context, user: disnake.Member = None):
175 | if user is None:
176 | user = ctx.author
177 |
178 | results = (
179 | await self.bot.db.fetchall(
180 | "SELECT * FROM edit_snipe WHERE channel_id = ? and user_id = ?",
181 | (ctx.channel.id, user.id),
182 | )
183 | )[::-1]
184 | if not results:
185 | m = await send_embed(
186 | ctx,
187 | "Snipe",
188 | f"> No deleted messages from {user.mention} found in {ctx.channel.mention}.",
189 | )
190 | return await m.add_reaction(CLOSE)
191 |
192 | result = results[0]
193 | user = await self.getch_user(result[2])
194 |
195 | embed = disnake.Embed(
196 | description=f"""
197 | Message in: {ctx.channel.mention}
198 | Message from: {user.mention}
199 | Message edited at: {disnake.utils.format_dt(datetime.fromtimestamp(result[5]))}
200 | ```yaml
201 | {result[3]}
202 | ```
203 | changed to:
204 | ```yaml
205 | {result[4]}
206 | ```
207 | """,
208 | colour=get_colour(),
209 | ).set_thumbnail(url=user.display_avatar.url or ctx.author.default_avatar)
210 | await ctx.send(embed=embed)
211 |
212 |
213 | def setup(bot: JesterBot) -> None:
214 | bot.add_cog(Snipe(bot))
215 |
--------------------------------------------------------------------------------
/cogs/trivia.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import datetime
3 | import random
4 | import re
5 |
6 | import disnake
7 | from disnake.ext import commands
8 |
9 | from core import Context, JesterBot
10 |
11 |
12 | class Trivia(commands.Cog):
13 | def __init__(self, bot: JesterBot):
14 | self.bot = bot
15 | self.token = "..."
16 |
17 | async def replace_token(self) -> None:
18 | async with self.bot.client.get(
19 | url="https://opentdb.com/api_token.php?command=request"
20 | ) as resp:
21 | json = await resp.json()
22 | self.token = json["token"]
23 |
24 | async def get_question(self):
25 | async with self.bot.client.get(
26 | url=f"https://opentdb.com/api.php?amount=1&token={self.token}"
27 | ) as resp:
28 | json = await resp.json()
29 | if json["response_code"] == 3:
30 | await self.replace_token()
31 | return await self.get_question()
32 | output = json["results"][0]
33 |
34 | choices = [re.sub("&.*?;", "", k) for k in output["incorrect_answers"]]
35 | choices.append(re.sub("&.*?;", "", output["correct_answer"]))
36 |
37 | random.shuffle(choices)
38 | choices = [f"{chr(i)}) {e}" for i, e in enumerate(choices, start=97)]
39 | for i, v in enumerate(choices):
40 | if v.endswith(re.sub("&.*?;", "", output["correct_answer"])):
41 | answer = v[0]
42 |
43 | "".join(" " for i in range(0, 36 - len(", ".join(choices))))
44 | return (
45 | """```yaml
46 | ++ -- {re.sub('&.*?;', '', output['question'])} -- ++
47 |
48 | Your choices are:
49 | {spaces if spaces else ''}{', '.join(choices)}
50 |
51 | [category]: {output['category']}
52 | [difficulty]: {output['difficulty']}
53 |
54 | [send the letter corresponding to the answer]```""",
55 | answer,
56 | )
57 |
58 | @commands.command(aliases=["question"])
59 | async def trivia_question(self, ctx: Context) -> None:
60 | (content, answer) = await self.get_question()
61 | await ctx.em(content)
62 |
63 | msg = await self.bot.wait_for(
64 | "message",
65 | check=lambda m: m.channel == ctx.channel and m.author == ctx.author,
66 | )
67 |
68 | if msg.content.lower() == answer:
69 | return await ctx.em("Thats the correct answer!")
70 | await ctx.em(f"Incorrect answer - the right answer was `{answer}`")
71 |
72 | @commands.command(aliases=["start", "trivia_start"])
73 | async def trivia_play(self, ctx: Context) -> None:
74 | await ctx.em(
75 | """```yaml
76 | ++ -- Starting the trivia game! -- ++
77 |
78 | [Stop the game:] Type 'end' to end the game.
79 | [How to play:] The bot sends the question, waits 30 seconds for responses. When the bot sends the message you respond with the letter corresponding with the answer.
80 | [Change your answer:]
81 | - If you want to change your answer you have to type; 'change ' and replace with the new letter you want.
82 | - Eg you send 'a' as the answer, but you want to change it to be 'b' so you send 'change b'``` """
83 | )
84 |
85 | input_dict = {}
86 | run = False
87 | num = 0
88 |
89 | while True:
90 | (content, answer) = await self.get_question()
91 | bot_msg = await ctx.em(content)
92 |
93 | try:
94 | time = datetime.datetime.utcnow()
95 | msg = await self.bot.wait_for(
96 | "message",
97 | check=lambda m: m.channel == ctx.channel and not m.author.bot,
98 | timeout=30,
99 | )
100 |
101 | while True:
102 | if msg.author == ctx.author and msg.content.lower() == "end":
103 | return await ctx.em("Game ending...")
104 |
105 | if msg.author.name in input_dict:
106 | if not input_dict[msg.author.name]["answer"]:
107 | input_dict[msg.author.name]["answer"] = msg.content.lower()
108 | if msg.content.lower().startswith("change"):
109 | input_dict[msg.author.name]["answer"] = msg.content[
110 | 7
111 | ].lower()
112 |
113 | else:
114 | input_dict[msg.author.name] = {
115 | "score": 0,
116 | "answer": msg.content.lower(),
117 | }
118 |
119 | run = True
120 | msg = await self.bot.wait_for(
121 | "message",
122 | check=lambda m: m.channel == ctx.channel and not m.author.bot,
123 | timeout=30
124 | + (time - datetime.datetime.utcnow()).total_seconds(),
125 | )
126 |
127 | except asyncio.TimeoutError:
128 | if not run:
129 | num += 1
130 | await bot_msg.delete()
131 |
132 | if num == 2:
133 | break
134 |
135 | await ctx.em(
136 | "```yaml\nNo one responded in time! If no one responds on the next round the game ends```",
137 | delete_after=60,
138 | )
139 |
140 | else:
141 | correct = [
142 | k for k in input_dict if input_dict[k]["answer"] == answer
143 | ]
144 | sorted_dict = dict(
145 | sorted(
146 | input_dict.items(),
147 | key=lambda k: k[1]["score"],
148 | reverse=True,
149 | )
150 | )
151 | for k in correct:
152 | input_dict[k]["score"] += 1
153 | "\n".join(
154 | [
155 | f" - {k}: {[input_dict[k]['score']]}"
156 | for k in sorted_dict
157 | ]
158 | )
159 |
160 | await ctx.em(
161 | """```yaml
162 | ++ -- The answer was {answer.upper()} -- ++
163 |
164 | {"[Correct:] " + ', '.join(correct) if correct else "No one got it right!"}
165 | [Leaderboard:]
166 | {leaderboard if leaderboard else "No one answered"}```""",
167 | delete_after=30,
168 | )
169 |
170 | for k in input_dict:
171 | input_dict[k]["answer"] = ""
172 | run = False
173 | num = 0
174 |
175 | await ctx.em("```yaml\nThanks for playing!```")
176 |
177 |
178 | def setup(bot: JesterBot) -> None:
179 | bot.add_cog(Trivia(bot))
180 |
--------------------------------------------------------------------------------
/cogs/urbandictionary.py:
--------------------------------------------------------------------------------
1 | from disnake.ext import commands
2 |
3 | from core import Context, JesterBot
4 |
5 |
6 | class UrbanDictionary(commands.Cog):
7 | RANDOM_URL = "http://api.urbandictionary.com/v0/random"
8 | SPECIFIC_URL = "http://api.urbandictionary.com/v0/define?term={}"
9 |
10 | def __init__(self, bot) -> None:
11 | self.bot = bot
12 |
13 | def parse_dict(self, dictionary: dict) -> str:
14 | return """```yaml\n{content}```"""
15 |
16 | @commands.command(aliases=["search", "usearch"])
17 | async def urban_search(self, ctx: Context, *, query: str) -> None:
18 | async with self.bot.client.get(url=self.SPECIFIC_URL + query) as resp:
19 | json = await resp.json()
20 | try:
21 | output = self.parse_dict(json["list"][0])
22 | except Exception:
23 | output = "That doesnt exist in the urban dictionary"
24 |
25 | await ctx.reply(output)
26 |
27 | @commands.command(aliases=["randomsearch", "rsearch"])
28 | async def random_urban_search(self, ctx: Context) -> None:
29 | async with self.bot.client.get(url=self.RANDOM_URL) as resp:
30 | json = await resp.json()
31 | output = self.parse_dict(json["list"][0])
32 |
33 | await ctx.reply(output)
34 |
35 |
36 | def setup(bot: JesterBot) -> None:
37 | bot.add_cog(UrbanDictionary(bot))
38 |
--------------------------------------------------------------------------------
/cogs/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import setup
2 |
--------------------------------------------------------------------------------
/core/__init__.py:
--------------------------------------------------------------------------------
1 | from .bot import JesterBot
2 | from .context import Context
3 | from .errors import error_handler
4 |
--------------------------------------------------------------------------------
/core/context.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import asyncio
4 | import inspect
5 | from typing import TYPE_CHECKING, Any, Dict, List, Optional
6 |
7 | import disnake
8 | import disnake.abc
9 | import disnake.utils
10 | from disnake.ext.commands import Command, Context, view
11 | from disnake.message import Message
12 |
13 | from core.constants import TRASHCAN
14 | from core.utils import get_colour
15 |
16 | if TYPE_CHECKING:
17 | from disnake.state import ConnectionState
18 |
19 | from core.bot import JesterBot
20 |
21 | MISSING: Any = disnake.utils.MISSING
22 |
23 |
24 | class Context(Context):
25 | def __init__(
26 | self,
27 | *,
28 | message: Message,
29 | bot: JesterBot,
30 | view: view.StringView,
31 | args: List[Any] = MISSING,
32 | kwargs: Dict[
33 | str,
34 | Any,
35 | ] = MISSING,
36 | prefix: Optional[str] = None,
37 | command: Optional[Command] = None,
38 | invoked_with: Optional[str] = None,
39 | invoked_parents: List[str] = MISSING,
40 | invoked_subcommand: Optional[Command] = None,
41 | subcommand_passed: Optional[str] = None,
42 | command_failed: bool = False,
43 | current_parameter: Optional[inspect.Parameter] = None,
44 | ):
45 | self.message: Message = message
46 | self.bot: JesterBot = bot
47 | self.args: List[Any] = args or []
48 | self.kwargs: Dict[
49 | str,
50 | Any,
51 | ] = (
52 | kwargs or {}
53 | )
54 | self.prefix: Optional[str] = prefix
55 | self.command: Optional[Command] = command
56 | self.view: view.StringView = view
57 | self.invoked_with: Optional[str] = invoked_with
58 | self.invoked_parents: List[str] = invoked_parents or []
59 | self.invoked_subcommand: Optional[Command] = invoked_subcommand
60 | self.subcommand_passed: Optional[str] = subcommand_passed
61 | self.command_failed: bool = command_failed
62 | self.current_parameter: Optional[inspect.Parameter] = current_parameter
63 | self._state: ConnectionState = self.message._state
64 |
65 | async def em(self, message, **kwargs):
66 | return await super().send(
67 | embed=disnake.Embed(description=message, colour=get_colour()), **kwargs
68 | )
69 |
70 | async def send(self, content: any = None, **kwargs):
71 | perms = self.channel.permissions_for(self.me)
72 | if not perms.send_messages:
73 | try:
74 | await self.author.send(
75 | "I can't send any messages in that channel. \nPlease give me sufficient permissions to do so."
76 | )
77 | except disnake.Forbidden:
78 | pass
79 | return
80 |
81 | require_embed_perms = kwargs.pop("embed_perms", False)
82 | if require_embed_perms and not perms.embed_links:
83 | kwargs = {}
84 | content = (
85 | "Oops! I need **Embed Links** permission to work properly. \n"
86 | "Please tell a server admin to grant me that permission."
87 | )
88 | if isinstance(content, disnake.Embed):
89 | kwargs["embed"] = content
90 | content = None
91 | if isinstance(content, disnake.File):
92 | kwargs["file"] = content
93 | content = None
94 |
95 | msg = await super().send(content, **kwargs)
96 | self.bot.data[self.message] = {"bot": msg}
97 | try:
98 |
99 | async def reaction_task(msg, arg, kwargs):
100 | def check(r, u):
101 | return r.message == msg and u == arg.author
102 |
103 | await asyncio.sleep(3)
104 | try:
105 | await msg.add_reaction(TRASHCAN)
106 | except Exception:
107 | return
108 |
109 | try:
110 | (r, u) = await arg.bot.wait_for(
111 | "reaction_add", check=check, timeout=250
112 | )
113 | if str(r.emoji.id) == TRASHCAN[11:-1]:
114 | await msg.delete()
115 | except Exception:
116 | try:
117 | await msg.clear_reactions()
118 | except Exception:
119 | pass
120 |
121 | loop = asyncio.get_running_loop()
122 | loop.create_task(reaction_task(msg, self, kwargs))
123 | except Exception:
124 | pass
125 | return msg
126 |
127 | async def reply(self, content: any = None, **kwargs):
128 | perms = self.channel.permissions_for(self.me)
129 | if not perms.send_messages:
130 | try:
131 | await self.author.send(
132 | "I can't send any messages in that channel. \nPlease give me sufficient permissions to do so."
133 | )
134 | except disnake.Forbidden:
135 | pass
136 | return
137 |
138 | require_embed_perms = kwargs.pop("embed_perms", False)
139 | if require_embed_perms and not perms.embed_links:
140 | kwargs = {}
141 | content = (
142 | "Oops! I need **Embed Links** permission to work properly. \n"
143 | "Please tell a server admin to grant me that permission."
144 | )
145 | if isinstance(content, disnake.Embed):
146 | kwargs["embed"] = content
147 | content = None
148 | if isinstance(content, disnake.File):
149 | kwargs["file"] = content
150 | content = None
151 |
152 | msg = await super().reply(content, **kwargs)
153 | self.bot.data[self.message] = {"bot": msg}
154 | try:
155 |
156 | async def reaction_task(msg, arg, kwargs):
157 | def check(r, u):
158 | return r.message == msg and u == arg.author
159 |
160 | await asyncio.sleep(3)
161 | try:
162 | await msg.add_reaction(TRASHCAN)
163 | except Exception:
164 | return
165 |
166 | try:
167 | (r, u) = await arg.bot.wait_for(
168 | "reaction_add", check=check, timeout=250
169 | )
170 | if str(r.emoji.id) == TRASHCAN[11:-1]:
171 | await msg.delete()
172 | except Exception:
173 | try:
174 | await msg.clear_reactions()
175 | except Exception:
176 | pass
177 |
178 | loop = asyncio.get_running_loop()
179 | loop.create_task(reaction_task(msg, self, kwargs))
180 | except Exception:
181 | pass
182 | return msg
183 |
--------------------------------------------------------------------------------
/core/database.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | import typing as t
4 |
5 | import aiosqlite
6 |
7 |
8 | class Database:
9 | def __init__(self):
10 | self.db: aiosqlite.Connection
11 |
12 | @classmethod
13 | async def create(cls: t.Type[Database]) -> Database:
14 | self = cls()
15 | self.db = await aiosqlite.connect("./db/database.db")
16 |
17 | with open("./schema.sql") as file:
18 | await self.db.executescript(file.read())
19 | return self
20 |
21 | async def execute(self, query: str, *args: t.Any) -> aiosqlite.Cursor:
22 | async with self.db.cursor() as cur:
23 | return await cur.execute(query, *args)
24 |
25 | async def update(self, query: str, *args: t.Any) -> aiosqlite.Cursor:
26 | async with self.db.cursor() as cur:
27 | resp = await cur.execute(query, *args)
28 | await self.db.commit()
29 | return resp
30 |
31 | async def fetchone(self, query: str, *args: t.Any) -> t.Tuple:
32 | async with self.db.cursor() as cur:
33 | return await (await cur.execute(query, *args)).fetchone()
34 |
35 | async def fetchall(self, query: str, *args: t.Any) -> t.Tuple:
36 | async with self.db.cursor() as cur:
37 | return await (await cur.execute(query, *args)).fetchall()
38 |
--------------------------------------------------------------------------------
/core/paginator.py:
--------------------------------------------------------------------------------
1 | import disnake
2 |
3 | from core.constants import LOCATION_EMOJIS as l
4 | from core.utils import get_colour
5 |
6 |
7 | async def message_edit(embed, **kwargs):
8 | content = kwargs.get("content")
9 | message = kwargs.get("message")
10 | current = kwargs.get("current")
11 | top = kwargs.get("top")
12 | embed.description = content
13 | embed.set_footer(text=f"{str(message.created_at)[11:16]} • Page: {current} / {top}")
14 | await message.edit(embed=embed)
15 |
16 |
17 | class Paginator:
18 | def __init__(self, context) -> None:
19 | self._pages = {}
20 | self._current = 0
21 | self.ctx = context
22 | self._top = 0
23 |
24 | @property
25 | async def pages(self):
26 | return self._pages
27 |
28 | async def paginate(self, **kwargs):
29 | content = kwargs.get("content")
30 | name = kwargs.get("name")
31 | icon_url = kwargs.get("icon_url")
32 | result = []
33 | for i in range(0, len(content), 2000):
34 | result.append(content[i : i + 2000])
35 | self._pages["0"] = {"content": result[0]}
36 | self._top = len(result) - 1
37 |
38 | embed = (
39 | disnake.Embed(colour=get_colour())
40 | .set_author(
41 | name=name,
42 | icon_url=icon_url if icon_url else self.ctx.author.display_avatar.url,
43 | )
44 | .set_footer(
45 | text=f"{str(self.ctx.message.created_at)[11:16]} • Page: {self._current} / {self._top}"
46 | )
47 | )
48 | embed.description = "```yaml\n{}```".format(
49 | self._pages[str(self._current)]["content"]
50 | )
51 | msg = await self.ctx.reply(embed=embed)
52 | if len(result) == 1:
53 | return
54 |
55 | for pagenum, page in enumerate(result[1:], start=1):
56 | self._pages[str(pagenum)] = {"content": page}
57 |
58 | for k in l:
59 | await msg.add_reaction(l[k])
60 |
61 | def check(e, u):
62 | return u == self.ctx.author and e.message == msg
63 |
64 | (e, u) = await self.ctx.bot.wait_for("reaction_add", check=check)
65 |
66 | while str(e.emoji) != l["close"]:
67 | name = str(e.emoji)
68 |
69 | if name == l["fastbackwards"]:
70 | self._current = 0
71 | elif name == l["backwards"]:
72 | if self._current > 0:
73 | self._current -= 1
74 | elif name == l["forwards"]:
75 | if self._current < self._top:
76 | self._current += 1
77 | elif name == l["fastforwards"]:
78 | self._current = self._top
79 |
80 | await msg.remove_reaction(member=self.ctx.author, emoji=e.emoji)
81 | await message_edit(
82 | embed,
83 | message=msg,
84 | content="```yaml\n{}```".format(
85 | self._pages[str(self._current)]["content"]
86 | ),
87 | current=self._current,
88 | top=self._top,
89 | )
90 |
91 | (e, u) = await self.ctx.bot.wait_for("reaction_add", check=check)
92 |
93 | else:
94 | return await msg.clear_reactions()
95 |
96 | async def edit(self, **kwargs):
97 | pass
98 |
--------------------------------------------------------------------------------
/core/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .checks import *
2 | from .get_prefix import create_embed
3 | from .utils import *
4 |
--------------------------------------------------------------------------------
/core/utils/checks.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import disnake
4 | from disnake import Message
5 | from disnake.ext.commands import Bot, Context
6 |
7 | from core.constants import THUMBS_DOWN, THUMBS_UP
8 | from core.utils.utils import get_colour, send_embed, update_json
9 |
10 |
11 | async def suggest(bot: Bot, message: Message) -> None:
12 | data = {}
13 | embed = (
14 | disnake.Embed(colour=get_colour())
15 | .set_author(
16 | name=message.author.name, icon_url=message.author.display_avatar.url
17 | )
18 | .set_footer(
19 | text=str(message.created_at)[11:16]
20 | + " • This suggestion was created by {}".format(message.author.name)
21 | )
22 | )
23 |
24 | def check(m):
25 | return m.author == message.author and isinstance(
26 | m.channel, disnake.channel.DMChannel
27 | )
28 |
29 | for a in [
30 | "What would you like the title to be? Type q at any point to end",
31 | "What would you like the description to be? Type q at any point to end",
32 | ]:
33 | await send_embed(message.author, "", a)
34 |
35 | try:
36 | received_msg = await bot.wait_for("message", check=check, timeout=360)
37 | except TimeoutError:
38 | return await message.delete()
39 |
40 | if received_msg.content.lower() == "q":
41 | await message.delete()
42 | return await received_msg.add_reaction("\u274c")
43 |
44 | await received_msg.add_reaction("\u2705")
45 | data[a] = received_msg.content
46 |
47 | for b in data:
48 | name = b.split(" ")
49 | embed.add_field(name=f"**{name[5]}**", value=data[b], inline=False)
50 |
51 | msg = await message.channel.send(embed=embed)
52 | await msg.add_reaction(THUMBS_UP)
53 | await msg.add_reaction(THUMBS_DOWN)
54 | await message.delete()
55 |
56 |
57 | async def run_check(bot: Bot, ctx: Context) -> bool:
58 | if bot.hiber and ctx.command.name not in ["hiber", "close"]:
59 | return False
60 |
61 | if not ctx.guild:
62 | await ctx.em(
63 | "Commands don't work in DMs! My prefix is `j.`, or you can ping me in a guild!"
64 | )
65 | return False
66 |
67 | if ctx.command.hidden:
68 | if not await bot.is_owner(ctx.author):
69 | await ctx.em(
70 | "You cannot run this command, it is a `hidden` command which only bot admins can run."
71 | )
72 | return False
73 | return True
74 |
75 | with open("./dicts/Check.json") as k:
76 | data = json.load(k)
77 |
78 | if str(ctx.author.id) in data:
79 | if ctx.command.name in data[str(ctx.author.id)]["commands"]:
80 | await ctx.em(
81 | "you cant run this command for some reason, possibly blacklisted"
82 | )
83 | return False
84 |
85 | with open("./dicts/Suggest.json") as k:
86 | data = json.load(k)
87 |
88 | if str(ctx.channel.id) in data:
89 | if data[str(ctx.channel.id)]["Yes"]:
90 | return False
91 | return True
92 |
93 |
94 | async def run_precheck(bot: Bot, message: Message) -> None:
95 | await bot.wait_until_ready()
96 |
97 | with open("./dicts/Suggest.json") as k:
98 | data = json.load(k)
99 |
100 | if str(message.channel.id) in data:
101 | if data[str(message.channel.id)]["Yes"]:
102 | for item in ("suggest", "sug"):
103 | if item in message.content:
104 | return await suggest(bot, message)
105 | if message.author.id != 828363172717133874:
106 | try:
107 | await message.delete()
108 | except Exception:
109 | pass
110 |
111 |
112 | async def run_executed(ctx: Bot) -> None:
113 | bot = ctx.bot
114 |
115 | if ctx.author.id != 298043305927639041:
116 | user = bot.get_user(298043305927639041)
117 | await user.send(
118 | f"```yaml\nName: {ctx.author.name} \nGuild: {ctx.guild} \nCommand: {ctx.command.name} \nChannel: {ctx.channel.name}```"
119 | )
120 |
121 | score = await bot.db.fetchone("SELECT score FROM overall_score")
122 | try:
123 | await bot.db.update("UPDATE overall_score SET score = ?", (int(score[0]) + 1,))
124 | except TypeError:
125 | return
126 |
127 | if ctx.command.name == "Colour":
128 | for cog in tuple(bot.extensions):
129 | bot.reload_extension(cog)
130 |
131 | with open("./dicts/score.json", "r+") as k:
132 | loaded1 = json.load(k)
133 | if str(ctx.author.id) not in loaded1:
134 | loaded1[str(ctx.author.id)] = {
135 | "name": ctx.author.name,
136 | "guild": ctx.guild.name,
137 | "score": 0,
138 | }
139 | update_json(k, loaded1)
140 |
141 | with open("./dicts/score.json", "r+") as f:
142 | data = json.load(f)
143 |
144 | if str(ctx.author.id) in data:
145 | data[str(ctx.author.id)]["score"] += 1
146 | update_json(f, data)
147 |
148 | with open("./dicts/commands_used.json") as y:
149 | data = json.load(y)
150 | if str(ctx.command) not in data:
151 | data[str(ctx.command)] = {"score": 1}
152 | with open("./dicts/commands_used.json", "r+") as y:
153 | update_json(y, data)
154 | else:
155 | with open("./dicts/commands_used.json", "r+") as y:
156 | data[str(ctx.command)]["score"] += 1
157 | update_json(y, data)
158 |
--------------------------------------------------------------------------------
/core/utils/comedy.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import randfacts
4 | from jokeapi import Jokes
5 |
6 |
7 | async def fact() -> str:
8 | return randfacts.get_fact()
9 |
10 |
11 | async def quote(bot) -> str:
12 | async with bot.client.get(
13 | url="http://api.forismatic.com/api/1.0/?method=getQuote&format=json&lang=en"
14 | ) as response:
15 | return '*"{quoteText}"*\n{quoteAuthor}'.format(
16 | **json.loads(await response.read())
17 | )
18 |
19 |
20 | async def joke() -> str:
21 | j = await Jokes()
22 | joke = await j.get_joke()
23 |
24 | if joke["type"] == "single":
25 | return joke["joke"]
26 | return f"**{joke['setup']}** - {joke['delivery']}"
27 |
28 |
29 | async def pickup(bot) -> str:
30 | async with bot.client.get(
31 | url="http://getpickuplines.herokuapp.com/lines/random"
32 | ) as response:
33 | json = await response.json()
34 | return json["line"]
35 |
--------------------------------------------------------------------------------
/core/utils/commands/eval.py:
--------------------------------------------------------------------------------
1 | import contextlib
2 | import io
3 | import json
4 | import textwrap
5 | from traceback import format_exception
6 |
7 | import disnake
8 | from disnake.ext import commands
9 |
10 | from core.context import Context
11 | from core.paginator import Paginator
12 | from core.utils import update_json
13 |
14 |
15 | def clean_code(content: str) -> str:
16 | if content.startswith("```py"):
17 | content = content[5:-3]
18 | content = content.strip("`")
19 | content = (
20 | content.replace("‘", "'").replace("“", '"').replace("”", '"').replace("’", "'")
21 | )
22 | return content
23 |
24 |
25 | async def run_eval(ctx: Context, code, **kwargs) -> None:
26 | _eval = kwargs.get("_eval")
27 |
28 | local_variables = {
29 | "disnake": disnake,
30 | "commands": commands,
31 | "bot": ctx.bot,
32 | "client": ctx.bot,
33 | "ctx": ctx,
34 | "channel": ctx.channel,
35 | "author": ctx.author,
36 | "guild": ctx.guild,
37 | "message": ctx.message,
38 | }
39 |
40 | if code == "reset":
41 | with open("./dicts/Num.json", "r+") as k:
42 | data = json.load(k)
43 | if str(ctx.author.id) in data:
44 | data[str(ctx.author.id)]["Score"] = 0
45 | z = data[str(ctx.author.id)]["Score"]
46 | else:
47 | data[str(ctx.author.id)] = {"Name": ctx.author.name, "Score": 0}
48 | update_json(k, data)
49 | return await ctx.reply("reset")
50 | else:
51 | with open("./dicts/Num.json", "r+") as k:
52 | data = json.load(k)
53 | if str(ctx.author.id) in data:
54 | data[str(ctx.author.id)]["Score"] += 1
55 | z = data[str(ctx.author.id)]["Score"]
56 | else:
57 | data[str(ctx.author.id)] = {"Name": ctx.author.name, "Score": 1}
58 | update_json(k, data)
59 |
60 | code = clean_code(code)
61 | stdout = io.StringIO()
62 |
63 | pref = await ctx.bot.get_prefix(ctx.message)
64 | message = clean_code(ctx.message.content[len(pref) - 1 :])
65 |
66 | if _eval == "dir":
67 | code = f"print(dir({code}))"
68 |
69 | elif _eval == "return":
70 | code = f"return {code}"
71 |
72 | try:
73 | with contextlib.redirect_stdout(stdout):
74 | exec(f"async def func():\n{textwrap.indent(code, ' ')}", local_variables)
75 | obj = await local_variables["func"]()
76 |
77 | result = f"{stdout.getvalue()}{obj}\n"
78 | except Exception as e:
79 | result = "".join(format_exception(e, e, e.__traceback__))
80 | pass
81 |
82 | result = result.replace("`", "")
83 | message = message.replace("`", "")
84 | if result.replace("\n", "").endswith("None") and result != "None":
85 | result = result[:-5]
86 | if len(result) < 2000:
87 | msg = f"```py\nIn[{z}]: {message}\nOut[{z}]: {result}\n```"
88 | else:
89 | y = Paginator(ctx)
90 | return await y.paginate(content=result, name="Eval")
91 | return msg
92 |
--------------------------------------------------------------------------------
/core/utils/font/ABeeZee-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaedenPH/JesterBot/9f194144004f74248ed762dbba1b44ce770bd2f3/core/utils/font/ABeeZee-Regular.otf
--------------------------------------------------------------------------------
/core/utils/get_prefix.py:
--------------------------------------------------------------------------------
1 | from disnake import Embed, Message
2 |
3 | from core.utils.comedy import joke
4 |
5 | from .utils import get_colour
6 |
7 |
8 | async def create_embed(message: Message, bot) -> Embed:
9 | avatar = message.guild.get_member(bot.user.id).display_avatar.url
10 | await bot.get_prefix(message)
11 |
12 | embed = Embed(
13 | title=f"Hello {message.author.name}",
14 | description="""
15 | │ My default prefix is: `j.` │
16 | │ My prefix for you is: {', '.join(f"`{k}`" for k in prefix if not k.startswith('<@'))} │
17 | │ Type `j.prefix [prefix], [prefix], etc` to change the prefix for you! │
18 | """,
19 | colour=get_colour(),
20 | )
21 | embed.set_author(name="JesterBot", icon_url=avatar)
22 | embed.add_field(
23 | name="Also here is a joke for you:", value=f"│ {await joke()} │", inline=False
24 | )
25 | embed.set_footer(text="You can get more of these jokes with j.joke!")
26 | return embed
27 |
--------------------------------------------------------------------------------
/core/utils/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import disnake
4 |
5 |
6 | def update_json(_file, _data: str) -> None:
7 | """Update file to match data"""
8 |
9 | _file.truncate(0)
10 | _file.seek(0)
11 | _file.write(json.dumps(_data, indent=4))
12 |
13 |
14 | def get_colour() -> int:
15 | with open("./dicts/Colour.json") as fp:
16 | data = json.load(fp)
17 |
18 | colour = data["colour"]
19 | return int(colour, 16)
20 |
21 |
22 | async def send_embed(
23 | channel: disnake.abc.Messageable,
24 | title: str,
25 | description: str = None,
26 | **kwargs
27 | ) -> disnake.Message:
28 | from core.context import Context
29 |
30 | author = kwargs.get("a")
31 | icon_url = kwargs.get("i_u")
32 | footer = kwargs.get("f")
33 | thumbnail = kwargs.get("t")
34 | image = kwargs.get("i")
35 |
36 | embed = disnake.Embed(title=title, description=description, colour=get_colour())
37 | if footer:
38 | embed.set_footer(text=footer)
39 | if author:
40 | embed.set_author(name=author)
41 | if icon_url:
42 | embed.set_author(name=author, icon_url=icon_url)
43 | if thumbnail:
44 | embed.set_thumbnail(url=thumbnail)
45 | if image:
46 | embed.set_image(url=image)
47 |
48 | if isinstance(channel, Context):
49 | embed.timestamp = channel.message.created_at
50 | if not author:
51 | embed.set_author(
52 | name=channel.author.name, icon_url=channel.author.display_avatar.url
53 | )
54 | msg = await channel.reply(embed=embed)
55 | else:
56 | msg = await channel.send(embed=embed)
57 | return msg
58 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2020-present CaedenPH
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a
7 | copy of this software and associated documentation files (the "Software"),
8 | to deal in the Software without restriction, including without limitation
9 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | and/or sell copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 | OR IMPLIED, INCLUDING BUT NOT LIMxITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 | DEALINGS IN THE SOFTWARE.
23 | """
24 |
25 | from core import JesterBot
26 |
27 | if __name__ == "__main__":
28 | JesterBot().run()
29 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.ruff]
2 | ignore = [
3 | "F401"
4 | ]
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiosqlite
2 | animals.py
3 | art
4 | async_cse
5 | asyncpraw
6 | discord-together
7 | disnake
8 | disnake-jishaku
9 | disnake_paginator
10 | essential-generators
11 | fuzzywuzzy
12 | glitch-this
13 | jishaku
14 | jokeapi
15 | opencv-python
16 | pyMorseTranslator
17 | python-dotenv
18 | python-Levenshtein
19 | pytz
20 | pyyaml
21 | randfacts
22 | simpleeval
23 | svglib
24 | vacefron.py
25 | wikipedia
26 | youtube-dl
27 | youtube-search-python
28 |
--------------------------------------------------------------------------------
/resources/celebrities.json:
--------------------------------------------------------------------------------
1 | [
2 | "Adele",
3 | "Aerosmith",
4 | "Aretha Franklin",
5 | "Ayumi Hamasaki",
6 | "B'z",
7 | "Barbra Streisand",
8 | "Barry Manilow",
9 | "Barry White",
10 | "Beyonce",
11 | "Billy Joel",
12 | "Bob Dylan",
13 | "Bob Marley",
14 | "Bob Seger",
15 | "Bon Jovi",
16 | "Britney Spears",
17 | "Bruce Springsteen",
18 | "Bruno Mars",
19 | "Bryan Adams",
20 | "Céline Dion",
21 | "Cher",
22 | "Christina Aguilera",
23 | "Darude",
24 | "David Bowie",
25 | "Donna Summer",
26 | "Drake",
27 | "Ed Sheeran",
28 | "Elton John",
29 | "Elvis Presley",
30 | "Eminem",
31 | "Enya",
32 | "Flo Rida",
33 | "Frank Sinatra",
34 | "Garth Brooks",
35 | "George Harrison",
36 | "George Michael",
37 | "George Strait",
38 | "Guido Van Rossum",
39 | "James Taylor",
40 | "Janet Jackson",
41 | "Jay-Z",
42 | "John Lennon",
43 | "Johnny Cash",
44 | "Johnny Hallyday",
45 | "Julio Iglesias",
46 | "Justin Bieber",
47 | "Justin Timberlake",
48 | "Kanye West",
49 | "Katy Perry",
50 | "Kenny G",
51 | "Kenny Rogers",
52 | "Lady Gaga",
53 | "Lil Wayne",
54 | "Linda Ronstadt",
55 | "Lionel Richie",
56 | "Madonna",
57 | "Mariah Carey",
58 | "Meat Loaf",
59 | "Michael Jackson",
60 | "Neil Diamond",
61 | "Nicki Minaj",
62 | "Olivia Newton-John",
63 | "Paul McCartney",
64 | "Phil Collins",
65 | "Pink",
66 | "Prince",
67 | "Reba McEntire",
68 | "Rick Astley",
69 | "Rihanna",
70 | "Ringo Starr",
71 | "Robbie Williams",
72 | "Rod Stewart",
73 | "Santana",
74 | "Shania Twain",
75 | "Stevie Wonder",
76 | "Taylor Swift",
77 | "The Weeknd",
78 | "Tim McGraw",
79 | "Tina Turner",
80 | "Tom Petty",
81 | "Tupac Shakur",
82 | "Usher",
83 | "Van Halen",
84 | "Whitney Houston"
85 | ]
86 |
--------------------------------------------------------------------------------
/resources/foods.json:
--------------------------------------------------------------------------------
1 | [
2 | "apple",
3 | "avocado",
4 | "bagel",
5 | "banana",
6 | "bread",
7 | "broccoli",
8 | "burrito",
9 | "cake",
10 | "candy",
11 | "carrot",
12 | "cheese",
13 | "cherries",
14 | "chestnut",
15 | "chili",
16 | "chocolate",
17 | "coconut",
18 | "coffee",
19 | "cookie",
20 | "corn",
21 | "croissant",
22 | "cupcake",
23 | "donut",
24 | "dumpling",
25 | "falafel",
26 | "grapes",
27 | "honey",
28 | "kiwi",
29 | "lemon",
30 | "lollipop",
31 | "mango",
32 | "mushroom",
33 | "orange",
34 | "pancakes",
35 | "peanut",
36 | "pear",
37 | "pie",
38 | "pineapple",
39 | "popcorn",
40 | "potato",
41 | "pretzel",
42 | "ramen",
43 | "rice",
44 | "salad",
45 | "spaghetti",
46 | "stew",
47 | "strawberry",
48 | "sushi",
49 | "taco",
50 | "tomato",
51 | "watermelon"
52 | ]
53 |
--------------------------------------------------------------------------------
/resources/save_the_planet.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "Choose renewable energy",
4 | "image": {"url": "https://cdn.dnaindia.com/sites/default/files/styles/full/public/2019/07/23/851602-renewable-energy-istock-072419.jpg"},
5 | "footer": {"text": "Help out by sharing this information!"},
6 | "fields": [
7 | {
8 | "name": "The problem",
9 | "value": "Getting energy from oil or fossil fuels isn't a good idea, because there is only so much of it.",
10 | "inline": false
11 | },
12 |
13 | {
14 | "name": "What you can do",
15 | "value": "Use renewable energy, such as wind, solar, and hydro, because it is healthier and is not a finite resource!",
16 | "inline": false
17 | }
18 | ]
19 | },
20 |
21 | {
22 | "title": "Save the trees!",
23 | "image": {"url": "https://www.thecollegesolution.com/wp-content/uploads/2014/07/crumpled-paper-1.jpg"},
24 | "footer": {"text": "Help out by sharing this information!"},
25 | "fields": [
26 | {
27 | "name": "The problem",
28 | "value": "We often waste trees on making paper, and just getting rid of them for no good reason.",
29 | "inline": false
30 | },
31 |
32 | {
33 | "name": "What you can do",
34 | "value": "Make sure you only use paper when absolutely necessary. When you do, make sure to use recycled paper because making new paper causes pollution. Find ways to plant trees (Hacktober Fest!) to combat losing them.",
35 | "inline": false
36 | }
37 | ]
38 | },
39 |
40 | {
41 | "title": "Less time in the car!",
42 | "image": {"url": "https://www.careeraddict.com/uploads/article/55294/businessman-riding-bike.jpg"},
43 | "footer": {"text": "Help out by sharing this information!"},
44 | "fields": [
45 | {
46 | "name": "The problem",
47 | "value": "Every mile you drive to work produces about a pound of C0₂. That's crazy! What's crazier is how clean the planet could be if we spent less time in the car!",
48 | "inline": false
49 | },
50 |
51 | {
52 | "name": "What you can do",
53 | "value": "Instead of using your car, ride your bike if possible! Not only does it save that pound of C0₂, it is also great exercise and is cheaper!",
54 | "inline": false
55 | }
56 | ]
57 | },
58 |
59 | {
60 | "title":"Paint your roof white!",
61 | "image": {"url": "https://modernize.com/wp-content/uploads/2016/10/Cool-roof.jpg"},
62 | "footer": {"text":"Help out by sharing this information!"},
63 | "fields": [
64 | {
65 | "name": "The problem",
66 | "value": "People with dark roofs often spend 20 to 40% more on their electricity bills because of the extra heat, which means more electricity needs to be made, and a lot of it isn't renewable.",
67 | "inline": false
68 | },
69 |
70 | {
71 | "name":"What you can do",
72 | "value": "Having a light colored roof will save you money, and also researchers at the Lawrence Berkeley National Laboratory estimated that if 80 percent of roofs in tropical and temperate climate areas were painted white, it could offset the greenhouse gas emissions of 300 million automobiles around the world.",
73 | "inline": false
74 | }
75 | ]
76 | }
77 | ]
78 |
--------------------------------------------------------------------------------
/resources/seasonal/april_fools_videos.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "url": "https://youtu.be/OYcv406J_J4",
4 | "channel": "google"
5 | },
6 | {
7 | "url": "https://youtu.be/0_5X6N6DHyk",
8 | "channel": "google"
9 | },
10 | {
11 | "url": "https://youtu.be/UmJ2NBHXTqo",
12 | "channel": "google"
13 | },
14 | {
15 | "url": "https://youtu.be/3MA6_21nka8",
16 | "channel": "google"
17 | },
18 | {
19 | "url": "https://youtu.be/QAwL0O5nXe0",
20 | "channel": "google"
21 | },
22 | {
23 | "url": "https://youtu.be/DPEJB-FCItk",
24 | "channel": "google"
25 | },
26 | {
27 | "url": "https://youtu.be/LSZPNwZex9s",
28 | "channel": "google"
29 | },
30 | {
31 | "url": "https://youtu.be/dFrgNiweQDk",
32 | "channel": "google"
33 | },
34 | {
35 | "url": "https://youtu.be/F0F6SnbqUcE",
36 | "channel": "google"
37 | },
38 | {
39 | "url": "https://youtu.be/VkOuShXpoKc",
40 | "channel": "google"
41 | },
42 | {
43 | "url": "https://youtu.be/HQtGFBbwKEk",
44 | "channel": "google"
45 | },
46 | {
47 | "url": "https://youtu.be/Cp10_PygJ4o",
48 | "channel": "google"
49 | },
50 | {
51 | "url": "https://youtu.be/XTTtkisylQw",
52 | "channel": "google"
53 | },
54 | {
55 | "url": "https://youtu.be/hydLZJXG3Tk",
56 | "channel": "google"
57 | },
58 | {
59 | "url": "https://youtu.be/U2JBFlW--UU",
60 | "channel": "google"
61 | },
62 | {
63 | "url": "https://youtu.be/G3NXNnoGr3Y",
64 | "channel": "google"
65 | },
66 | {
67 | "url": "https://youtu.be/4YMD6xELI_k",
68 | "channel": "google"
69 | },
70 | {
71 | "url": "https://youtu.be/qcgWRpQP6ds",
72 | "channel": "google"
73 | },
74 | {
75 | "url": "https://youtu.be/Zr4JwPb99qU",
76 | "channel": "google"
77 | },
78 | {
79 | "url": "https://youtu.be/VFbYadm_mrw",
80 | "channel": "google"
81 | },
82 | {
83 | "url": "https://youtu.be/_qFFHC0eIUc",
84 | "channel": "google"
85 | },
86 | {
87 | "url": "https://youtu.be/H542nLTTbu0",
88 | "channel": "google"
89 | },
90 | {
91 | "url": "https://youtu.be/Je7Xq9tdCJc",
92 | "channel": "google"
93 | },
94 | {
95 | "url": "https://youtu.be/re0VRK6ouwI",
96 | "channel": "google"
97 | },
98 | {
99 | "url": "https://youtu.be/1KhZKNZO8mQ",
100 | "channel": "google"
101 | },
102 | {
103 | "url": "https://youtu.be/UiLSiqyDf4Y",
104 | "channel": "google"
105 | },
106 | {
107 | "url": "https://youtu.be/rznYifPHxDg",
108 | "channel": "google"
109 | },
110 | {
111 | "url": "https://youtu.be/blB_X38YSxQ",
112 | "channel": "google"
113 | },
114 | {
115 | "url": "https://youtu.be/Bu927_ul_X0",
116 | "channel": "google"
117 | },
118 | {
119 | "url": "https://youtu.be/smM-Wdk2RLQ",
120 | "channel": "nvidia"
121 | },
122 | {
123 | "url": "https://youtu.be/IlCx5gjAmqI",
124 | "channel": "razer"
125 | },
126 | {
127 | "url": "https://youtu.be/j8UJE7DoyJ8",
128 | "channel": "razer"
129 | }
130 | ]
131 |
--------------------------------------------------------------------------------
/resources/tags/args-kwargs.md:
--------------------------------------------------------------------------------
1 | `*args` and `**kwargs`
2 |
3 | These special parameters allow functions to take arbitrary amounts of positional and keyword arguments. The names `args` and `kwargs` are purely convention, and could be named any other valid variable name. The special functionality comes from the single and double asterisks (`*`). If both are used in a function signature, `*args` **must** appear before `**kwargs`.
4 |
5 | **Single asterisk**
6 | `*args` will ingest an arbitrary amount of **positional arguments**, and store it in a tuple. If there are parameters after `*args` in the parameter list with no default value, they will become **required** keyword arguments by default.
7 |
8 | **Double asterisk**
9 | `**kwargs` will ingest an arbitrary amount of **keyword arguments**, and store it in a dictionary. There can be **no** additional parameters **after** `**kwargs` in the parameter list.
10 |
11 | **Use cases**
12 | • **Decorators** (see `!tags decorators`)
13 | • **Inheritance** (overriding methods)
14 | • **Future proofing** (in the case of the first two bullet points, if the parameters change, your code won't break)
15 | • **Flexibility** (writing functions that behave like `dict()` or `print()`)
16 |
17 | *See* `!tags positional-keyword` *for information about positional and keyword arguments*
18 |
--------------------------------------------------------------------------------
/resources/tags/async-await.md:
--------------------------------------------------------------------------------
1 | **Concurrency in Python**
2 |
3 | Python provides the ability to run multiple tasks and coroutines simultaneously with the use of the `asyncio` library, which is included in the Python standard library.
4 |
5 | This works by running these coroutines in an event loop, where the context of the running coroutine switches periodically to allow all other coroutines to run, thus giving the appearance of running at the same time. This is different to using threads or processes in that all code runs in the main process and thread, although it is possible to run coroutines in other threads.
6 |
7 | To call an async function we can either `await` it, or run it in an event loop which we get from `asyncio`.
8 |
9 | To create a coroutine that can be used with asyncio we need to define a function using the `async` keyword:
10 | ```py
11 | async def main():
12 | await something_awaitable()
13 | ```
14 | Which means we can call `await something_awaitable()` directly from within the function. If this were a non-async function, it would raise the exception `SyntaxError: 'await' outside async function`
15 |
16 | To run the top level async function from outside the event loop we need to use [`asyncio.run()`](https://docs.python.org/3/library/asyncio-task.html#asyncio.run), like this:
17 | ```py
18 | import asyncio
19 |
20 | async def main():
21 | await something_awaitable()
22 |
23 | asyncio.run(main())
24 | ```
25 | Note that in the `asyncio.run()`, where we appear to be calling `main()`, this does not execute the code in `main`. Rather, it creates and returns a new `coroutine` object (i.e `main() is not main()`) which is then handled and run by the event loop via `asyncio.run()`.
26 |
27 | To learn more about asyncio and its use, see the [asyncio documentation](https://docs.python.org/3/library/asyncio.html).
28 |
--------------------------------------------------------------------------------
/resources/tags/blocking.md:
--------------------------------------------------------------------------------
1 | **Why do we need asynchronous programming?**
2 | Imagine that you're coding a Discord bot and every time somebody uses a command, you need to get some information from a database. But there's a catch: the database servers are acting up today and take a whole 10 seconds to respond. If you do **not** use asynchronous methods, your whole bot will stop running until it gets a response from the database. How do you fix this? Asynchronous programming.
3 |
4 | **What is asynchronous programming?**
5 | An asynchronous program utilises the `async` and `await` keywords. An asynchronous program pauses what it's doing and does something else whilst it waits for some third-party service to complete whatever it's supposed to do. Any code within an `async` context manager or function marked with the `await` keyword indicates to Python, that whilst this operation is being completed, it can do something else. For example:
6 |
7 | ```py
8 | import discord
9 |
10 | # Bunch of bot code
11 |
12 | async def ping(ctx):
13 | await ctx.reply("Pong!")
14 | ```
15 | **What does the term "blocking" mean?**
16 | A blocking operation is wherever you do something without `await`ing it. This tells Python that this step must be completed before it can do anything else. Common examples of blocking operations, as simple as they may seem, include: outputting text, adding two numbers and appending an item onto a list. Most common Python libraries have an asynchronous version available to use in asynchronous contexts.
17 |
18 | **`async` libraries**
19 | The standard async library - `asyncio`
20 | Asynchronous web requests - `aiohttp`
21 | Talking to PostgreSQL asynchronously - `asyncpg`
22 | MongoDB interactions asynchronously - `motor`
23 | Check out [this](https://github.com/timofurrer/awesome-asyncio) list for even more!
24 |
--------------------------------------------------------------------------------
/resources/tags/botvar.md:
--------------------------------------------------------------------------------
1 | Python allows you to set custom attributes to most objects, like your bot! By storing things as attributes of the bot object, you can access them anywhere you access your bot. In the discord.py library, these custom attributes are commonly known as "bot variables" and can be a lifesaver if your bot is divided into many different files. An example on how to use custom attributes on your bot is shown below:
2 |
3 | ```py
4 | bot = commands.Bot(command_prefix="!")
5 | # Set an attribute on our bot
6 | bot.test = "I am accessible everywhere!"
7 |
8 | @bot.command()
9 | async def get(ctx: commands.Context):
10 | """A command to get the current value of `test`."""
11 | # Send what the test attribute is currently set to
12 | await ctx.reply(ctx.bot.test)
13 |
14 | @bot.command()
15 | async def setval(ctx: commands.Context, *, new_text: str):
16 | """A command to set a new value of `test`."""
17 | # Here we change the attribute to what was specified in new_text
18 | bot.test = new_text
19 | ```
20 |
21 | This all applies to cogs as well! You can set attributes to `self` as you wish.
22 |
23 | *Be sure **not** to overwrite attributes discord.py uses, like `cogs` or `users`. Name your attributes carefully!*
24 |
--------------------------------------------------------------------------------
/resources/tags/class.md:
--------------------------------------------------------------------------------
1 | **Classes**
2 |
3 | Classes are used to create objects that have specific behavior.
4 |
5 | Every object in python has a class, including `list`s, `dict`ionaries and even numbers. Using a class to group code and data like this is the foundation of Object Oriented Programming. Classes allow you to expose a simple, consistent interface while hiding the more complicated details. This simplifies the rest of your program and makes it easier to separately maintain and debug each component.
6 |
7 | Here is an example class:
8 |
9 | ```python
10 | class Foo:
11 | def __init__(self, somedata):
12 | self.my_attrib = somedata
13 |
14 | def show(self):
15 | print(self.my_attrib)
16 | ```
17 |
18 | To use a class, you need to instantiate it. The following creates a new object named `bar`, with `Foo` as its class.
19 |
20 | ```python
21 | bar = Foo('data')
22 | bar.show()
23 | ```
24 |
25 | We can access any of `Foo`'s methods via `bar.my_method()`, and access any of `bar`s data via `bar.my_attribute`.
26 |
--------------------------------------------------------------------------------
/resources/tags/classmethod.md:
--------------------------------------------------------------------------------
1 | Although most methods are tied to an _object instance_, it can sometimes be useful to create a method that does something with _the class itself_. To achieve this in Python, you can use the `@classmethod` decorator. This is often used to provide alternative constructors for a class.
2 |
3 | For example, you may be writing a class that takes some magic token (like an API key) as a constructor argument, but you sometimes read this token from a configuration file. You could make use of a `@classmethod` to create an alternate constructor for when you want to read from the configuration file.
4 | ```py
5 | class Bot:
6 | def __init__(self, token: str):
7 | self._token = token
8 |
9 | @classmethod
10 | def from_config(cls, config: dict) -> Bot:
11 | token = config['token']
12 | return cls(token)
13 |
14 | # now we can create the bot instance like this
15 | alternative_bot = Bot.from_config(default_config)
16 |
17 | # but this still works, too
18 | regular_bot = Bot("tokenstring")
19 | ```
20 | This is just one of the many use cases of `@classmethod`. A more in-depth explanation can be found [here](https://stackoverflow.com/questions/12179271/meaning-of-classmethod-and-staticmethod-for-beginner#12179752).
21 |
--------------------------------------------------------------------------------
/resources/tags/codeblock.md:
--------------------------------------------------------------------------------
1 | Here's how to format Python code on Discord:
2 |
3 | \`\`\`py
4 | print('Hello world!')
5 | \`\`\`
6 |
7 | **These are backticks, not quotes.** Check [this](https://superuser.com/questions/254076/how-do-i-type-the-tick-and-backtick-characters-on-windows/254077#254077) out if you can't find the backtick key.
8 |
--------------------------------------------------------------------------------
/resources/tags/comparison.md:
--------------------------------------------------------------------------------
1 | **Assignment vs. Comparison**
2 |
3 | The assignment operator (`=`) is used to assign variables.
4 | ```python
5 | x = 5
6 | print(x) # Prints 5
7 | ```
8 | The equality operator (`==`) is used to compare values.
9 | ```python
10 | if x == 5:
11 | print("The value of x is 5")
12 | ```
13 |
--------------------------------------------------------------------------------
/resources/tags/contribute.md:
--------------------------------------------------------------------------------
1 | **Contribute to Python Discord's Open Source Projects**
2 | Looking to contribute to Open Source Projects for the first time? Want to add a feature or fix a bug on the bots on this server? We have on-going projects that people can contribute to, even if you've never contributed to open source before!
3 |
4 | **Projects to Contribute to**
5 | • [Sir Lancebot](https://github.com/python-discord/sir-lancebot) - our fun, beginner-friendly bot
6 | • [Python](https://github.com/python-discord/bot) - our utility & moderation bot
7 | • [Site](https://github.com/python-discord/site) - resources, guides, and more
8 |
9 | **Where to start**
10 | 1. Read our [contributing guidelines](https://pythondiscord.com/pages/guides/pydis-guides/contributing/)
11 | 2. Chat with us in <#635950537262759947> if you're ready to jump in or have any questions
12 | 3. Open an issue or ask to be assigned to an issue to work on
13 |
--------------------------------------------------------------------------------
/resources/tags/customchecks.md:
--------------------------------------------------------------------------------
1 | **Custom Command Checks in discord.py**
2 |
3 | Often you may find the need to use checks that don't exist by default in discord.py. Fortunately, discord.py provides `discord.ext.commands.check` which allows you to create you own checks like this:
4 | ```py
5 | from discord.ext.commands import check, Context
6 |
7 | def in_any_channel(*channels):
8 | async def predicate(ctx: Context):
9 | return ctx.channel.id in channels
10 | return check(predicate)
11 | ```
12 | This check is to check whether the invoked command is in a given set of channels. The inner function, named `predicate` here, is used to perform the actual check on the command, and check logic should go in this function. It must be an async function, and always provides a single `commands.Context` argument which you can use to create check logic. This check function should return a boolean value indicating whether the check passed (return `True`) or failed (return `False`).
13 |
14 | The check can now be used like any other commands check as a decorator of a command, such as this:
15 | ```py
16 | @bot.command(name="ping")
17 | @in_any_channel(728343273562701984)
18 | async def ping(ctx: Context):
19 | ...
20 | ```
21 | This would lock the `ping` command to only be used in the channel `728343273562701984`. If this check function fails it will raise a `CheckFailure` exception, which can be handled in your error handler.
22 |
--------------------------------------------------------------------------------
/resources/tags/customcooldown.md:
--------------------------------------------------------------------------------
1 | **Cooldowns in discord.py**
2 |
3 | Cooldowns can be used in discord.py to rate-limit. In this example, we're using it in an on_message.
4 |
5 | ```python
6 | from discord.ext import commands
7 |
8 | message_cooldown = commands.CooldownMapping.from_cooldown(1.0, 60.0, commands.BucketType.user)
9 |
10 | @bot.event
11 | async def on_message(message):
12 | bucket = message_cooldown.get_bucket(message)
13 | retry_after = bucket.update_rate_limit()
14 | if retry_after:
15 | await message.channel.send(f"Slow down! Try again in {retry_after} seconds.")
16 | else:
17 | await message.channel.send("Not ratelimited!")
18 | ```
19 |
20 | `from_cooldown` takes the amount of `update_rate_limit()`s needed to trigger the cooldown, the time in which the cooldown is triggered, and a [`BucketType`](https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.discord.ext.commands.BucketType).
21 |
--------------------------------------------------------------------------------
/resources/tags/customhelp.md:
--------------------------------------------------------------------------------
1 | **Custom help commands in discord.py**
2 |
3 | To learn more about how to create custom help commands in discord.py by subclassing the help command, please see [this tutorial](https://gist.github.com/InterStella0/b78488fb28cadf279dfd3164b9f0cf96#embed-minimalhelpcommand) by Stella#2000
4 |
--------------------------------------------------------------------------------
/resources/tags/decorators.md:
--------------------------------------------------------------------------------
1 | **Decorators**
2 |
3 | A decorator is a function that modifies another function.
4 |
5 | Consider the following example of a timer decorator:
6 | ```py
7 | >>> import time
8 | >>> def timer(f):
9 | ... def inner(*args, **kwargs):
10 | ... start = time.time()
11 | ... result = f(*args, **kwargs)
12 | ... print('Time elapsed:', time.time() - start)
13 | ... return result
14 | ... return inner
15 | ...
16 | >>> @timer
17 | ... def slow(delay=1):
18 | ... time.sleep(delay)
19 | ... return 'Finished!'
20 | ...
21 | >>> print(slow())
22 | Time elapsed: 1.0011568069458008
23 | Finished!
24 | >>> print(slow(3))
25 | Time elapsed: 3.000307321548462
26 | Finished!
27 | ```
28 |
29 | More information:
30 | • [Corey Schafer's video on decorators](https://youtu.be/FsAPt_9Bf3U)
31 | • [Real python article](https://realpython.com/primer-on-python-decorators/)
32 |
--------------------------------------------------------------------------------
/resources/tags/defaultdict.md:
--------------------------------------------------------------------------------
1 | **[`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict)**
2 |
3 | The Python `defaultdict` type behaves almost exactly like a regular Python dictionary, but if you try to access or modify a missing key, the `defaultdict` will automatically insert the key and generate a default value for it.
4 | While instantiating a `defaultdict`, we pass in a function that tells it how to create a default value for missing keys.
5 |
6 | ```py
7 | >>> from collections import defaultdict
8 | >>> my_dict = defaultdict(int)
9 | >>> my_dict
10 | defaultdict(, {})
11 | ```
12 |
13 | In this example, we've used the `int` class which returns 0 when called like a function, so any missing key will get a default value of 0. You can also get an empty list by default with `list` or an empty string with `str`.
14 |
15 | ```py
16 | >>> my_dict["foo"]
17 | 0
18 | >>> my_dict["bar"] += 5
19 | >>> my_dict
20 | defaultdict(, {'foo': 0, 'bar': 5})
21 | ```
22 |
--------------------------------------------------------------------------------
/resources/tags/dict-get.md:
--------------------------------------------------------------------------------
1 | Often while using dictionaries in Python, you may run into `KeyErrors`. This error is raised when you try to access a key that isn't present in your dictionary. Python gives you some neat ways to handle them.
2 |
3 | **The `dict.get` method**
4 |
5 | The [`dict.get`](https://docs.python.org/3/library/stdtypes.html#dict.get) method will return the value for the key if it exists, and None (or a default value that you specify) if the key doesn't exist. Hence it will _never raise_ a KeyError.
6 | ```py
7 | >>> my_dict = {"foo": 1, "bar": 2}
8 | >>> print(my_dict.get("foobar"))
9 | None
10 | ```
11 | Below, 3 is the default value to be returned, because the key doesn't exist-
12 | ```py
13 | >>> print(my_dict.get("foobar", 3))
14 | 3
15 | ```
16 | Some other methods for handling `KeyError`s gracefully are the [`dict.setdefault`](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) method and [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) (check out the `!defaultdict` tag).
17 |
--------------------------------------------------------------------------------
/resources/tags/dictcomps.md:
--------------------------------------------------------------------------------
1 | Dictionary comprehensions (*dict comps*) provide a convenient way to make dictionaries, just like list comps:
2 | ```py
3 | >>> {word.lower(): len(word) for word in ('I', 'love', 'Python')}
4 | {'i': 1, 'love': 4, 'python': 6}
5 | ```
6 | The syntax is very similar to list comps except that you surround it with curly braces and have two expressions: one for the key and one for the value.
7 |
8 | One can use a dict comp to change an existing dictionary using its `items` method
9 | ```py
10 | >>> first_dict = {'i': 1, 'love': 4, 'python': 6}
11 | >>> {key.upper(): value * 2 for key, value in first_dict.items()}
12 | {'I': 2, 'LOVE': 8, 'PYTHON': 12}
13 | ```
14 | For more information and examples, check out [PEP 274](https://www.python.org/dev/peps/pep-0274/)
15 |
--------------------------------------------------------------------------------
/resources/tags/docstring.md:
--------------------------------------------------------------------------------
1 | A [`docstring`](https://docs.python.org/3/glossary.html#term-docstring) is a string - always using triple quotes - that's placed at the top of files, classes and functions. A docstring should contain a clear explanation of what it's describing. You can also include descriptions of the subject's parameter(s) and what it returns, as shown below:
2 | ```py
3 | def greet(name: str, age: int) -> str:
4 | """
5 | Return a string that greets the given person, using their name and age.
6 |
7 | :param name: The name of the person to greet.
8 | :param age: The age of the person to greet.
9 |
10 | :return: The greeting.
11 | """
12 | return f"Hello {name}, you are {age} years old!"
13 | ```
14 | You can get the docstring by using the [`inspect.getdoc`](https://docs.python.org/3/library/inspect.html#inspect.getdoc) function, from the built-in [`inspect`](https://docs.python.org/3/library/inspect.html) module, or by accessing the `.__doc__` attribute. `inspect.getdoc` is often preferred, as it clears indents from the docstring.
15 |
16 | For the last example, you can print it by doing this: `print(inspect.getdoc(greet))`.
17 |
18 | For more details about what a docstring is and its usage, check out this guide by [Real Python](https://realpython.com/documenting-python-code/#docstrings-background), or the [official docstring specification](https://www.python.org/dev/peps/pep-0257/#what-is-a-docstring).
19 |
--------------------------------------------------------------------------------
/resources/tags/dotenv.md:
--------------------------------------------------------------------------------
1 | **Using .env files in Python**
2 |
3 | `.env` (dotenv) files are a type of file commonly used for storing application secrets and variables, for example API tokens and URLs, although they may also be used for storing other configurable values. While they are commonly used for storing secrets, at a high level their purpose is to load environment variables into a program.
4 |
5 | Dotenv files are especially suited for storing secrets as they are a key-value store in a file, which can be easily loaded in most programming languages and ignored by version control systems like Git with a single entry in a `.gitignore` file.
6 |
7 | In python you can use dotenv files with the [`python-dotenv`](https://pypi.org/project/python-dotenv) module from PyPI, which can be installed with `pip install python-dotenv`. To use dotenv files you'll first need a file called `.env`, with content such as the following:
8 | ```
9 | TOKEN=a00418c85bff087b49f23923efe40aa5
10 | ```
11 | Next, in your main Python file, you need to load the environment variables from the dotenv file you just created:
12 | ```py
13 | from dotenv import load_dotenv()
14 |
15 | load_dotenv(".env")
16 | ```
17 | The variables from the file have now been loaded into your programs environment, and you can access them using `os.getenv()` anywhere in your program, like this:
18 | ```py
19 | from os import getenv
20 |
21 | my_token = getenv("TOKEN")
22 | ```
23 | For further reading about tokens and secrets, please read [this explanation](https://vcokltfre.dev/tips/tokens).
24 |
--------------------------------------------------------------------------------
/resources/tags/dunder-methods.md:
--------------------------------------------------------------------------------
1 | **Dunder methods**
2 |
3 | Double-underscore methods, or "dunder" methods, are special methods defined in a class that are invoked implicitly. Like the name suggests, they are prefixed and suffixed with dunders. You've probably already seen some, such as the `__init__` dunder method, also known as the "constructor" of a class, which is implicitly invoked when you instantiate an instance of a class.
4 |
5 | When you create a new class, there will be default dunder methods inherited from the `object` class. However, we can override them by redefining these methods within the new class. For example, the default `__init__` method from `object` doesn't take any arguments, so we almost always override that to fit our needs.
6 |
7 | Other common dunder methods to override are `__str__` and `__repr__`. `__repr__` is the developer-friendly string representation of an object - usually the syntax to recreate it - and is implicitly called on arguments passed into the `repr` function. `__str__` is the user-friendly string representation of an object, and is called by the `str` function. Note here that, if not overriden, the default `__str__` invokes `__repr__` as a fallback.
8 |
9 | ```py
10 | class Foo:
11 | def __init__(self, value): # constructor
12 | self.value = value
13 | def __str__(self):
14 | return f"This is a Foo object, with a value of {self.value}!" # string representation
15 | def __repr__(self):
16 | return f"Foo({self.value!r})" # way to recreate this object
17 |
18 |
19 | bar = Foo(5)
20 |
21 | # print also implicitly calls __str__
22 | print(bar) # Output: This is a Foo object, with a value of 5!
23 |
24 | # dev-friendly representation
25 | print(repr(bar)) # Output: Foo(5)
26 | ```
27 |
28 | Another example: did you know that when you use the ` + ` syntax, you're implicitly calling `.__add__()`? The same applies to other operators, and you can look at the [`operator` built-in module documentation](https://docs.python.org/3/library/operator.html) for more information!
29 |
--------------------------------------------------------------------------------
/resources/tags/empty-json.md:
--------------------------------------------------------------------------------
1 | When using JSON, you might run into the following error:
2 | ```
3 | JSONDecodeError: Expecting value: line 1 column 1 (char 0)
4 | ```
5 | This error could have appeared because you just created the JSON file and there is nothing in it at the moment.
6 |
7 | Whilst having empty data is no problem, the file itself may never be completely empty.
8 |
9 | You most likely wanted to structure your JSON as a dictionary. To do this, edit your empty JSON file so that it instead contains `{}`.
10 |
11 | Different data types are also supported. If you wish to read more on these, please refer to [this article](https://www.tutorialspoint.com/json/json_data_types.htm).
12 |
--------------------------------------------------------------------------------
/resources/tags/enumerate.md:
--------------------------------------------------------------------------------
1 | Ever find yourself in need of the current iteration number of your `for` loop? You should use **enumerate**! Using `enumerate`, you can turn code that looks like this:
2 | ```py
3 | index = 0
4 | for item in my_list:
5 | print(f"{index}: {item}")
6 | index += 1
7 | ```
8 | into beautiful, _pythonic_ code:
9 | ```py
10 | for index, item in enumerate(my_list):
11 | print(f"{index}: {item}")
12 | ```
13 | For more information, check out [the official docs](https://docs.python.org/3/library/functions.html#enumerate), or [PEP 279](https://www.python.org/dev/peps/pep-0279/).
14 |
--------------------------------------------------------------------------------
/resources/tags/environments.md:
--------------------------------------------------------------------------------
1 | **Python Environments**
2 |
3 | The main purpose of Python [virtual environments](https://docs.Python.org/3/library/venv.html#venv-def) is to create an isolated environment for Python projects. This means that each project can have its own dependencies, such as third party packages installed using pip, regardless of what dependencies every other project has.
4 |
5 | To see the current environment in use by Python, you can run:
6 | ```py
7 | >>> import sys
8 | >>> print(sys.executable)
9 | /usr/bin/python3
10 | ```
11 |
12 | To see the environment in use by pip, you can do `pip debug` (`pip3 debug` for Linux/macOS). The 3rd line of the output will contain the path in use e.g. `sys.executable: /usr/bin/python3`.
13 |
14 | If Python's `sys.executable` doesn't match pip's, then they are currently using different environments! This may cause Python to raise a `ModuleNotFoundError` when you try to use a package you just installed with pip, as it was installed to a different environment.
15 |
16 | **Why use a virtual environment?**
17 |
18 | • Resolve dependency issues by allowing the use of different versions of a package for different projects. For example, you could use Package A v2.7 for Project X and Package A v1.3 for Project Y.
19 | • Make your project self-contained and reproducible by capturing all package dependencies in a requirements file. Try running `pip freeze` to see what you currently have installed!
20 | • Keep your global `site-packages/` directory tidy by removing the need to install packages system-wide which you might only need for one project.
21 |
22 |
23 | **Further reading:**
24 |
25 | • [Python Virtual Environments: A Primer](https://realpython.com/python-virtual-environments-a-primer)
26 | • [pyenv: Simple Python Version Management](https://github.com/pyenv/pyenv)
27 |
--------------------------------------------------------------------------------
/resources/tags/except.md:
--------------------------------------------------------------------------------
1 | A key part of the Python philosophy is to ask for forgiveness, not permission. This means that it's okay to write code that may produce an error, as long as you specify how that error should be handled. Code written this way is readable and resilient.
2 | ```py
3 | try:
4 | number = int(user_input)
5 | except ValueError:
6 | print("failed to convert user_input to a number. setting number to 0.")
7 | number = 0
8 | ```
9 | You should always specify the exception type if it is possible to do so, and your `try` block should be as short as possible. Attempting to handle broad categories of unexpected exceptions can silently hide serious problems.
10 | ```py
11 | try:
12 | number = int(user_input)
13 | item = some_list[number]
14 | except Exception:
15 | print("An exception was raised, but we have no idea if it was a ValueError or an IndexError.")
16 | ```
17 | For more information about exception handling, see [the official Python docs](https://docs.python.org/3/tutorial/errors.html), or watch [Corey Schafer's video on exception handling](https://www.youtube.com/watch?v=NIWwJbo-9_8).
18 |
--------------------------------------------------------------------------------
/resources/tags/exit().md:
--------------------------------------------------------------------------------
1 | **Exiting Programmatically**
2 |
3 | If you want to exit your code programmatically, you might think to use the functions `exit()` or `quit()`, however this is bad practice. These functions are constants added by the [`site`](https://docs.python.org/3/library/site.html#module-site) module as a convenient method for exiting the interactive interpreter shell, and should not be used in programs.
4 |
5 | You should use either [`SystemExit`](https://docs.python.org/3/library/exceptions.html#SystemExit) or [`sys.exit()`](https://docs.python.org/3/library/sys.html#sys.exit) instead.
6 | There's not much practical difference between these two other than having to `import sys` for the latter. Both take an optional argument to provide an exit status.
7 |
8 | [Official documentation](https://docs.python.org/3/library/constants.html#exit) with the warning not to use `exit()` or `quit()` in source code.
9 |
--------------------------------------------------------------------------------
/resources/tags/f-strings.md:
--------------------------------------------------------------------------------
1 | Creating a Python string with your variables using the `+` operator can be difficult to write and read. F-strings (*format-strings*) make it easy to insert values into a string. If you put an `f` in front of the first quote, you can then put Python expressions between curly braces in the string.
2 |
3 | ```py
4 | >>> snake = "pythons"
5 | >>> number = 21
6 | >>> f"There are {number * 2} {snake} on the plane."
7 | "There are 42 pythons on the plane."
8 | ```
9 | Note that even when you include an expression that isn't a string, like `number * 2`, Python will convert it to a string for you.
10 |
--------------------------------------------------------------------------------
/resources/tags/faq.md:
--------------------------------------------------------------------------------
1 | ---
2 | embed:
3 | title: "Frequently asked questions"
4 | ---
5 |
6 | As the largest Python community on Discord, we get hundreds of questions every day. Many of these questions have been asked before. We've compiled a list of the most frequently asked questions along with their answers, which can be found on our [FAQ page](https://www.pythondiscord.com/pages/frequently-asked-questions/).
7 |
--------------------------------------------------------------------------------
/resources/tags/floats.md:
--------------------------------------------------------------------------------
1 | **Floating Point Arithmetic**
2 | You may have noticed that when doing arithmetic with floats in Python you sometimes get strange results, like this:
3 | ```python
4 | >>> 0.1 + 0.2
5 | 0.30000000000000004
6 | ```
7 | **Why this happens**
8 | Internally your computer stores floats as binary fractions. Many decimal values cannot be stored as exact binary fractions, which means an approximation has to be used.
9 |
10 | **How you can avoid this**
11 | You can use [math.isclose](https://docs.python.org/3/library/math.html#math.isclose) to check if two floats are close, or to get an exact decimal representation, you can use the [decimal](https://docs.python.org/3/library/decimal.html) or [fractions](https://docs.python.org/3/library/fractions.html) module. Here are some examples:
12 | ```python
13 | >>> math.isclose(0.1 + 0.2, 0.3)
14 | True
15 | >>> decimal.Decimal('0.1') + decimal.Decimal('0.2')
16 | Decimal('0.3')
17 | ```
18 | Note that with `decimal.Decimal` we enter the number we want as a string so we don't pass on the imprecision from the float.
19 |
20 | For more details on why this happens check out this [page in the python docs](https://docs.python.org/3/tutorial/floatingpoint.html) or this [Computerphile video](https://www.youtube.com/watch/PZRI1IfStY0).
21 |
--------------------------------------------------------------------------------
/resources/tags/foo.md:
--------------------------------------------------------------------------------
1 | **Metasyntactic variables**
2 |
3 | A specific word or set of words identified as a placeholder used in programming. They are used to name entities such as variables, functions, etc, whose exact identity is unimportant and serve only to demonstrate a concept, which is useful for teaching programming.
4 |
5 | Common examples include `foobar`, `foo`, `bar`, `baz`, and `qux`.
6 | Python has its own metasyntactic variables, namely `spam`, `eggs`, and `bacon`. This is a reference to a [Monty Python](https://en.wikipedia.org/wiki/Monty_Python) sketch (the eponym of the language).
7 |
8 | More information:
9 | • [History of foobar](https://en.wikipedia.org/wiki/Foobar)
10 | • [Monty Python sketch](https://en.wikipedia.org/wiki/Spam_%28Monty_Python%29)
11 |
--------------------------------------------------------------------------------
/resources/tags/for-else.md:
--------------------------------------------------------------------------------
1 | **for-else**
2 |
3 | In Python it's possible to attach an `else` clause to a for loop. The code under the `else` block will be run when the iterable is exhausted (there are no more items to iterate over). Code within the else block will **not** run if the loop is broken out using `break`.
4 |
5 | Here's an example of its usage:
6 | ```py
7 | numbers = [1, 3, 5, 7, 9, 11]
8 |
9 | for number in numbers:
10 | if number % 2 == 0:
11 | print(f"Found an even number: {number}")
12 | break
13 | print(f"{number} is odd.")
14 | else:
15 | print("All numbers are odd. How odd.")
16 | ```
17 | Try running this example but with an even number in the list, see how the output changes as you do so.
18 |
--------------------------------------------------------------------------------
/resources/tags/functions-are-objects.md:
--------------------------------------------------------------------------------
1 | **Calling vs. Referencing functions**
2 |
3 | When assigning a new name to a function, storing it in a container, or passing it as an argument, a common mistake made is to call the function. Instead of getting the actual function, you'll get its return value.
4 |
5 | In Python you can treat function names just like any other variable. Assume there was a function called `now` that returns the current time. If you did `x = now()`, the current time would be assigned to `x`, but if you did `x = now`, the function `now` itself would be assigned to `x`. `x` and `now` would both equally reference the function.
6 |
7 | **Examples**
8 | ```py
9 | # assigning new name
10 |
11 | def foo():
12 | return 'bar'
13 |
14 | def spam():
15 | return 'eggs'
16 |
17 | baz = foo
18 | baz() # returns 'bar'
19 |
20 | ham = spam
21 | ham() # returns 'eggs'
22 | ```
23 | ```py
24 | # storing in container
25 |
26 | import math
27 | functions = [math.sqrt, math.factorial, math.log]
28 | functions[0](25) # returns 5.0
29 | # the above equivalent to math.sqrt(25)
30 | ```
31 | ```py
32 | # passing as argument
33 |
34 | class C:
35 | builtin_open = staticmethod(open)
36 |
37 | # open function is passed
38 | # to the staticmethod class
39 | ```
40 |
--------------------------------------------------------------------------------
/resources/tags/global.md:
--------------------------------------------------------------------------------
1 | When adding functions or classes to a program, it can be tempting to reference inaccessible variables by declaring them as global. Doing this can result in code that is harder to read, debug and test. Instead of using globals, pass variables or objects as parameters and receive return values.
2 |
3 | Instead of writing
4 | ```py
5 | def update_score():
6 | global score, roll
7 | score = score + roll
8 | update_score()
9 | ```
10 | do this instead
11 | ```py
12 | def update_score(score, roll):
13 | return score + roll
14 | score = update_score(score, roll)
15 | ```
16 | For in-depth explanations on why global variables are bad news in a variety of situations, see [this Stack Overflow answer](https://stackoverflow.com/questions/19158339/why-are-global-variables-evil/19158418#19158418).
17 |
--------------------------------------------------------------------------------
/resources/tags/guilds.md:
--------------------------------------------------------------------------------
1 | **Communities**
2 |
3 | The [communities page](https://pythondiscord.com/pages/resources/communities/) on our website contains a number of communities we have partnered with as well as a [curated list](https://github.com/mhxion/awesome-discord-communities) of other communities relating to programming and technology.
4 |
--------------------------------------------------------------------------------
/resources/tags/identity.md:
--------------------------------------------------------------------------------
1 | **Identity vs. Equality**
2 |
3 | Should I be using `is` or `==`?
4 |
5 | To check if two objects are equal, use the equality operator (`==`).
6 | ```py
7 | x = 5
8 | if x == 5:
9 | print("x equals 5")
10 | if x == 3:
11 | print("x equals 3")
12 | # Prints 'x equals 5'
13 | ```
14 | To check if two objects are actually the same thing in memory, use the identity comparison operator (`is`).
15 | ```py
16 | list_1 = [1, 2, 3]
17 | list_2 = [1, 2, 3]
18 | if list_1 is [1, 2, 3]:
19 | print("list_1 is list_2")
20 | reference_to_list_1 = list_1
21 | if list_1 is reference_to_list_1:
22 | print("list_1 is reference_to_list_1")
23 | # Prints 'list_1 is reference_to_list_1'
24 | ```
25 |
--------------------------------------------------------------------------------
/resources/tags/if-name-main.md:
--------------------------------------------------------------------------------
1 | `if __name__ == '__main__'`
2 |
3 | This is a statement that is only true if the module (your source code) it appears in is being run directly, as opposed to being imported into another module. When you run your module, the `__name__` special variable is automatically set to the string `'__main__'`. Conversely, when you import that same module into a different one, and run that, `__name__` is instead set to the filename of your module minus the `.py` extension.
4 |
5 | **Example**
6 | ```py
7 | # foo.py
8 |
9 | print('spam')
10 |
11 | if __name__ == '__main__':
12 | print('eggs')
13 | ```
14 | If you run the above module `foo.py` directly, both `'spam'`and `'eggs'` will be printed. Now consider this next example:
15 | ```py
16 | # bar.py
17 |
18 | import foo
19 | ```
20 | If you run this module named `bar.py`, it will execute the code in `foo.py`. First it will print `'spam'`, and then the `if` statement will fail, because `__name__` will now be the string `'foo'`.
21 |
22 | **Why would I do this?**
23 |
24 | • Your module is a library, but also has a special case where it can be run directly
25 | • Your module is a library and you want to safeguard it against people running it directly (like what `pip` does)
26 | • Your module is the main program, but has unit tests and the testing framework works by importing your module, and you want to avoid having your main code run during the test
27 |
--------------------------------------------------------------------------------
/resources/tags/indent.md:
--------------------------------------------------------------------------------
1 | **Indentation**
2 |
3 | Indentation is leading whitespace (spaces and tabs) at the beginning of a line of code. In the case of Python, they are used to determine the grouping of statements.
4 |
5 | Spaces should be preferred over tabs. To be clear, this is in reference to the character itself, not the keys on a keyboard. Your editor/IDE should be configured to insert spaces when the TAB key is pressed. The amount of spaces should be a multiple of 4, except optionally in the case of continuation lines.
6 |
7 | **Example**
8 | ```py
9 | def foo():
10 | bar = 'baz' # indented one level
11 | if bar == 'baz':
12 | print('ham') # indented two levels
13 | return bar # indented one level
14 | ```
15 | The first line is not indented. The next two lines are indented to be inside of the function definition. They will only run when the function is called. The fourth line is indented to be inside the `if` statement, and will only run if the `if` statement evaluates to `True`. The fifth and last line is like the 2nd and 3rd and will always run when the function is called. It effectively closes the `if` statement above as no more lines can be inside the `if` statement below that line.
16 |
17 | **Indentation is used after:**
18 | **1.** [Compound statements](https://docs.python.org/3/reference/compound_stmts.html) (eg. `if`, `while`, `for`, `try`, `with`, `def`, `class`, and their counterparts)
19 | **2.** [Continuation lines](https://www.python.org/dev/peps/pep-0008/#indentation)
20 |
21 | **More Info**
22 | **1.** [Indentation style guide](https://www.python.org/dev/peps/pep-0008/#indentation)
23 | **2.** [Tabs or Spaces?](https://www.python.org/dev/peps/pep-0008/#tabs-or-spaces)
24 | **3.** [Official docs on indentation](https://docs.python.org/3/reference/lexical_analysis.html#indentation)
25 |
--------------------------------------------------------------------------------
/resources/tags/inline.md:
--------------------------------------------------------------------------------
1 | **Inline codeblocks**
2 |
3 | Inline codeblocks look `like this`. To create them you surround text with single backticks, so \`hello\` would become `hello`.
4 |
5 | Note that backticks are not quotes, see [this](https://superuser.com/questions/254076/how-do-i-type-the-tick-and-backtick-characters-on-windows/254077#254077) if you are struggling to find the backtick key.
6 |
7 | For how to make multiline codeblocks see the `!codeblock` tag.
8 |
--------------------------------------------------------------------------------
/resources/tags/intents.md:
--------------------------------------------------------------------------------
1 | **Using intents in discord.py**
2 |
3 | Intents are a feature of Discord that tells the gateway exactly which events to send your bot. By default, discord.py has all intents enabled, except for the `Members` and `Presences` intents, which are needed for events such as `on_member` and to get members' statuses.
4 |
5 | To enable one of these intents, you need to first go to the [Discord developer portal](https://discord.com/developers/applications), then to the bot page of your bot's application. Scroll down to the `Privileged Gateway Intents` section, then enable the intents that you need.
6 |
7 | Next, in your bot you need to set the intents you want to connect with in the bot's constructor using the `intents` keyword argument, like this:
8 |
9 | ```py
10 | from discord import Intents
11 | from discord.ext import commands
12 |
13 | intents = Intents.default()
14 | intents.members = True
15 |
16 | bot = commands.Bot(command_prefix="!", intents=intents)
17 | ```
18 |
19 | For more info about using intents, see the [discord.py docs on intents](https://discordpy.readthedocs.io/en/latest/intents.html), and for general information about them, see the [Discord developer documentation on intents](https://discord.com/developers/docs/topics/gateway#gateway-intents).
20 |
--------------------------------------------------------------------------------
/resources/tags/iterate-dict.md:
--------------------------------------------------------------------------------
1 | There are two common ways to iterate over a dictionary in Python. To iterate over the keys:
2 | ```py
3 | for key in my_dict:
4 | print(key)
5 | ```
6 | To iterate over both the keys and values:
7 | ```py
8 | for key, val in my_dict.items():
9 | print(key, val)
10 | ```
11 |
--------------------------------------------------------------------------------
/resources/tags/kindling-projects.md:
--------------------------------------------------------------------------------
1 | **Kindling Projects**
2 |
3 | The [Kindling projects page](https://nedbatchelder.com/text/kindling.html) on Ned Batchelder's website contains a list of projects and ideas programmers can tackle to build their skills and knowledge.
4 |
--------------------------------------------------------------------------------
/resources/tags/listcomps.md:
--------------------------------------------------------------------------------
1 | Do you ever find yourself writing something like this?
2 | ```py
3 | >>> squares = []
4 | >>> for n in range(5):
5 | ... squares.append(n ** 2)
6 | [0, 1, 4, 9, 16]
7 | ```
8 | Using list comprehensions can make this both shorter and more readable. As a list comprehension, the same code would look like this:
9 | ```py
10 | >>> [n ** 2 for n in range(5)]
11 | [0, 1, 4, 9, 16]
12 | ```
13 | List comprehensions also get an `if` statement:
14 | ```python
15 | >>> [n ** 2 for n in range(5) if n % 2 == 0]
16 | [0, 4, 16]
17 | ```
18 |
19 | For more info, see [this pythonforbeginners.com post](http://www.pythonforbeginners.com/basics/list-comprehensions-in-python).
20 |
--------------------------------------------------------------------------------
/resources/tags/local-file.md:
--------------------------------------------------------------------------------
1 | Thanks to discord.py, sending local files as embed images is simple. You have to create an instance of [`discord.File`](https://discordpy.readthedocs.io/en/latest/api.html#discord.File) class:
2 | ```py
3 | # When you know the file exact path, you can pass it.
4 | file = discord.File("/this/is/path/to/my/file.png", filename="file.png")
5 |
6 | # When you have the file-like object, then you can pass this instead path.
7 | with open("/this/is/path/to/my/file.png", "rb") as f:
8 | file = discord.File(f)
9 | ```
10 | When using the file-like object, you have to open it in `rb` mode. Also, in this case, passing `filename` to it is not necessary.
11 | Please note that `filename` can't contain underscores. This is a Discord limitation.
12 |
13 | [`discord.Embed`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed) instances have a [`set_image`](https://discordpy.readthedocs.io/en/latest/api.html#discord.Embed.set_image) method which can be used to set an attachment as an image:
14 | ```py
15 | embed = discord.Embed()
16 | # Set other fields
17 | embed.set_image(url="attachment://file.png") # Filename here must be exactly same as attachment filename.
18 | ```
19 | After this, you can send an embed with an attachment to Discord:
20 | ```py
21 | await channel.send(file=file, embed=embed)
22 | ```
23 | This example uses [`discord.TextChannel`](https://discordpy.readthedocs.io/en/latest/api.html#discord.TextChannel) for sending, but any instance of [`discord.abc.Messageable`](https://discordpy.readthedocs.io/en/latest/api.html#discord.abc.Messageable) can be used for sending.
24 |
--------------------------------------------------------------------------------
/resources/tags/microsoft-build-tools.md:
--------------------------------------------------------------------------------
1 | **Microsoft Visual C++ Build Tools**
2 |
3 | When you install a library through `pip` on Windows, sometimes you may encounter this error:
4 |
5 | ```
6 | error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/
7 | ```
8 |
9 | This means the library you're installing has code written in other languages and needs additional tools to install. To install these tools, follow the following steps: (Requires 6GB+ disk space)
10 |
11 | **1.** Open [https://visualstudio.microsoft.com/visual-cpp-build-tools/](https://visualstudio.microsoft.com/visual-cpp-build-tools/).
12 | **2.** Click **`Download Build Tools >`**. A file named `vs_BuildTools` or `vs_BuildTools.exe` should start downloading. If no downloads start after a few seconds, click **`click here to retry`**.
13 | **3.** Run the downloaded file. Click **`Continue`** to proceed.
14 | **4.** Choose **C++ build tools** and press **`Install`**. You may need a reboot after the installation.
15 | **5.** Try installing the library via `pip` again.
16 |
--------------------------------------------------------------------------------
/resources/tags/modmail.md:
--------------------------------------------------------------------------------
1 | **Contacting the moderation team via ModMail**
2 |
3 | <@!683001325440860340> is a bot that will relay your messages to our moderation team, so that you can start a conversation with the moderation team. Your messages will be relayed to the entire moderator team, who will be able to respond to you via the bot.
4 |
5 | It supports attachments, codeblocks, and reactions. As communication happens over direct messages, the conversation will stay between you and the mod team.
6 |
7 | **To use it, simply send a direct message to the bot.**
8 |
9 | Should there be an urgent and immediate need for a moderator to look at a channel, feel free to ping the <@&831776746206265384> role instead.
10 |
--------------------------------------------------------------------------------
/resources/tags/mutability.md:
--------------------------------------------------------------------------------
1 | **Mutable vs immutable objects**
2 |
3 | Imagine that you want to make all letters in a string upper case. Conveniently, strings have an `.upper()` method.
4 |
5 | You might think that this would work:
6 | ```python
7 | >>> greeting = "hello"
8 | >>> greeting.upper()
9 | 'HELLO'
10 | >>> greeting
11 | 'hello'
12 | ```
13 |
14 | `greeting` didn't change. Why is that so?
15 |
16 | That's because strings in Python are _immutable_. You can't change them, you can only pass around existing strings or create new ones.
17 |
18 | ```python
19 | >>> greeting = "hello"
20 | >>> greeting = greeting.upper()
21 | >>> greeting
22 | 'HELLO'
23 | ```
24 |
25 | `greeting.upper()` creates and returns a new string which is like the old one, but with all the letters turned to upper case.
26 |
27 | `int`, `float`, `complex`, `tuple`, `frozenset` are other examples of immutable data types in Python.
28 |
29 | Mutable data types like `list`, on the other hand, can be changed in-place:
30 | ```python
31 | >>> my_list = [1, 2, 3]
32 | >>> my_list.append(4)
33 | >>> my_list
34 | [1, 2, 3, 4]
35 | ```
36 |
37 | Other examples of mutable data types in Python are `dict` and `set`. Instances of user-defined classes are also mutable.
38 |
--------------------------------------------------------------------------------
/resources/tags/mutable-default-args.md:
--------------------------------------------------------------------------------
1 | **Mutable Default Arguments**
2 |
3 | Default arguments in python are evaluated *once* when the function is
4 | **defined**, *not* each time the function is **called**. This means that if
5 | you have a mutable default argument and mutate it, you will have
6 | mutated that object for all future calls to the function as well.
7 |
8 | For example, the following `append_one` function appends `1` to a list
9 | and returns it. `foo` is set to an empty list by default.
10 | ```python
11 | >>> def append_one(foo=[]):
12 | ... foo.append(1)
13 | ... return foo
14 | ...
15 | ```
16 | See what happens when we call it a few times:
17 | ```python
18 | >>> append_one()
19 | [1]
20 | >>> append_one()
21 | [1, 1]
22 | >>> append_one()
23 | [1, 1, 1]
24 | ```
25 | Each call appends an additional `1` to our list `foo`. It does not
26 | receive a new empty list on each call, it is the same list everytime.
27 |
28 | To avoid this problem, you have to create a new object every time the
29 | function is **called**:
30 | ```python
31 | >>> def append_one(foo=None):
32 | ... if foo is None:
33 | ... foo = []
34 | ... foo.append(1)
35 | ... return foo
36 | ...
37 | >>> append_one()
38 | [1]
39 | >>> append_one()
40 | [1]
41 | ```
42 |
43 | **Note**:
44 |
45 | • This behavior can be used intentionally to maintain state between
46 | calls of a function (eg. when writing a caching function).
47 | • This behavior is not unique to mutable objects, all default
48 | arguments are evaulated only once when the function is defined.
49 |
--------------------------------------------------------------------------------
/resources/tags/names.md:
--------------------------------------------------------------------------------
1 | **Naming and Binding**
2 |
3 | A name is a piece of text that is bound to an object. They are a **reference** to an object. Examples are function names, class names, module names, variables, etc.
4 |
5 | **Note:** Names **cannot** reference other names, and assignment **never** creates a copy.
6 | ```py
7 | x = 1 # x is bound to 1
8 | y = x # y is bound to VALUE of x
9 | x = 2 # x is bound to 2
10 | print(x, y) # 2 1
11 | ```
12 | When doing `y = x`, the name `y` is being bound to the *value* of `x` which is `1`. Neither `x` nor `y` are the 'real' name. The object `1` simply has *multiple* names. They are the exact same object.
13 | ```
14 | >>> x = 1
15 | x ━━ 1
16 |
17 | >>> y = x
18 | x ━━ 1
19 | y ━━━┛
20 |
21 | >>> x = 2
22 | x ━━ 2
23 | y ━━ 1
24 | ```
25 | **Names are created in multiple ways**
26 | You might think that the only way to bind a name to an object is by using assignment, but that isn't the case. All of the following work exactly the same as assignment:
27 | • `import` statements
28 | • `class` and `def`
29 | • `for` loop headers
30 | • `as` keyword when used with `except`, `import`, and `with`
31 | • formal parameters in function headers
32 |
33 | There is also `del` which has the purpose of *unbinding* a name.
34 |
35 | **More info**
36 | • Please watch [Ned Batchelder's talk](https://youtu.be/_AEJHKGk9ns) on names in python for a detailed explanation with examples
37 | • [Official documentation](https://docs.python.org/3/reference/executionmodel.html#naming-and-binding)
38 |
--------------------------------------------------------------------------------
/resources/tags/off-topic-names.md:
--------------------------------------------------------------------------------
1 | **Off-topic channels**
2 |
3 | There are three off-topic channels:
4 | • <#291284109232308226>
5 | • <#463035241142026251>
6 | • <#463035268514185226>
7 |
8 | The channel names change every night at midnight UTC and are often fun meta references to jokes or conversations that happened on the server.
9 |
10 | See our [off-topic etiquette](https://pythondiscord.com/pages/resources/guides/off-topic-etiquette/) page for more guidance on how the channels should be used.
11 |
--------------------------------------------------------------------------------
/resources/tags/open.md:
--------------------------------------------------------------------------------
1 | **Opening files**
2 |
3 | The built-in function `open()` is one of several ways to open files on your computer. It accepts many different parameters, so this tag will only go over two of them (`file` and `mode`). For more extensive documentation on all these parameters, consult the [official documentation](https://docs.python.org/3/library/functions.html#open). The object returned from this function is a [file object or stream](https://docs.python.org/3/glossary.html#term-file-object), for which the full documentation can be found [here](https://docs.python.org/3/library/io.html#io.TextIOBase).
4 |
5 | See also:
6 | • `!tags with` for information on context managers
7 | • `!tags pathlib` for an alternative way of opening files
8 | • `!tags seek` for information on changing your position in a file
9 |
10 | **The `file` parameter**
11 |
12 | This should be a [path-like object](https://docs.python.org/3/glossary.html#term-path-like-object) denoting the name or path (absolute or relative) to the file you want to open.
13 |
14 | An absolute path is the full path from your root directory to the file you want to open. Generally this is the option you should choose so it doesn't matter what directory you're in when you execute your module.
15 |
16 | See `!tags relative-path` for more information on relative paths.
17 |
18 | **The `mode` parameter**
19 |
20 | This is an optional string that specifies the mode in which the file should be opened. There's not enough room to discuss them all, but listed below are some of the more confusing modes.
21 |
22 | `'r+'` Opens for reading and writing (file must already exist)
23 | `'w+'` Opens for reading and writing and truncates (can create files)
24 | `'x'` Creates file and opens for writing (file must **not** already exist)
25 | `'x+'` Creates file and opens for reading and writing (file must **not** already exist)
26 | `'a+'` Opens file for reading and writing at **end of file** (can create files)
27 |
--------------------------------------------------------------------------------
/resources/tags/or-gotcha.md:
--------------------------------------------------------------------------------
1 | When checking if something is equal to one thing or another, you might think that this is possible:
2 | ```py
3 | if favorite_fruit == 'grapefruit' or 'lemon':
4 | print("That's a weird favorite fruit to have.")
5 | ```
6 | While this makes sense in English, it may not behave the way you would expect. In Python, you should have _[complete instructions on both sides of the logical operator](https://docs.python.org/3/reference/expressions.html#boolean-operations)_.
7 |
8 | So, if you want to check if something is equal to one thing or another, there are two common ways:
9 | ```py
10 | # Like this...
11 | if favorite_fruit == 'grapefruit' or favorite_fruit == 'lemon':
12 | print("That's a weird favorite fruit to have.")
13 |
14 | # ...or like this.
15 | if favorite_fruit in ('grapefruit', 'lemon'):
16 | print("That's a weird favorite fruit to have.")
17 | ```
18 |
--------------------------------------------------------------------------------
/resources/tags/ot.md:
--------------------------------------------------------------------------------
1 | **Off-topic channel:** <#463035268514185226>
2 |
3 | Please read our [off-topic etiquette](https://pythondiscord.com/pages/resources/guides/off-topic-etiquette/) before participating in conversations.
4 |
--------------------------------------------------------------------------------
/resources/tags/param-arg.md:
--------------------------------------------------------------------------------
1 | **Parameters vs. Arguments**
2 |
3 | A parameter is a variable defined in a function signature (the line with `def` in it), while arguments are objects passed to a function call.
4 |
5 | ```py
6 | def square(n): # n is the parameter
7 | return n*n
8 |
9 | print(square(5)) # 5 is the argument
10 | ```
11 |
12 | Note that `5` is the argument passed to `square`, but `square(5)` in its entirety is the argument passed to `print`
13 |
--------------------------------------------------------------------------------
/resources/tags/paste.md:
--------------------------------------------------------------------------------
1 | **Pasting large amounts of code**
2 |
3 | If your code is too long to fit in a codeblock in discord, you can paste your code here:
4 | https://paste.pythondiscord.com/
5 |
6 | After pasting your code, **save** it by clicking the floppy disk icon in the top right, or by typing `ctrl + S`. After doing that, the URL should **change**. Copy the URL and post it here so others can see it.
7 |
--------------------------------------------------------------------------------
/resources/tags/pathlib.md:
--------------------------------------------------------------------------------
1 | **Pathlib**
2 |
3 | Python 3 comes with a new module named `Pathlib`. Since Python 3.6, `pathlib.Path` objects work nearly everywhere that `os.path` can be used, meaning you can integrate your new code directly into legacy code without having to rewrite anything. Pathlib makes working with paths way simpler than `os.path` does.
4 |
5 | **Feature spotlight**:
6 |
7 | • Normalizes file paths for all platforms automatically
8 | • Has glob-like utilites (eg. `Path.glob`, `Path.rglob`) for searching files
9 | • Can read and write files, and close them automatically
10 | • Convenient syntax, utilising the `/` operator (e.g. `Path('~') / 'Documents'`)
11 | • Can easily pick out components of a path (eg. name, parent, stem, suffix, anchor)
12 | • Supports method chaining
13 | • Move and delete files
14 | • And much more
15 |
16 | **More Info**:
17 |
18 | • [**Why you should use pathlib** - Trey Hunner](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
19 | • [**Answering concerns about pathlib** - Trey Hunner](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
20 | • [**Official Documentation**](https://docs.python.org/3/library/pathlib.html)
21 | • [**PEP 519** - Adding a file system path protocol](https://www.python.org/dev/peps/pep-0519/)
22 |
--------------------------------------------------------------------------------
/resources/tags/pep8.md:
--------------------------------------------------------------------------------
1 | **PEP 8** is the official style guide for Python. It includes comprehensive guidelines for code formatting, variable naming, and making your code easy to read. Professional Python developers are usually required to follow the guidelines, and will often use code-linters like flake8 to verify that the code they're writing complies with the style guide.
2 |
3 | More information:
4 | • [PEP 8 document](https://www.python.org/dev/peps/pep-0008)
5 | • [Our PEP 8 song!](https://www.youtube.com/watch?v=hgI0p1zf31k) :notes:
6 |
--------------------------------------------------------------------------------
/resources/tags/positional-keyword.md:
--------------------------------------------------------------------------------
1 | **Positional vs. Keyword arguments**
2 |
3 | Functions can take two different kinds of arguments. A positional argument is just the object itself. A keyword argument is a name assigned to an object.
4 |
5 | **Example**
6 | ```py
7 | >>> print('Hello', 'world!', sep=', ')
8 | Hello, world!
9 | ```
10 | The first two strings `'Hello'` and `world!'` are positional arguments.
11 | The `sep=', '` is a keyword argument.
12 |
13 | **Note**
14 | A keyword argument can be passed positionally in some cases.
15 | ```py
16 | def sum(a, b=1):
17 | return a + b
18 |
19 | sum(1, b=5)
20 | sum(1, 5) # same as above
21 | ```
22 | [Somtimes this is forced](https://www.python.org/dev/peps/pep-0570/#history-of-positional-only-parameter-semantics-in-python), in the case of the `pow()` function.
23 |
24 | The reverse is also true:
25 | ```py
26 | >>> def foo(a, b):
27 | ... print(a, b)
28 | ...
29 | >>> foo(a=1, b=2)
30 | 1 2
31 | >>> foo(b=1, a=2)
32 | 2 1
33 | ```
34 |
35 | **More info**
36 | • [Keyword only arguments](https://www.python.org/dev/peps/pep-3102/)
37 | • [Positional only arguments](https://www.python.org/dev/peps/pep-0570/)
38 | • `!tags param-arg` (Parameters vs. Arguments)
39 |
--------------------------------------------------------------------------------
/resources/tags/precedence.md:
--------------------------------------------------------------------------------
1 | **Operator Precedence**
2 |
3 | Operator precedence is essentially like an order of operations for python's operators.
4 |
5 | **Example 1** (arithmetic)
6 | `2 * 3 + 1` is `7` because multiplication is first
7 | `2 * (3 + 1)` is `8` because the parenthesis change the precedence allowing the sum to be first
8 |
9 | **Example 2** (logic)
10 | `not True or True` is `True` because the `not` is first
11 | `not (True or True)` is `False` because the `or` is first
12 |
13 | The full table of precedence from lowest to highest is [here](https://docs.python.org/3/reference/expressions.html#operator-precedence)
14 |
--------------------------------------------------------------------------------
/resources/tags/quotes.md:
--------------------------------------------------------------------------------
1 | **String Quotes**
2 |
3 | Single and Double quoted strings are the **same** in Python. The choice of which one to use is up to you, just make sure that you **stick to that choice**.
4 |
5 | With that said, there are exceptions to this that are more important than consistency. If a single or double quote is needed *inside* the string, using the opposite quotation is better than using escape characters.
6 |
7 | Example:
8 | ```py
9 | 'My name is "Guido"' # good
10 | "My name is \"Guido\"" # bad
11 |
12 | "Don't go in there" # good
13 | 'Don\'t go in there' # bad
14 | ```
15 | **Note:**
16 | If you need both single and double quotes inside your string, use the version that would result in the least amount of escapes. In the case of a tie, use the quotation you use the most.
17 |
18 | **References:**
19 | • [pep-8 on quotes](https://www.python.org/dev/peps/pep-0008/#string-quotes)
20 | • [convention for triple quoted strings](https://www.python.org/dev/peps/pep-0257/)
21 |
--------------------------------------------------------------------------------
/resources/tags/range-len.md:
--------------------------------------------------------------------------------
1 | Iterating over `range(len(...))` is a common approach to accessing each item in an ordered collection.
2 | ```py
3 | for i in range(len(my_list)):
4 | do_something(my_list[i])
5 | ```
6 | The pythonic syntax is much simpler, and is guaranteed to produce elements in the same order:
7 | ```py
8 | for item in my_list:
9 | do_something(item)
10 | ```
11 | Python has other solutions for cases when the index itself might be needed. To get the element at the same index from two or more lists, use [zip](https://docs.python.org/3/library/functions.html#zip). To get both the index and the element at that index, use [enumerate](https://docs.python.org/3/library/functions.html#enumerate).
12 |
--------------------------------------------------------------------------------
/resources/tags/relative-path.md:
--------------------------------------------------------------------------------
1 | **Relative Path**
2 |
3 | A relative path is a partial path that is relative to your current working directory. A common misconception is that your current working directory is the location of the module you're executing, **but this is not the case**. Your current working directory is actually the **directory you were in when you ran the python interpreter**. The reason for this misconception is because a common way to run your code is to navigate to the directory your module is stored, and run `python .py`. Thus, in this case your current working directory will be the same as the location of the module. However, if we instead did `python path/to/.py`, our current working directory would no longer be the same as the location of the module we're executing.
4 |
5 | **Why is this important?**
6 |
7 | When opening files in python, relative paths won't always work since it's dependent on what directory you were in when you ran your code. A common issue people face is running their code in an IDE thinking they can open files that are in the same directory as their module, but the current working directory will be different than what they expect and so they won't find the file. The way to avoid this problem is by using absolute paths, which is the full path from your root directory to the file you want to open.
8 |
--------------------------------------------------------------------------------
/resources/tags/repl.md:
--------------------------------------------------------------------------------
1 | **Read-Eval-Print Loop**
2 |
3 | A REPL is an interactive language shell environment. It first **reads** one or more expressions entered by the user, **evaluates** it, yields the result, and **prints** it out to the user. It will then **loop** back to the **read** step.
4 |
5 | To use python's REPL, execute the interpreter with no arguments. This will drop you into the interactive interpreter shell, print out some relevant information, and then prompt you with the primary prompt `>>>`. At this point it is waiting for your input.
6 |
7 | Firstly you can start typing in some valid python expressions, pressing to either bring you to the **eval** step, or prompting you with the secondary prompt `...` (or no prompt at all depending on your environment), meaning your expression isn't yet terminated and it's waiting for more input. This is useful for code that requires multiple lines like loops, functions, and classes. If you reach the secondary prompt in a clause that can have an arbitrary amount of expressions, you can terminate it by pressing on a blank line. In other words, for the last expression you write in the clause, must be pressed twice in a row.
8 |
9 | Alternatively, you can make use of the builtin `help()` function. `help(thing)` to get help on some `thing` object, or `help()` to start an interactive help session. This mode is extremely powerful, read the instructions when first entering the session to learn how to use it.
10 |
11 | Lastly you can run your code with the `-i` flag to execute your code normally, but be dropped into the REPL once execution is finished, giving you access to all your global variables/functions in the REPL.
12 |
13 | To **exit** either a help session, or normal REPL prompt, you must send an EOF signal to the prompt. In *nix systems, this is done with `ctrl + D`, and in windows systems it is `ctrl + Z`. You can also exit the normal REPL prompt with the dedicated functions `exit()` or `quit()`.
14 |
--------------------------------------------------------------------------------
/resources/tags/resources.md:
--------------------------------------------------------------------------------
1 | ---
2 | embed:
3 | title: "Resources"
4 | ---
5 |
6 | The [Resources page](https://www.pythondiscord.com/resources/) on our website contains a list of hand-selected learning resources that we regularly recommend to both beginners and experts.
7 |
--------------------------------------------------------------------------------
/resources/tags/return.md:
--------------------------------------------------------------------------------
1 | **Return Statement**
2 |
3 | When calling a function, you'll often want it to give you a value back. In order to do that, you must `return` it. The reason for this is because functions have their own scope. Any values defined within the function body are inaccessible outside of that function.
4 |
5 | *For more information about scope, see `!tags scope`*
6 |
7 | Consider the following function:
8 | ```py
9 | def square(n):
10 | return n*n
11 | ```
12 | If we wanted to store 5 squared in a variable called `x`, we could do that like so:
13 | `x = square(5)`. `x` would now equal `25`.
14 |
15 | **Common Mistakes**
16 | ```py
17 | >>> def square(n):
18 | ... n*n # calculates then throws away, returns None
19 | ...
20 | >>> x = square(5)
21 | >>> print(x)
22 | None
23 | >>> def square(n):
24 | ... print(n*n) # calculates and prints, then throws away and returns None
25 | ...
26 | >>> x = square(5)
27 | 25
28 | >>> print(x)
29 | None
30 | ```
31 | **Things to note**
32 | • `print()` and `return` do **not** accomplish the same thing. `print()` will only print the value, it will not be accessible outside of the function afterwards.
33 | • A function will return `None` if it ends without reaching an explicit `return` statement.
34 | • When you want to print a value calculated in a function, instead of printing inside the function, it is often better to return the value and print the *function call* instead.
35 | • [Official documentation for `return`](https://docs.python.org/3/reference/simple_stmts.html#the-return-statement)
36 |
--------------------------------------------------------------------------------
/resources/tags/round.md:
--------------------------------------------------------------------------------
1 | **Round half to even**
2 |
3 | Python 3 uses bankers' rounding (also known by other names), where if the fractional part of a number is `.5`, it's rounded to the nearest **even** result instead of away from zero.
4 |
5 | Example:
6 | ```py
7 | >>> round(2.5)
8 | 2
9 | >>> round(1.5)
10 | 2
11 | ```
12 | In the first example, there is a tie between 2 and 3, and since 3 is odd and 2 is even, the result is 2.
13 | In the second example, the tie is between 1 and 2, and so 2 is also the result.
14 |
15 | **Why this is done:**
16 | The round half up technique creates a slight bias towards the larger number. With a large amount of calculations, this can be significant. The round half to even technique eliminates this bias.
17 |
18 | It should be noted that round half to even distorts the distribution by increasing the probability of evens relative to odds, however this is considered less important than the bias explained above.
19 |
20 | **References:**
21 | • [Wikipedia article about rounding](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even)
22 | • [Documentation on `round` function](https://docs.python.org/3/library/functions.html#round)
23 | • [`round` in what's new in python 3](https://docs.python.org/3/whatsnew/3.0.html#builtins) (4th bullet down)
24 | • [How to force rounding technique](https://stackoverflow.com/a/10826537/4607272)
25 |
--------------------------------------------------------------------------------
/resources/tags/scope.md:
--------------------------------------------------------------------------------
1 | **Scoping Rules**
2 |
3 | A *scope* defines the visibility of a name within a block, where a block is a piece of python code executed as a unit. For simplicity, this would be a module, a function body, and a class definition. A name refers to text bound to an object.
4 |
5 | *For more information about names, see `!tags names`*
6 |
7 | A module is the source code file itself, and encompasses all blocks defined within it. Therefore if a variable is defined at the module level (top-level code block), it is a global variable and can be accessed anywhere in the module as long as the block in which it's referenced is executed after it was defined.
8 |
9 | Alternatively if a variable is defined within a function block for example, it is a local variable. It is not accessible at the module level, as that would be *outside* its scope. This is the purpose of the `return` statement, as it hands an object back to the scope of its caller. Conversely if a function was defined *inside* the previously mentioned block, it *would* have access to that variable, because it is within the first function's scope.
10 | ```py
11 | >>> def outer():
12 | ... foo = 'bar' # local variable to outer
13 | ... def inner():
14 | ... print(foo) # has access to foo from scope of outer
15 | ... return inner # brings inner to scope of caller
16 | ...
17 | >>> inner = outer() # get inner function
18 | >>> inner() # prints variable foo without issue
19 | bar
20 | ```
21 | **Official Documentation**
22 | **1.** [Program structure, name binding and resolution](https://docs.python.org/3/reference/executionmodel.html#execution-model)
23 | **2.** [`global` statement](https://docs.python.org/3/reference/simple_stmts.html#the-global-statement)
24 | **3.** [`nonlocal` statement](https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement)
25 |
--------------------------------------------------------------------------------
/resources/tags/seek.md:
--------------------------------------------------------------------------------
1 | **Seek**
2 |
3 | In the context of a [file object](https://docs.python.org/3/glossary.html#term-file-object), the `seek` function changes the stream position to a given byte offset, with an optional argument of where to offset from. While you can find the official documentation [here](https://docs.python.org/3/library/io.html#io.IOBase.seek), it can be unclear how to actually use this feature, so keep reading to see examples on how to use it.
4 |
5 | File named `example`:
6 | ```
7 | foobar
8 | spam eggs
9 | ```
10 | Open file for reading in byte mode:
11 | ```py
12 | f = open('example', 'rb')
13 | ```
14 | Note that stream positions start from 0 in much the same way that the index for a list does. If we do `f.seek(3, 0)`, our stream position will move 3 bytes forward relative to the **beginning** of the stream. Now if we then did `f.read(1)` to read a single byte from where we are in the stream, it would return the string `'b'` from the 'b' in 'foobar'. Notice that the 'b' is the 4th character. Also note that after we did `f.read(1)`, we moved the stream position again 1 byte forward relative to the **current** position in the stream. So the stream position is now currently at position 4.
15 |
16 | Now lets do `f.seek(4, 1)`. This will move our stream position 4 bytes forward relative to our **current** position in the stream. Now if we did `f.read(1)`, it would return the string `'p'` from the 'p' in 'spam' on the next line. Note this time that the character at position 6 is the newline character `'\n'`.
17 |
18 | Finally, lets do `f.seek(-4, 2)`, moving our stream position *backwards* 4 bytes relative to the **end** of the stream. Now if we did `f.read()` to read everything after our position in the file, it would return the string `'eggs'` and also move our stream position to the end of the file.
19 |
20 | **Note**
21 | • For the second argument in `seek()`, use `os.SEEK_SET`, `os.SEEK_CUR`, and `os.SEEK_END` in place of 0, 1, and 2 respectively.
22 | • `os.SEEK_CUR` is only usable when the file is in byte mode.
23 |
--------------------------------------------------------------------------------
/resources/tags/self.md:
--------------------------------------------------------------------------------
1 | **Class instance**
2 |
3 | When calling a method from a class instance (ie. `instance.method()`), the instance itself will automatically be passed as the first argument implicitly. By convention, we call this `self`, but it could technically be called any valid variable name.
4 |
5 | ```py
6 | class Foo:
7 | def bar(self):
8 | print('bar')
9 |
10 | def spam(self, eggs):
11 | print(eggs)
12 |
13 | foo = Foo()
14 | ```
15 |
16 | If we call `foo.bar()`, it is equivalent to doing `Foo.bar(foo)`. Our instance `foo` is passed for us to the `bar` function, so while we initially gave zero arguments, it is actually called with one.
17 |
18 | Similarly if we call `foo.spam('ham')`, it is equivalent to
19 | doing `Foo.spam(foo, 'ham')`.
20 |
21 | **Why is this useful?**
22 |
23 | Methods do not inherently have access to attributes defined in the class. In order for any one method to be able to access other methods or variables defined in the class, it must have access to the instance.
24 |
25 | Consider if outside the class, we tried to do this: `spam(foo, 'ham')`. This would give an error, because we don't have access to the `spam` method directly, we have to call it by doing `foo.spam('ham')`. This is also the case inside of the class. If we wanted to call the `bar` method inside the `spam` method, we'd have to do `self.bar()`, just doing `bar()` would give an error.
26 |
--------------------------------------------------------------------------------
/resources/tags/site.md:
--------------------------------------------------------------------------------
1 | ---
2 | embed:
3 | title: "Python Discord Website"
4 | ---
5 |
6 | [Our official website](https://www.pythondiscord.com/) is an open-source community project created with Python and Django. It contains information about the server itself, lets you sign up for upcoming events, has its own wiki, contains a list of valuable learning resources, and much more.
7 |
--------------------------------------------------------------------------------
/resources/tags/sql-fstring.md:
--------------------------------------------------------------------------------
1 | **SQL & f-strings**
2 | Don't use f-strings (`""`) or other forms of "string interpolation" (`%`, `+`, `.format`) to inject data into a SQL query. It is an endless source of bugs and syntax errors. Additionally, in user-facing applications, it presents a major security risk via SQL injection.
3 |
4 | Your database library should support "query parameters". A query parameter is a placeholder that you put in the SQL query. When the query is executed, you provide data to the database library, and the library inserts the data into the query for you, **safely**.
5 |
6 | For example, the sqlite3 package supports using `?` as a placeholder:
7 | ```py
8 | query = "SELECT * FROM stocks WHERE symbol = ?;"
9 | params = ("RHAT",)
10 | db.execute(query, params)
11 | ```
12 | Note: Different database libraries support different placeholder styles, e.g. `%s` and `$1`. Consult your library's documentation for details.
13 |
14 | **See Also**
15 | • [Extended Example with SQLite](https://docs.python.org/3/library/sqlite3.html) (search for "Instead, use the DB-API's parameter substitution")
16 | • [PEP-249](https://www.python.org/dev/peps/pep-0249) - A specification of how database libraries in Python should work
17 |
--------------------------------------------------------------------------------
/resources/tags/star-imports.md:
--------------------------------------------------------------------------------
1 | **Star / Wildcard imports**
2 |
3 | Wildcard imports are import statements in the form `from import *`. What imports like these do is that they import everything **[1]** from the module into the current module's namespace **[2]**. This allows you to use names defined in the imported module without prefixing the module's name.
4 |
5 | Example:
6 | ```python
7 | >>> from math import *
8 | >>> sin(pi / 2)
9 | 1.0
10 | ```
11 | **This is discouraged, for various reasons:**
12 |
13 | Example:
14 | ```python
15 | >>> from custom_sin import sin
16 | >>> from math import *
17 | >>> sin(pi / 2) # uses sin from math rather than your custom sin
18 | ```
19 | • Potential namespace collision. Names defined from a previous import might get shadowed by a wildcard import.
20 | • Causes ambiguity. From the example, it is unclear which `sin` function is actually being used. From the Zen of Python **[3]**: `Explicit is better than implicit.`
21 | • Makes import order significant, which they shouldn't. Certain IDE's `sort import` functionality may end up breaking code due to namespace collision.
22 |
23 | **How should you import?**
24 |
25 | • Import the module under the module's namespace (Only import the name of the module, and names defined in the module can be used by prefixing the module's name)
26 | ```python
27 | >>> import math
28 | >>> math.sin(math.pi / 2)
29 | ```
30 | • Explicitly import certain names from the module
31 | ```python
32 | >>> from math import sin, pi
33 | >>> sin(pi / 2)
34 | ```
35 | Conclusion: Namespaces are one honking great idea -- let's do more of those! *[3]*
36 |
37 | **[1]** If the module defines the variable `__all__`, the names defined in `__all__` will get imported by the wildcard import, otherwise all the names in the module get imported (except for names with a leading underscore)
38 | **[2]** [Namespaces and scopes](https://www.programiz.com/python-programming/namespace)
39 | **[3]** [Zen of Python](https://www.python.org/dev/peps/pep-0020/)
40 |
--------------------------------------------------------------------------------
/resources/tags/str-join.md:
--------------------------------------------------------------------------------
1 | **Joining Iterables**
2 |
3 | If you want to display a list (or some other iterable), you can write:
4 | ```py
5 | Colours = ['red', 'green', 'blue', 'yellow']
6 | output = ""
7 | separator = ", "
8 | for Colour in Colours:
9 | output += Colour + separator
10 | print(output)
11 | # Prints 'red, green, blue, yellow, '
12 | ```
13 | However, the separator is still added to the last element, and it is relatively slow.
14 |
15 | A better solution is to use `str.join`.
16 | ```py
17 | Colours = ['red', 'green', 'blue', 'yellow']
18 | separator = ", "
19 | print(separator.join(Colours))
20 | # Prints 'red, green, blue, yellow'
21 | ```
22 | An important thing to note is that you can only `str.join` strings. For a list of ints,
23 | you must convert each element to a string before joining.
24 | ```py
25 | integers = [1, 3, 6, 10, 15]
26 | print(", ".join(str(e) for e in integers))
27 | # Prints '1, 3, 6, 10, 15'
28 | ```
29 |
--------------------------------------------------------------------------------
/resources/tags/string-formatting.md:
--------------------------------------------------------------------------------
1 | **String Formatting Mini-Language**
2 | The String Formatting Language in Python is a powerful way to tailor the display of strings and other data structures. This string formatting mini language works for f-strings and `.format()`.
3 |
4 | Take a look at some of these examples!
5 | ```py
6 | >>> my_num = 2134234523
7 | >>> print(f"{my_num:,}")
8 | 2,134,234,523
9 |
10 | >>> my_smaller_num = -30.0532234
11 | >>> print(f"{my_smaller_num:=09.2f}")
12 | -00030.05
13 |
14 | >>> my_str = "Center me!"
15 | >>> print(f"{my_str:-^20}")
16 | -----Center me!-----
17 |
18 | >>> repr_str = "Spam \t Ham"
19 | >>> print(f"{repr_str!r}")
20 | 'Spam \t Ham'
21 | ```
22 | **Full Specification & Resources**
23 | [String Formatting Mini Language Specification](https://docs.python.org/3/library/string.html#format-specification-mini-language)
24 | [pyformat.info](https://pyformat.info/)
25 |
--------------------------------------------------------------------------------
/resources/tags/tools.md:
--------------------------------------------------------------------------------
1 | ---
2 | embed:
3 | title: "Tools"
4 | ---
5 |
6 | The [Tools page](https://www.pythondiscord.com/resources/tools/) on our website contains a couple of the most popular tools for programming in Python.
7 |
--------------------------------------------------------------------------------
/resources/tags/traceback.md:
--------------------------------------------------------------------------------
1 | Please provide the full traceback for your exception in order to help us identify your issue.
2 |
3 | A full traceback could look like:
4 | ```py
5 | Traceback (most recent call last):
6 | File "tiny", line 3, in
7 | do_something()
8 | File "tiny", line 2, in do_something
9 | a = 6 / b
10 | ZeroDivisionError: division by zero
11 | ```
12 | The best way to read your traceback is bottom to top.
13 |
14 | • Identify the exception raised (in this case `ZeroDivisionError`)
15 | • Make note of the line number (in this case `2`), and navigate there in your program.
16 | • Try to understand why the error occurred (in this case because `b` is `0`).
17 |
18 | To read more about exceptions and errors, please refer to the [PyDis Wiki](https://pythondiscord.com/pages/guides/pydis-guides/asking-good-questions/#examining-tracebacks) or the [official Python tutorial](https://docs.python.org/3.7/tutorial/errors.html).
19 |
--------------------------------------------------------------------------------
/resources/tags/venv.md:
--------------------------------------------------------------------------------
1 | **Virtual Environments**
2 |
3 | Virtual environments are isolated Python environments, which make it easier to keep your system clean and manage dependencies. By default, when activated, only libraries and scripts installed in the virtual environment are accessible, preventing cross-project dependency conflicts, and allowing easy isolation of requirements.
4 |
5 | To create a new virtual environment, you can use the standard library `venv` module: `python3 -m venv .venv` (replace `python3` with `python` or `py` on Windows)
6 |
7 | Then, to activate the new virtual environment:
8 |
9 | **Windows** (PowerShell): `.venv\Scripts\Activate.ps1`
10 | or (Command Prompt): `.venv\Scripts\activate.bat`
11 | **MacOS / Linux** (Bash): `source .venv/bin/activate`
12 |
13 | Packages can then be installed to the virtual environment using `pip`, as normal.
14 |
15 | For more information, take a read of the [documentation](https://docs.python.org/3/library/venv.html). If you run code through your editor, check its documentation on how to make it use your virtual environment. For example, see the [VSCode](https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment) or [PyCharm](https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html) docs.
16 |
17 | Tools such as [poetry](https://python-poetry.org/docs/basic-usage/) and [pipenv](https://pipenv.pypa.io/en/latest/) can manage the creation of virtual environments as well as project dependencies, making packaging and installing your project easier.
18 |
19 | **Note:** When using Windows PowerShell, you may need to change the [execution policy](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies) first. This is only required once:
20 | `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
21 |
--------------------------------------------------------------------------------
/resources/tags/voice-verification.md:
--------------------------------------------------------------------------------
1 | **Voice verification**
2 |
3 | Can’t talk in voice chat? Check out <#764802555427029012> to get access. The criteria for verifying are specified there.
4 |
--------------------------------------------------------------------------------
/resources/tags/windows-path.md:
--------------------------------------------------------------------------------
1 | **PATH on Windows**
2 |
3 | If you have installed Python but forgot to check the `Add Python to PATH` option during the installation, you may still be able to access your installation with ease.
4 |
5 | If you did not uncheck the option to install the `py launcher`, then you'll instead have a `py` command which can be used in the same way. If you want to be able to access your Python installation via the `python` command, then your best option is to re-install Python (remembering to tick the `Add Python to PATH` checkbox).
6 |
7 | You can pass any options to the Python interpreter, e.g. to install the `[numpy](https://pypi.org/project/numpy/)` module from PyPI you can run `py -3 -m pip install numpy` or `python -m pip install numpy`.
8 |
9 | You can also access different versions of Python using the version flag of the `py` command, like so:
10 | ```
11 | C:\Users\Username> py -3.7
12 | ... Python 3.7 starts ...
13 | C:\Users\Username> py -3.6
14 | ... Python 3.6 starts ...
15 | C:\Users\Username> py -2
16 | ... Python 2 (any version installed) starts ...
17 | ```
18 |
--------------------------------------------------------------------------------
/resources/tags/with.md:
--------------------------------------------------------------------------------
1 | The `with` keyword triggers a context manager. Context managers automatically set up and take down data connections, or any other kind of object that implements the magic methods `__enter__` and `__exit__`.
2 | ```py
3 | with open("test.txt", "r") as file:
4 | do_things(file)
5 | ```
6 | The above code automatically closes `file` when the `with` block exits, so you never have to manually do a `file.close()`. Most connection types, including file readers and database connections, support this.
7 |
8 | For more information, read [the official docs](https://docs.python.org/3/reference/compound_stmts.html#with), watch [Corey Schafer\'s context manager video](https://www.youtube.com/watch?v=-aKFBoZpiqA), or see [PEP 343](https://www.python.org/dev/peps/pep-0343/).
9 |
--------------------------------------------------------------------------------
/resources/tags/xy-problem.md:
--------------------------------------------------------------------------------
1 | **xy-problem**
2 |
3 | The XY problem can be summarised as asking about your attempted solution, rather than your actual problem.
4 |
5 | Often programmers will get distracted with a potential solution they've come up with, and will try asking for help getting it to work. However, it's possible this solution either wouldn't work as they expect, or there's a much better solution instead.
6 |
7 | For more information and examples, see http://xyproblem.info/
8 |
--------------------------------------------------------------------------------
/resources/tags/ytdl.md:
--------------------------------------------------------------------------------
1 | Per [Python Discord's Rule 5](https://pythondiscord.com/pages/rules), we are unable to assist with questions related to youtube-dl, pytube, or other YouTube video downloaders, as their usage violates YouTube's Terms of Service.
2 |
3 | For reference, this usage is covered by the following clauses in [YouTube's TOS](https://www.youtube.com/static?gl=GB&template=terms), as of 2021-03-17:
4 | ```
5 | The following restrictions apply to your use of the Service. You are not allowed to:
6 |
7 | 1. access, reproduce, download, distribute, transmit, broadcast, display, sell, license, alter, modify or otherwise use any part of the Service or any Content except Exception: (a) as specifically permitted by the Service; (b) with prior written permission from YouTube and, if applicable, the respective rights holders; or (c) as permitted by applicable law;
8 |
9 | 3. access the Service using any automated means (such as robots, botnets or scrapers) except Exception: (a) in the case of public search engines, in accordance with YouTube’s robots.txt file; (b) with YouTube’s prior written permission; or (c) as permitted by applicable law;
10 |
11 | 9. use the Service to view or listen to Content other than for personal, non-commercial use (for example, you may not publicly screen videos or stream music from the Service)
12 | ```
13 |
--------------------------------------------------------------------------------
/resources/tags/zip.md:
--------------------------------------------------------------------------------
1 | The zip function allows you to iterate through multiple iterables simultaneously. It joins the iterables together, almost like a zipper, so that each new element is a tuple with one element from each iterable.
2 |
3 | ```py
4 | letters = 'abc'
5 | numbers = [1, 2, 3]
6 | # list(zip(letters, numbers)) --> [('a', 1), ('b', 2), ('c', 3)]
7 | for letter, number in zip(letters, numbers):
8 | print(letter, number)
9 | ```
10 | The `zip()` iterator is exhausted after the length of the shortest iterable is exceeded. If you would like to retain the other values, consider using [itertools.zip_longest](https://docs.python.org/3/library/itertools.html#itertools.zip_longest).
11 |
12 | For more information on zip, please refer to the [official documentation](https://docs.python.org/3/library/functions.html#zip).
13 |
--------------------------------------------------------------------------------
/resources/topic.yaml:
--------------------------------------------------------------------------------
1 | - What is your favourite Easter candy or treat?
2 | - What is your earliest memory of Easter?
3 | - What is the title of the last book you read?
4 | - "What is better: Milk, Dark or White chocolate?"
5 | - What is your favourite holiday?
6 | - If you could have any superpower, what would it be?
7 | - If you could be anyone else for one day, who would it be?
8 | - What Easter tradition do you enjoy most?
9 | - What is the best gift you've been given?
10 | - Name one famous person you would like to have at your easter dinner.
11 | - What was the last movie you saw in a cinema?
12 | - What is your favourite food?
13 | - If you could travel anywhere in the world, where would you go?
14 | - Tell us 5 things you do well.
15 | - What is your favourite place that you have visited?
16 | - What is your favourite Colour?
17 | - If you had $100 bill in your Easter Basket, what would you do with it?
18 | - What would you do if you know you could succeed at anything you chose to do?
19 | - If you could take only three things from your house, what would they be?
20 | - What's the best pastry?
21 | - What's your favourite kind of soup?
22 | - What is the most useless talent that you have?
23 | - Would you rather fight 100 duck sized horses or one horse sized duck?
24 | - What is your favourite Colour?
25 | - What's your favourite type of weather?
26 | - Tea or coffee? What about milk?
27 | - Do you speak a language other than English?
28 | - What is your favorite TV show?
29 | - What is your favorite media genre?
30 | - How many years have you spent coding?
31 | - What book do you highly recommend everyone to read?
32 | - What websites do you use daily to keep yourself up to date with the industry?
33 | - What made you want to join this Discord server?
34 | - How are you?
35 | - What is the best advice you have ever gotten in regards to programming/software?
36 | - What is the most satisfying thing you've done in your life?
37 | - Who is your favorite music composer/producer/singer?
38 | - What is your favorite song?
39 | - What is your favorite video game?
40 | - What are your hobbies other than programming?
41 | - Who is your favorite Writer?
42 | - What is your favorite movie?
43 | - What is your favorite sport?
44 | - What is your favorite fruit?
45 | - What is your favorite juice?
46 | - What is the best scenery you've ever seen?
47 | - What artistic talents do you have?
48 | - What is the tallest building you've entered?
49 | - What is the oldest computer you've ever used?
50 |
--------------------------------------------------------------------------------
/schema.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE IF NOT EXISTS chatbot (
2 | channel_id BIGINT PRIMARY KEY
3 | );
4 |
5 | CREATE TABLE IF NOT EXISTS levels_config (
6 | guild_id BIGINT PRIMARY KEY,
7 | channel_id BIGINT,
8 | ping TEXT
9 | );
10 |
11 | CREATE TABLE IF NOT EXISTS prefix (
12 | user_id BIGINT PRIMARY KEY,
13 | prefixes TEXT
14 | );
15 |
16 | CREATE TABLE IF NOT EXISTS users (
17 | user_id BIGINT PRIMARY KEY,
18 | guild_id BIGINT,
19 | xp INT,
20 | level INT,
21 | name TEXT
22 | );
23 |
24 | CREATE TABLE IF NOT EXISTS channels_config (
25 | channel_id BIGINT PRIMARY KEY,
26 | channel_types TEXT
27 | );
28 |
29 | CREATE TABLE IF NOT EXISTS snipe (
30 | id BIGINT PRIMARY KEY,
31 | channel_id BIGINT,
32 | user_id BIGINT,
33 | message_content TEXT,
34 | create_epoch DATE
35 | );
36 |
37 | CREATE TABLE IF NOT EXISTS edit_snipe (
38 | id BIGINT PRIMARY KEY,
39 | channel_id BIGINT,
40 | user_id BIGINT,
41 | before_content TEXT,
42 | after_content TEXT,
43 | create_epoch DATE
44 | );
45 |
46 | CREATE TABLE IF NOT EXISTS overall_score (
47 | score INT
48 | );
49 |
50 | CREATE TABLE IF NOT EXISTS general_data (
51 | create_epoch DATE,
52 | ping INT,
53 | users INT,
54 | guilds INT,
55 | channels INT,
56 | disnake_version INT
57 | );
58 |
--------------------------------------------------------------------------------
/scripts/create_json_files.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | from pathlib import Path
4 | from typing import IO, Any, Iterable
5 |
6 |
7 | def get_files() -> Iterable[str]:
8 | """"""
9 | for root, _, files in os.walk("./"):
10 | for file in files:
11 | if file.endswith(".py"):
12 | yield os.path.join(root, file)
13 |
14 |
15 | def main():
16 | json_path = Path("./dicts/")
17 | if not json_path.exists():
18 | json_path.mkdir()
19 |
20 | files = get_files()
21 | for file in files:
22 | with open(file, encoding="utf-8") as f:
23 | results: list[str] = re.findall(r'open\("./dicts/(.*?.json)"', f.read())
24 | for r in results:
25 | json_file = json_path / r
26 | if not json_file.exists():
27 | json_file.touch()
28 | if r == "Colour.json":
29 | json_file.write_text('{"colour": "000000"}')
30 | else:
31 | json_file.write_text("{}")
32 | print(f"Created {json_file}")
33 |
34 |
35 | if __name__ == "__main__":
36 | main()
37 |
--------------------------------------------------------------------------------