├── .build_docs.sh
├── .flake8
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yaml
└── workflows
│ └── ci.yml
├── .gitignore
├── .linter.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── __init__.py
├── administration
├── __init__.py
├── permissions
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── roles
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── settings
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
└── sudo
│ ├── __init__.py
│ ├── cog.py
│ ├── documentation.md
│ ├── permissions.py
│ └── translations
│ └── en.yml
├── contributor.py
├── general
├── __init__.py
├── betheprofessional
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── custom_commands
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── discord_bot_token_deleter
│ ├── __init__.py
│ ├── cog.py
│ ├── documentation.md
│ └── translations
│ │ └── en.yml
├── news
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── polls
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── reactionpin
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ │ └── en.yml
├── reactionrole
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── remind_me
│ ├── __init__.py
│ ├── cog.py
│ ├── documentation.md
│ └── translations
│ │ └── en.yml
├── utils
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
└── voice_channel
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── names
│ ├── computer_science_pioneers.txt
│ ├── data_structures.txt
│ ├── elements.txt
│ ├── famous_mathematicians.txt
│ ├── fields_medalists.txt
│ ├── greek_alphabet.txt
│ ├── greek_alphabet_names.txt
│ ├── greek_gods_olymp.txt
│ ├── hex.txt
│ ├── intel_codenames.txt
│ ├── nobelprize_physics.txt
│ ├── np_complete_problems.txt
│ ├── programming_languages.txt
│ ├── roman_gods_olymp.txt
│ ├── spaces.txt
│ └── turing_award.txt
│ ├── permissions.py
│ └── translations
│ └── en.yml
├── information
├── __init__.py
├── bot_info
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ └── translations
│ │ └── en.yml
├── heartbeat
│ ├── __init__.py
│ ├── cog.py
│ ├── documentation.md
│ └── translations
│ │ └── en.yml
├── help
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ └── translations
│ │ └── en.yml
├── inactivity
│ ├── __init__.py
│ ├── cog.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ │ └── en.yml
├── server_info
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ └── translations
│ │ └── en.yml
└── user_info
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ └── en.yml
├── integrations
├── __init__.py
├── adventofcode
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ │ └── en.yml
├── cleverbot
│ ├── __init__.py
│ ├── api.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── python_docs
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ └── translations
│ │ └── en.yml
├── reddit
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ │ └── en.yml
└── run_code
│ ├── __init__.py
│ ├── api.py
│ ├── cog.py
│ ├── documentation.md
│ └── translations
│ └── en.yml
├── moderation
├── __init__.py
├── autoclear
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── automod
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ │ └── en.yml
├── autorole
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── content_filter
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── invites
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── logging
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ │ └── en.yml
├── mediaonly
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── message
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── mod
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── role_notifications
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── spam_detection
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ │ └── en.yml
├── threads
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
├── user_notes
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ └── translations
│ │ └── en.yml
└── verification
│ ├── __init__.py
│ ├── cog.py
│ ├── colors.py
│ ├── documentation.md
│ ├── models.py
│ ├── permissions.py
│ ├── settings.py
│ └── translations
│ └── en.yml
├── poetry.lock
├── pre-commit.sh
├── pubsub.md
├── pubsub.py
├── pyproject.toml
└── translations.py
/.build_docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | repo=$(mktemp -d)
4 | git clone --recursive https://github.com/PyDrocsid/documentation.git $repo
5 | rm -rf $repo/cogs
6 | mkdir $repo/cogs
7 | cp -r * $repo/cogs/
8 | pushd $repo
9 |
10 | ./pages_build.sh
11 |
12 | popd
13 | mv $repo/site .
14 | rm -rf $repo
15 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | max-line-length = 120
3 | ignore = C812,C813,C815,C816,Q000,RST210,RST213,RST215,RST304,W503
4 | extend-ignore = D,E800,S311,WPS
5 | extend-exclude = .venv
6 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @Defelo
2 | translations/ @PyDrocsid/code-reviewer @PyDrocsid/text-reviewer
3 | /custom_commands/ @PyDrocsid/code-reviewer @PyDrocsid/text-reviewer
4 | *.md @PyDrocsid/text-reviewer
5 | *.py @PyDrocsid/code-reviewer
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. 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 | **Your Configuration (please complete the following information):**
27 | - Bot Information (name, link to repository, exact version):
28 | - Hosting Method (with/without Pipenv/Docker):
29 | - Database (MySQL/MariaDB/PostreSQL):
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. E.g. "I am always frustrated when [...]"
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Description**
2 | Provide a description of what your changes do.
3 |
4 | **Issue**
5 | Closes #XX
6 |
7 | **Additional Notes**
8 | Add any other context about the pull request here.
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 |
8 | updates:
9 | - package-ecosystem: "github-actions"
10 | # Workflow files stored in the default location of `.github/workflows`
11 | directory: "/"
12 | schedule:
13 | interval: "daily"
14 | reviewers:
15 | - "Defelo"
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [](https://github.com/PyDrocsid/cogs/actions/workflows/ci.yml)
6 | [](https://github.com/psf/black)
7 | [](https://codeclimate.com/github/PyDrocsid/cogs/maintainability)
8 | [](https://pydrocsid.defelo.de/discord)
9 | [](https://pydrocsid.defelo.de/matrix)
10 |
11 |
12 |
13 |
14 | # PyDrocsid Cog Library
15 |
16 | The official cog library used by PyDrocsid bots
17 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | from . import translations as _ # noqa
2 |
--------------------------------------------------------------------------------
/administration/__init__.py:
--------------------------------------------------------------------------------
1 | from .permissions import PermissionsCog
2 | from .roles import RolesCog
3 | from .settings import SettingsCog
4 | from .sudo import SudoCog
5 |
6 |
7 | __all__ = ["PermissionsCog", "RolesCog", "SettingsCog", "SudoCog"]
8 |
--------------------------------------------------------------------------------
/administration/permissions/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import PermissionsCog
2 |
3 |
4 | __all__ = ["PermissionsCog"]
5 |
--------------------------------------------------------------------------------
/administration/permissions/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Permissions = MaterialColors.blue["a700"]
6 |
--------------------------------------------------------------------------------
/administration/permissions/documentation.md:
--------------------------------------------------------------------------------
1 | # Permissions
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/administration/permissions/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class PermissionsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.permissions.permissions[self.name]
11 |
12 | view_own = auto()
13 | view_all = auto()
14 | manage = auto()
15 |
--------------------------------------------------------------------------------
/administration/permissions/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | view_own: view own permissions
3 | view_all: view all permissions
4 | manage: manage permissions
5 |
6 | commands:
7 | permissions: manage bot permissions
8 | permissions_list: list all permissions
9 | permissions_my: list all permissions granted to the user
10 | permissions_set: configure bot permissions
11 | permissions_permission_levels: list all permission levels
12 |
13 | no_permissions: No priviliges granted.
14 | invalid_permission_level: Invalid permission level.
15 | invalid_permission: Invalid permission.
16 | cannot_manage_permission_level: You are not allowed to manage this permission level.
17 | permission_set: "Permission `{}` has been granted to `>= {}` :white_check_mark:"
18 | log_permission_set: "**Permission** `{}` has been **granted** to `>= {}` :white_check_mark:"
19 | permissions_title: Permissions
20 | my_permissions_title: My Permissions
21 | permission_levels: Permission Levels
22 | aliases: "Aliases: {}"
23 | level: "Level: `{}`"
24 | granted_by: "Granted by:"
25 |
--------------------------------------------------------------------------------
/administration/roles/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import RolesCog
2 |
3 |
4 | __all__ = ["RolesCog"]
5 |
--------------------------------------------------------------------------------
/administration/roles/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Roles = MaterialColors.blue["a700"]
6 | MissingPermissions = MaterialColors.yellow["a700"]
7 |
--------------------------------------------------------------------------------
/administration/roles/documentation.md:
--------------------------------------------------------------------------------
1 | # Roles
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/administration/roles/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Union
4 |
5 | from sqlalchemy import BigInteger, Boolean, Column
6 |
7 | from PyDrocsid.database import Base, db, select
8 |
9 |
10 | class RoleAuth(Base):
11 | __tablename__ = "role_auth"
12 |
13 | source: Union[Column, int] = Column(BigInteger, primary_key=True)
14 | target: Union[Column, int] = Column(BigInteger, primary_key=True)
15 | perma_allowed: Union[Column, bool] = Column(Boolean())
16 |
17 | @staticmethod
18 | async def add(source: int, target: int, perma_allowed: bool):
19 | await db.add(RoleAuth(source=source, target=target, perma_allowed=perma_allowed))
20 |
21 | @staticmethod
22 | async def check(source: int, target: int) -> bool:
23 | return await db.exists(select(RoleAuth).filter_by(source=source, target=target))
24 |
25 |
26 | class PermaRole(Base):
27 | __tablename__ = "perma_role"
28 |
29 | member_id: Union[Column, int] = Column(BigInteger, primary_key=True)
30 | role_id: Union[Column, int] = Column(BigInteger, primary_key=True)
31 |
32 | @staticmethod
33 | async def add(member_id: int, role_id: int):
34 | await db.add(PermaRole(member_id=member_id, role_id=role_id))
35 |
--------------------------------------------------------------------------------
/administration/roles/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class RolesPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.roles.permissions[self.name]
11 |
12 | config_read = auto()
13 | config_write = auto()
14 | auth_read = auto()
15 | auth_write = auto()
16 | list_members = auto()
17 | roles_clone = auto()
18 |
--------------------------------------------------------------------------------
/administration/roles/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | config_read: read role configuration
3 | config_write: write role configuration
4 | auth_read: read role assignment permissions
5 | auth_write: write role assignment permissions
6 | list_members: list role members
7 | roles_clone: clones existing roles
8 |
9 | commands:
10 | roles: manage roles
11 | roles_config: role configuration
12 | roles_auth: configure role assignment authorizations
13 | roles_auth_add: add a new role assignment authorization
14 | roles_auth_remove: remove a role assignment authorization
15 | roles_add: assign a role to a member
16 | roles_clone: clone an existing role
17 | roles_remove: remove a role from a member
18 | roles_perma_add: assign a role permanently to a member
19 | roles_perma_unset: remove the perma flag from a member role
20 | roles_perma_remove: remove a perma role from a member
21 | roles_list: list all members with a specific role
22 | roles_perma_list: list all permanent role assignments
23 |
24 | roles: Roles
25 | configure_role: configure {} role
26 | role_not_set: ":x: Role not set."
27 | role_set: "Role has been set successfully. :white_check_mark:"
28 | log_role_set: "**{} role** has been **set to** {} ({})"
29 |
30 | role_auth: Role Assignment Authorizations
31 | no_role_auth: No role assignment authorizations have been created yet.
32 | role_auths: Role Authorizations
33 | user_auths: User Authorizations
34 | no_auth_for_bots: You cannot authorize bots to assign roles.
35 | role_auth_already_exists: Role assignment authorization already exists.
36 | role_auth_created: Role assignment authorization has been created successfully.
37 | log_role_auth_created: Role assignment authorization `@{}` -> `@{}` has been created.
38 | role_auth_not_found: Role assignment authorization does not exist.
39 | role_auth_removed: Role assignment authorization has been removed successfully.
40 | log_role_auth_removed: Role assignment authorization `@{}` -> `@{}` has been removed.
41 | role_not_authorized: You are not allowed to manage this role.
42 | member_list: Member List
43 | member_list_cnt:
44 | one: "Member List of @{} ({cnt} Member)"
45 | many: "Member List of @{} ({cnt} Members)"
46 | member_list_line: ":small_orange_diamond: {} ({})"
47 | no_members: No member was found with this role.
48 | role_already_assigned: Role has already been assigned to this member.
49 | role_not_assigned: Role has not yet been assigned to this member.
50 | added_perma_role: Role {} has been permanently assigned to {} ({}).
51 | removed_perma_role: Perma role {} has been removed from {} ({}).
52 | cannot_remove_perma: This role has been permanently assigned to this member and can therefore only be removed by using the `{}roles perma_remove` command.
53 | perma_reassigned: Perma role {} has been reassigned to {} ({}). If you want to remove this role, please use the `{}roles perma_remove` command.
54 | no_perma_roles: No permanent role assignments.
55 | perma_roles: Permanent Role Assignments
56 | could_not_reassign: Could not reassign perma role {} to {} ({}).
57 | clone_no_permission: I cannot clone this role because I do not have `manage_roles` permission on this server.
58 | failed_to_clone_role_permissions: ":warning: Could not clone following permissions:"
59 |
--------------------------------------------------------------------------------
/administration/settings/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import SettingsCog
2 |
3 |
4 | __all__ = ["SettingsCog"]
5 |
--------------------------------------------------------------------------------
/administration/settings/cog.py:
--------------------------------------------------------------------------------
1 | import string
2 |
3 | from discord import Embed
4 | from discord.ext import commands
5 | from discord.ext.commands import CommandError, Context, guild_only
6 |
7 | from PyDrocsid.cog import Cog
8 | from PyDrocsid.command import docs, reply
9 | from PyDrocsid.prefix import set_prefix
10 | from PyDrocsid.translations import t
11 |
12 | from .colors import Colors
13 | from .permissions import SettingsPermission
14 | from ...contributor import Contributor
15 | from ...pubsub import send_to_changelog
16 |
17 |
18 | tg = t.g
19 | t = t.settings
20 |
21 |
22 | class SettingsCog(Cog, name="Settings"):
23 | CONTRIBUTORS = [Contributor.Defelo]
24 |
25 | @commands.command(name="prefix")
26 | @SettingsPermission.change_prefix.check
27 | @guild_only()
28 | @docs(t.commands.change_prefix)
29 | async def change_prefix(self, ctx: Context, *, new_prefix: str):
30 | if not 0 < len(new_prefix) <= 16:
31 | raise CommandError(t.invalid_prefix_length)
32 |
33 | valid_chars = set(string.ascii_letters + string.digits + string.punctuation) - {"`"}
34 | if any(c not in valid_chars for c in new_prefix):
35 | raise CommandError(t.prefix_invalid_chars)
36 |
37 | await set_prefix(new_prefix)
38 | embed = Embed(title=t.prefix, description=t.prefix_updated, colour=Colors.prefix)
39 | await reply(ctx, embed=embed)
40 | await send_to_changelog(ctx.guild, t.log_prefix_updated(new_prefix))
41 |
--------------------------------------------------------------------------------
/administration/settings/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | prefix = MaterialColors.indigo
6 |
--------------------------------------------------------------------------------
/administration/settings/documentation.md:
--------------------------------------------------------------------------------
1 | # Settings
2 |
3 | Contains commands to change various settings of the bot.
4 |
5 |
6 | ## `prefix`
7 |
8 | Changes the bot prefix. Any message containing a command has to start with this prefix, directly followed by the command itself. When messaging the bot directly you can (but don't have to) omit the prefix.
9 |
10 | ```css
11 | .prefix
12 | ```
13 |
14 | Arguments:
15 |
16 | | Argument | Required | Description |
17 | |:------------:|:-------------------------:|:---------------|
18 | | `new_prefix` | :fontawesome-solid-check: | The new prefix |
19 |
20 | Required Permissions:
21 |
22 | - `settings.change_prefix`
23 |
24 | !!! note
25 | - The prefix cannot contain more than 16 characters.
26 | - Only alphanumeric and punctuation characters are allowed.
27 |
--------------------------------------------------------------------------------
/administration/settings/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class SettingsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.settings.permissions[self.name]
11 |
12 | change_prefix = auto()
13 |
--------------------------------------------------------------------------------
/administration/settings/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | change_prefix: change bot prefix
3 |
4 | commands:
5 | change_prefix: change the bot prefix
6 |
7 | prefix: Prefix
8 | invalid_prefix_length: Length of prefix must be between 1 and 16
9 | prefix_invalid_chars: Prefix contains invalid characters.
10 | prefix_updated: "Prefix has been updated. :white_check_mark:"
11 | log_prefix_updated: "**Bot prefix** has been **changed** to `{}`"
12 |
--------------------------------------------------------------------------------
/administration/sudo/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import SudoCog
2 |
3 |
4 | __all__ = ["SudoCog"]
5 |
--------------------------------------------------------------------------------
/administration/sudo/cog.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from discord import Message, TextChannel
4 | from discord.ext import commands
5 | from discord.ext.commands import CheckFailure, Context, check
6 |
7 | from PyDrocsid.cog import Cog
8 | from PyDrocsid.config import Config
9 | from PyDrocsid.emojis import name_to_emoji
10 | from PyDrocsid.environment import OWNER_ID
11 | from PyDrocsid.events import call_event_handlers
12 | from PyDrocsid.permission import permission_override
13 | from PyDrocsid.redis import redis
14 | from PyDrocsid.translations import t
15 |
16 | from .permissions import SudoPermission
17 | from ...contributor import Contributor
18 |
19 |
20 | tg = t.g
21 | t = t.sudo
22 |
23 |
24 | @check
25 | def is_sudoer(ctx: Context) -> bool:
26 | if ctx.author.id != OWNER_ID:
27 | raise CheckFailure(t.not_in_sudoers_file(ctx.author.mention))
28 |
29 | return True
30 |
31 |
32 | class SudoCog(Cog, name="Sudo"):
33 | CONTRIBUTORS = [Contributor.Defelo, Contributor.TNT2k]
34 |
35 | def __init__(self):
36 | super().__init__()
37 |
38 | self.sudo_cache: dict[TextChannel, Message] = {}
39 |
40 | @staticmethod
41 | def prepare():
42 | return bool(OWNER_ID)
43 |
44 | async def on_command_error(self, ctx: Context, _):
45 | if ctx.author.id == OWNER_ID:
46 | self.sudo_cache[ctx.channel] = ctx.message
47 |
48 | @commands.command(hidden=True)
49 | @is_sudoer
50 | async def sudo(self, ctx: Context, *, cmd: str):
51 | message: Message = ctx.message
52 | message.content = ctx.prefix + cmd
53 |
54 | if cmd == "!!" and ctx.channel in self.sudo_cache:
55 | message.content = self.sudo_cache.pop(ctx.channel).content
56 |
57 | permission_override.set(Config.PERMISSION_LEVELS.max())
58 | await self.bot.process_commands(message)
59 |
60 | @commands.command()
61 | @SudoPermission.clear_cache.check
62 | async def clear_cache(self, ctx: Context):
63 | await redis.flushdb()
64 | await ctx.message.add_reaction(name_to_emoji["white_check_mark"])
65 |
66 | @commands.command()
67 | @SudoPermission.reload.check
68 | async def reload(self, ctx: Context):
69 | await call_event_handlers("ready")
70 | await ctx.message.add_reaction(name_to_emoji["white_check_mark"])
71 |
72 | @commands.command()
73 | @SudoPermission.stop.check
74 | async def stop(self, ctx: Context):
75 | await ctx.message.add_reaction(name_to_emoji["white_check_mark"])
76 | await self.bot.close()
77 |
78 | @commands.command()
79 | @SudoPermission.kill.check
80 | async def kill(self, ctx: Context):
81 | await ctx.message.add_reaction(name_to_emoji["white_check_mark"])
82 | sys.exit(1)
83 |
--------------------------------------------------------------------------------
/administration/sudo/documentation.md:
--------------------------------------------------------------------------------
1 | # Sudo
2 |
3 | Contains the `.sudo` command, as well as some other commands used to maintain the bot instance.
4 |
5 |
6 | ## `sudo`
7 |
8 | Allows a specific user to execute any command even without having the necessary permission level by temporarily granting the user the highest permission level (similar to the `sudo` command on Linux).
9 |
10 | ```css
11 | .sudo
12 | ```
13 |
14 | Arguments:
15 |
16 | | Argument | Required | Description |
17 | |:---------:|:-------------------------:|:--------------------------------------------:|
18 | | `command` | :fontawesome-solid-check: | The command to execute with owner privileges |
19 |
20 | !!! note
21 | To use this command your user ID has to match the value of the `OWNER_ID` environment variable. If this environment variable is not set, the Sudo cog is disabled.
22 |
23 | !!! Hint
24 | If you have run a command without having the required permission level, you can use `.sudo !!` to rerun this command with `owner` privileges.
25 |
26 |
27 | ## Maintenance Commands
28 |
29 | !!! note
30 | These commands do not necessarily have to be executed with the `.sudo` command. Theoretically, the required permission levels can be changed to any other permission level, so that users who are not allowed to execute the `.sudo` command can also use these maintenance commands. However, it is recommended to only allow trusted users to use these commands.
31 |
32 |
33 | ### `clear_cache`
34 |
35 | Clears the redis cache by executing the `FLUSHDB` command.
36 |
37 | ```css
38 | .clear_cache
39 | ```
40 |
41 | Required Permissions:
42 |
43 | - `sudo.clear_cache`
44 |
45 |
46 | ### `reload`
47 |
48 | Reloads the bot by refiring all startup functions.
49 |
50 | ```css
51 | .reload
52 | ```
53 |
54 | Required Permissions:
55 |
56 | - `sudo.reload`
57 |
58 |
59 | ### `stop`
60 |
61 | Stops the running bot instance gracefully.
62 |
63 | ```css
64 | .stop
65 | ```
66 |
67 | Required Permissions:
68 |
69 | - `sudo.stop`
70 |
71 |
72 | ### `kill`
73 |
74 | Kills the running bot instance.
75 |
76 | ```css
77 | .kill
78 | ```
79 |
80 | Required Permissions:
81 |
82 | - `sudo.kill`
83 |
--------------------------------------------------------------------------------
/administration/sudo/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class SudoPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.sudo.permissions[self.name]
11 |
12 | clear_cache = auto()
13 | reload = auto()
14 | stop = auto()
15 | kill = auto()
16 |
--------------------------------------------------------------------------------
/administration/sudo/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | clear_cache: clear redis cache
3 | reload: reload the bot
4 | stop: stop the bot
5 | kill: kill the bot
6 |
7 | not_in_sudoers_file: "{} is not in the sudoers file. This incident will be reported."
8 |
--------------------------------------------------------------------------------
/contributor.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.config import Contributor as ContributorBase
2 |
3 |
4 | class Contributor(ContributorBase):
5 | Scriptim = (137906177487929344, "MDQ6VXNlcjEzMDAzOTI0")
6 | Florian = (541341790176018432, "MDQ6VXNlcjU0Njg5Mzc0")
7 | AdriBloober = (330148908531580928, "MDQ6VXNlcjQ1MzIxMTA3")
8 | pohlium = (None, "MDQ6VXNlcjI0NDgwNTc0")
9 | Anorak = (202370816384565248, "MDQ6VXNlcjMyNDg3NzYw")
10 | LoC = (519559136237715486, "MDQ6VXNlcjY0MDk1MjUz")
11 | Tristan = (277069925678317568, "MDQ6VXNlcjQ1MzMwNjY3")
12 | Tert0 = (621330363167539210, "MDQ6VXNlcjYyMDM2NDY0")
13 | MarcelCoding = (398895973490884608, "MDQ6VXNlcjM0ODE5NTI0")
14 | Infinity = (846009958062358548, "MDQ6VXNlcjgzODgzODQ5")
15 | Felux = (206815202375761920, "MDQ6VXNlcjMyNzQ5NzE0")
16 | hackandcode = (301823559737671680, "MDQ6VXNlcjI3Njc5Mjc0")
17 |
--------------------------------------------------------------------------------
/general/__init__.py:
--------------------------------------------------------------------------------
1 | from .betheprofessional import BeTheProfessionalCog
2 | from .custom_commands import CustomCommandsCog
3 | from .discord_bot_token_deleter import DiscordBotTokenDeleterCog
4 | from .news import NewsCog
5 | from .polls import PollsCog
6 | from .reactionpin import ReactionPinCog
7 | from .reactionrole import ReactionRoleCog
8 | from .remind_me import RemindMeCog
9 | from .utils import UtilsCog
10 | from .voice_channel import VoiceChannelCog
11 |
12 |
13 | __all__ = [
14 | "BeTheProfessionalCog",
15 | "CustomCommandsCog",
16 | "DiscordBotTokenDeleterCog",
17 | "NewsCog",
18 | "PollsCog",
19 | "ReactionPinCog",
20 | "ReactionRoleCog",
21 | "RemindMeCog",
22 | "UtilsCog",
23 | "VoiceChannelCog",
24 | ]
25 |
--------------------------------------------------------------------------------
/general/betheprofessional/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import BeTheProfessionalCog
2 |
3 |
4 | __all__ = ["BeTheProfessionalCog"]
5 |
--------------------------------------------------------------------------------
/general/betheprofessional/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | BeTheProfessional = MaterialColors.yellow["a200"]
6 |
--------------------------------------------------------------------------------
/general/betheprofessional/documentation.md:
--------------------------------------------------------------------------------
1 | # BeTheProfessional
2 |
3 | Contains a system for self-assignable roles (further referred to as `topics`).
4 |
5 |
6 | ## `?` (list topics)
7 |
8 | Lists all available topics.
9 |
10 | ```css
11 | .?
12 | ```
13 |
14 |
15 | ## `+` (assign topics)
16 |
17 | Assigns the user the specified topics.
18 |
19 | ```css
20 | .+
21 | ```
22 |
23 | Arguments:
24 |
25 | | Argument | Required | Description |
26 | |:--------:|:-------------------------:|:---------------------------------------------|
27 | | `topics` | :fontawesome-solid-check: | One or more topics (separated by `,` or `;`) |
28 |
29 |
30 | ## `-` (unassign topics)
31 |
32 | Unassigns the user the specified topics.
33 |
34 | ```css
35 | .-
36 | ```
37 |
38 | Arguments:
39 |
40 | | Argument | Required | Description |
41 | |:--------:|:-------------------------:|:---------------------------------------------|
42 | | `topics` | :fontawesome-solid-check: | One or more topics (separated by `,` or `;`) |
43 |
44 | !!! hint
45 | You can use `.- *` to remove all topics at once.
46 |
47 |
48 | ## `*` (register topics)
49 |
50 | Adds new topics to the list of available topics. For each topic a new role will be created if there is no role with the same name yet (case insensitive).
51 |
52 | ```css
53 | .*
54 | ```
55 |
56 | Arguments:
57 |
58 | | Argument | Required | Description |
59 | |:--------:|:-------------------------:|:---------------------------------------------|
60 | | `topics` | :fontawesome-solid-check: | One or more topics (separated by `,` or `;`) |
61 |
62 | Required Permissions:
63 |
64 | - `betheprofessional.manage`
65 |
66 |
67 | ## `/` (delete topics)
68 |
69 | Removes topics from the list of available topics and deletes the associated roles.
70 |
71 | ```css
72 | ./
73 | ```
74 |
75 | Arguments:
76 |
77 | | Argument | Required | Description |
78 | |:--------:|:-------------------------:|:---------------------------------------------|
79 | | `topics` | :fontawesome-solid-check: | One or more topics (separated by `,` or `;`) |
80 |
81 | Required Permissions:
82 |
83 | - `betheprofessional.manage`
84 |
85 |
86 | ## `%` (unregister topics)
87 |
88 | Unregisters topics without deleting the associated roles.
89 |
90 | ```css
91 | .%
92 | ```
93 |
94 | Arguments:
95 |
96 | | Argument | Required | Description |
97 | |:--------:|:-------------------------:|:---------------------------------------------|
98 | | `topics` | :fontawesome-solid-check: | One or more topics (separated by `,` or `;`) |
99 |
100 | Required Permissions:
101 |
102 | - `betheprofessional.manage`
103 |
--------------------------------------------------------------------------------
/general/betheprofessional/models.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy import BigInteger, Column
4 |
5 | from PyDrocsid.database import Base, db
6 |
7 |
8 | class BTPRole(Base):
9 | __tablename__ = "btp_role"
10 |
11 | role_id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
12 |
13 | @staticmethod
14 | async def create(role_id: int) -> "BTPRole":
15 | row = BTPRole(role_id=role_id)
16 | await db.add(row)
17 | return row
18 |
--------------------------------------------------------------------------------
/general/betheprofessional/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class BeTheProfessionalPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.betheprofessional.permissions[self.name]
11 |
12 | manage = auto()
13 |
--------------------------------------------------------------------------------
/general/betheprofessional/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | manage: manage betheprofessional roles
3 |
4 | # betheprofessional
5 | betheprofessional: BeTheProfessional
6 | youre_not_the_first_one: "Topic `{}` not found.\nYou're not the first one to try this, {}"
7 | topic_not_found: Topic `{}` not found.
8 | topic_not_found_did_you_mean: Topic `{}` not found. Did you mean `{}`?
9 | available_topics_header: "Available Topics"
10 | no_topics_registered: No topics have been registered yet.
11 |
12 | topics_added:
13 | zero: No topic has been added.
14 | one: "Topic has been added successfully. :white_check_mark:"
15 | many: "{cnt} topics have been added successfully. :white_check_mark:"
16 |
17 | topics_removed:
18 | zero: No topic has been removed.
19 | one: "Topic has been removed successfully. :white_check_mark:"
20 | many: "{cnt} topics have been removed successfully. :white_check_mark:"
21 |
22 | topic_invalid_chars: Topic name `{}` contains invalid characters.
23 | topic_too_long: Topic name `{}` is too long.
24 | topic_already_registered: Topic `{}` has already been registered.
25 | topic_not_registered: Topic `{}` has not been registered.
26 | topic_not_registered_too_high: Topic could not be registered because `@{}` is higher than `@{}`.
27 | topic_not_registered_managed_role: Topic could not be registered because `@{}` cannot be assigned manually.
28 |
29 | topics_registered:
30 | one: "Topic has been registered successfully. :white_check_mark:"
31 | many: "{cnt} topics have been registered successfully. :white_check_mark:"
32 | log_topics_registered:
33 | one: "The new **topic** {topics} has been **registered**."
34 | many: "{cnt} **topics** have been **registered**: {topics}"
35 |
36 | topics_unregistered:
37 | one: "Topic has been deleted successfully. :white_check_mark:"
38 | many: "{cnt} topics have been deleted successfully. :white_check_mark:"
39 | log_topics_unregistered:
40 | one: "The **topic** {topics} has been **removed**."
41 | many: "{cnt} **topics** have been **removed**: {topics}"
42 |
--------------------------------------------------------------------------------
/general/custom_commands/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import CustomCommandsCog
2 |
3 |
4 | __all__ = ["CustomCommandsCog"]
5 |
--------------------------------------------------------------------------------
/general/custom_commands/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | CustomCommands = MaterialColors.teal[400]
6 |
--------------------------------------------------------------------------------
/general/custom_commands/documentation.md:
--------------------------------------------------------------------------------
1 | # Custom Commands
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/general/custom_commands/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Optional, Union
4 | from uuid import uuid4
5 |
6 | from sqlalchemy import BigInteger, Boolean, Column, ForeignKey, Integer, String, Text
7 | from sqlalchemy.orm import relationship
8 |
9 | from PyDrocsid.database import Base, db
10 | from PyDrocsid.permission import BasePermissionLevel
11 |
12 |
13 | class CustomCommand(Base):
14 | __tablename__ = "custom_command"
15 |
16 | id: Union[Column, str] = Column(String(36), primary_key=True, unique=True)
17 | name: Union[Column, str] = Column(Text, unique=True)
18 | description: Union[Column, str] = Column(Text)
19 | disabled: Union[Column, bool] = Column(Boolean)
20 | channel_parameter: Union[Column, bool] = Column(Boolean)
21 | channel_id: Union[Column, Optional[int]] = Column(BigInteger, nullable=True)
22 | delete_command: Union[Column, bool] = Column(Boolean)
23 | permission_level: Union[Column, bool] = Column(Integer)
24 | requires_confirmation: Union[Column, bool] = Column(Boolean)
25 | user_parameter: Union[Column, bool] = Column(Boolean)
26 | data: Union[Column, str] = Column(Text)
27 | aliases: list[Alias] = relationship("Alias", back_populates="command", cascade="all, delete", order_by="Alias.name")
28 |
29 | @staticmethod
30 | async def create(name: str, data: str, disabled: bool, permission_level: BasePermissionLevel) -> CustomCommand:
31 | row = CustomCommand(
32 | id=str(uuid4()),
33 | name=name,
34 | description=None,
35 | disabled=disabled,
36 | channel_parameter=False,
37 | channel_id=None,
38 | delete_command=False,
39 | permission_level=permission_level.level,
40 | requires_confirmation=False,
41 | user_parameter=True,
42 | data=data,
43 | )
44 | await db.add(row)
45 | return row
46 |
47 | @property
48 | def alias_names(self) -> list[str]:
49 | return [alias.name for alias in self.aliases]
50 |
51 | async def add_alias(self, name: str) -> Alias:
52 | alias = Alias(id=str(uuid4()), name=name, command_id=self.id)
53 | self.aliases.append(alias)
54 | await db.add(alias)
55 | return alias
56 |
57 |
58 | class Alias(Base):
59 | __tablename__ = "custom_command_alias"
60 |
61 | id: Union[Column, str] = Column(String(36), primary_key=True, unique=True)
62 | name: Union[Column, str] = Column(Text, unique=True)
63 | command_id: Union[Column, str] = Column(String(36), ForeignKey("custom_command.id"))
64 | command: CustomCommand = relationship("CustomCommand", back_populates="aliases")
65 |
--------------------------------------------------------------------------------
/general/custom_commands/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class CustomCommandsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.custom_commands.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/general/discord_bot_token_deleter/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import DiscordBotTokenDeleterCog
2 |
3 |
4 | __all__ = ["DiscordBotTokenDeleterCog"]
5 |
--------------------------------------------------------------------------------
/general/discord_bot_token_deleter/cog.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import binascii
3 | import re
4 |
5 | from aiohttp import ClientSession
6 | from discord import Embed, Forbidden, Message
7 |
8 | from PyDrocsid.cog import Cog
9 | from PyDrocsid.material_colors import MaterialColors
10 | from PyDrocsid.translations import t
11 |
12 | from ...contributor import Contributor
13 | from ...pubsub import send_alert
14 |
15 |
16 | tg = t.g
17 | t = t.discord_bot_token_deleter
18 |
19 |
20 | class DiscordBotTokenDeleterCog(Cog, name="Discord Bot Token Deleter"):
21 | CONTRIBUTORS = [Contributor.Tert0, Contributor.Defelo]
22 | RE_DC_TOKEN = re.compile(r"([A-Za-z\d\-_]+)\.[A-Za-z\d\-_]+\.[A-Za-z\d\-_]+")
23 |
24 | async def on_message(self, message: Message):
25 | """Delete a message if it contains a Discord bot token"""
26 |
27 | if message.author.id == self.bot.user.id or not message.guild:
28 | return
29 |
30 | for match in self.RE_DC_TOKEN.finditer(message.content):
31 | try:
32 | if not base64.urlsafe_b64decode(match.group(1)).isdigit():
33 | continue
34 | except binascii.Error:
35 | continue
36 |
37 | async with ClientSession() as session, session.get(
38 | "https://discord.com/api/users/@me", headers={"Authorization": f"Bot {match.group(0)}"}
39 | ) as response:
40 | if response.ok:
41 | break
42 | else:
43 | return
44 |
45 | embed = Embed(title=t.title, colour=MaterialColors.bluegrey, description=t.description)
46 | await message.channel.send(message.author.mention, embed=embed)
47 | try:
48 | await message.delete()
49 | except Forbidden:
50 | await send_alert(message.guild, t.not_deleted(message.jump_url, message.channel.mention))
51 |
--------------------------------------------------------------------------------
/general/discord_bot_token_deleter/documentation.md:
--------------------------------------------------------------------------------
1 | # Discord Bot Token Deleter
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/general/discord_bot_token_deleter/translations/en.yml:
--------------------------------------------------------------------------------
1 | title: Discord Bot Token Deleter
2 | description: >
3 | You have sent a message with a Discord bot token into this channel.
4 | I deleted this message because other people can use this token to control your bot!
5 | You can regenerate the token in the [Discord Developer Portal](https://discord.com/developers/applications):
6 | - Select the application
7 | - Click on "Bot" in the side menu
8 | - Click on "Regenerate" to regenerate the token
9 | not_deleted: >
10 | [This message]({}) contains a Discord bot token but I could not delete it because I don't have `manage_messages` permission in {}.
11 |
--------------------------------------------------------------------------------
/general/news/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import NewsCog
2 |
3 |
4 | __all__ = ["NewsCog"]
5 |
--------------------------------------------------------------------------------
/general/news/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | News = MaterialColors.orange
6 |
--------------------------------------------------------------------------------
/general/news/documentation.md:
--------------------------------------------------------------------------------
1 | # News
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/general/news/models.py:
--------------------------------------------------------------------------------
1 | from typing import Optional, Union
2 |
3 | from sqlalchemy import BigInteger, Column
4 |
5 | from PyDrocsid.database import Base, db
6 |
7 |
8 | class NewsAuthorization(Base):
9 | __tablename__ = "news_authorization"
10 |
11 | user_id: Union[Column, int] = Column(BigInteger, primary_key=True)
12 | channel_id: Union[Column, int] = Column(BigInteger, primary_key=True)
13 | notification_role_id: Union[Column, int] = Column(BigInteger)
14 |
15 | @staticmethod
16 | async def create(user_id: int, channel_id: int, notification_role_id: Optional[int]) -> "NewsAuthorization":
17 | row = NewsAuthorization(user_id=user_id, channel_id=channel_id, notification_role_id=notification_role_id)
18 | await db.add(row)
19 | return row
20 |
--------------------------------------------------------------------------------
/general/news/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class NewsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.news.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/general/news/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: read news authorizations
3 | write: write news authorizations
4 |
5 | news: News
6 | news_already_authorized: User is already authorized to send news in this channel.
7 | news_authorized: "User has been authorized to send news in this channel. :white_check_mark:"
8 | log_news_authorized: "User {} has been **authorized** to **send news** in {}."
9 | news_not_authorized: User is not authorized to send news in this channel.
10 | news_unauthorized: "User has been unauthorized to send news in this channel. :white_check_mark:"
11 | log_news_unauthorized: "User {} has been **unauthorized** to **send news** in {}."
12 | no_news_authorizations: No user has been authorized to send news yet.
13 | news_not_added_no_permissions: >
14 | User could not be authorized because I don't have `send_messages` permission in this channel.
15 | news_you_are_not_authorized: You are not authorized to send news in this channel.
16 | sent_by: Sent by @{} ({})
17 | send_message: Now send me the message!
18 | msg_could_not_be_sent: Message could not be sent.
19 | msg_sent: "Message has been sent successfully. :white_check_mark:"
20 |
--------------------------------------------------------------------------------
/general/polls/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import PollsCog
2 |
3 |
4 | __all__ = ["PollsCog"]
5 |
--------------------------------------------------------------------------------
/general/polls/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Polls = MaterialColors.orange[800]
6 |
--------------------------------------------------------------------------------
/general/polls/documentation.md:
--------------------------------------------------------------------------------
1 | # Polls
2 |
3 | Contains commands for simple "yes/no" polls, multiple choice polls and team polls.
4 |
5 |
6 | ## `yesno`
7 |
8 | Creates a "yes/no" poll by adding :thumbsup: and :thumbsdown: reactions to the message (pictures and other files work, too). You can also specify a different message to which the reactions should be added.
9 |
10 | ```css
11 | .[yesno|yn] [content|message]
12 | ```
13 |
14 | Arguments:
15 |
16 | | Argument | Required | Description |
17 | |:---------:|:--------:|:--------------------------------|
18 | | `content` | | The message content |
19 | | `message` | | The link to a different message |
20 |
21 |
22 | ## `poll`
23 |
24 | Creates a poll with 1 to a maximum of 19 options.
25 |
26 | ```css
27 | .[poll|vote]
28 | [emoji1]
29 | [emojiX] [optionX]
30 | ```
31 |
32 | Arguments:
33 |
34 | | Argument | Required | Description |
35 | |:----------:|:-------------------------:|:--------------------------------|
36 | | `question` | :fontawesome-solid-check: | The poll topic/question |
37 | | `emojiX` | | The reaction emote for option X |
38 | | `option1` | :fontawesome-solid-check: | The first poll option |
39 | | `optionX` | | The Xth poll option |
40 |
41 | !!! info
42 | Multiline titles and options can be specified using a \ at the end of a line
43 |
44 |
45 | ## `team_yesno`
46 |
47 | Creates a "yes/no" poll and shows which team members have not voted yet.
48 |
49 | ```css
50 | .[team_yesno|tyn]
51 | ```
52 |
53 | Arguments:
54 |
55 | | Argument | Required | Description |
56 | |:--------:|:-------------------------:|:------------------------|
57 | | `text` | :fontawesome-solid-check: | The poll topic/question |
58 |
59 | Required Permissions:
60 |
61 | - `polls.team_poll`
62 |
63 |
64 | ## `teampoll`
65 |
66 | Creates a poll with 1 to a maximum of 20 options and shows which team members have not voted yet.
67 |
68 | ```css
69 | .[teampoll|teamvote|tp]
70 | [emoji1]
71 | [emojiX] [optionX]
72 | ```
73 |
74 | Arguments:
75 |
76 | | Argument | Required | Description |
77 | |:----------:|:-------------------------:|:--------------------------------|
78 | | `question` | :fontawesome-solid-check: | The poll topic/question |
79 | | `emojiX` | | The reaction emote for option X |
80 | | `option1` | :fontawesome-solid-check: | The first poll option |
81 | | `optionX` | | The Xth poll option |
82 |
83 | Required Permissions:
84 |
85 | - `polls.team_poll`
86 |
87 | !!! info
88 | Multiline titles and options can be specified using a \ at the end of a line
89 |
--------------------------------------------------------------------------------
/general/polls/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class PollsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.polls.permissions[self.name]
11 |
12 | team_poll = auto()
13 | delete = auto()
14 |
--------------------------------------------------------------------------------
/general/polls/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | team_poll: start a team poll
3 | delete: delete polls
4 |
5 | poll: Poll
6 | team_poll: Team Poll
7 | vote_explanation: Vote using the reactions below!
8 | too_many_options: You specified too many options. The maximum amount is {}.
9 | option_too_long: Options are limited to {} characters.
10 | missing_options: Missing options
11 | option_duplicated: You may not use the same emoji twice!
12 | empty_option: Empty option
13 | poll_usage: |
14 |
15 | [emoji1]
16 | [emojiX] [optionX]
17 | team_role_not_set: Team role is not set.
18 | team_role_no_members: The team role has no members.
19 | teampoll_all_voted: "All teamlers voted :white_check_mark:"
20 | teamlers_missing:
21 | one: "{last} hasn't voted yet."
22 | many: "{teamlers} and {last} haven't voted yet."
23 | created_by: Created by @{} ({})
24 | can_not_use_wastebucket_as_option: "You can not use :wastebasket: as option"
25 | foreign_message: "You are not allowed to add yes/no reactions to foreign messages!"
26 | could_not_add_reactions: Could not add reactions because I don't have `add_reactions` permission in {}.
27 |
--------------------------------------------------------------------------------
/general/reactionpin/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import ReactionPinCog
2 |
3 |
4 | __all__ = ["ReactionPinCog"]
5 |
--------------------------------------------------------------------------------
/general/reactionpin/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | ReactionPin = MaterialColors.blue["a700"]
6 |
--------------------------------------------------------------------------------
/general/reactionpin/documentation.md:
--------------------------------------------------------------------------------
1 | # ReactionPin
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/general/reactionpin/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Union
4 |
5 | from sqlalchemy import BigInteger, Column
6 |
7 | from PyDrocsid.database import Base, db
8 |
9 |
10 | class ReactionPinChannel(Base):
11 | __tablename__ = "reactionpin_channel"
12 |
13 | channel: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
14 |
15 | @staticmethod
16 | async def create(channel: int) -> ReactionPinChannel:
17 | row = ReactionPinChannel(channel=channel)
18 | await db.add(row)
19 | return row
20 |
--------------------------------------------------------------------------------
/general/reactionpin/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class ReactionPinPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.reactionpin.permissions[self.name]
11 |
12 | pin = auto()
13 | read = auto()
14 | write = auto()
15 |
--------------------------------------------------------------------------------
/general/reactionpin/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class ReactionPinSettings(Settings):
5 | keep_pin_message = True
6 |
--------------------------------------------------------------------------------
/general/reactionpin/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | pin: pin other user's messages
3 | read: read reactionpin configuration
4 | write: write reactionpin configuration
5 |
6 | reactionpin: ReactionPin
7 | no_permission: I don't have `manage_messages` permission in this channel.
8 | no_permission_alert: ReactionPin does not work because I don't have `manage_messages` permission in {}.
9 | msg_not_pinned_system: Message could not be pinned, because it is a system message.
10 | msg_not_pinned_limit: Message could not be pinned, because 50 messages are already pinned in this channel.
11 | whitelisted_channels: Whitelisted Channels
12 | no_whitelisted_channels: No whitelisted channels.
13 | channel_already_whitelisted: Channel is already whitelisted.
14 | channel_whitelisted: "Channel has been whitelisted. :white_check_mark:"
15 | log_channel_whitelisted_rp: "**Channel** {} has been **added** to the **ReactionPin whitelist**."
16 | channel_not_whitelisted: ":x: Channel is not whitelisted."
17 | channel_removed: "Channel has been removed from the whitelist. :white_check_mark:"
18 | log_channel_removed_rp: "**Channel** {} has been **removed** from the **ReactionPin whitelist**."
19 | pin_messages: "Pin Messages"
20 | pin_messages_now_enabled: "Pin Messages have been enabled. :white_check_mark:"
21 | log_pin_messages_now_enabled: "Pin Messages have been **enabled**."
22 | pin_messages_now_disabled: "Pin Messages have been disabled. :white_check_mark:"
23 | log_pin_messages_now_disabled: "**Pin Messages** have been **disabled**."
24 | no_blocked_role: No blocked role configured.
25 | blocked_role: "Blocked role: `@{}`"
26 | blocked_role_updated: Blocked role has been updated.
27 |
--------------------------------------------------------------------------------
/general/reactionrole/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import ReactionRoleCog
2 |
3 |
4 | __all__ = ["ReactionRoleCog"]
5 |
--------------------------------------------------------------------------------
/general/reactionrole/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | ReactionRole = MaterialColors.blue["a700"]
6 |
--------------------------------------------------------------------------------
/general/reactionrole/documentation.md:
--------------------------------------------------------------------------------
1 | # ReactionRole
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/general/reactionrole/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Optional, Union
4 |
5 | from sqlalchemy import BigInteger, Boolean, Column, String
6 |
7 | from PyDrocsid.database import Base, db, select
8 |
9 |
10 | class ReactionRole(Base):
11 | __tablename__ = "reactionrole"
12 |
13 | channel_id: Union[Column, int] = Column(BigInteger, primary_key=True)
14 | message_id: Union[Column, int] = Column(BigInteger, primary_key=True)
15 | emoji: Union[Column, str] = Column(String(64), primary_key=True)
16 | role_id: Union[Column, int] = Column(BigInteger)
17 | reverse: Union[Column, bool] = Column(Boolean)
18 | auto_remove: Union[Column, bool] = Column(Boolean)
19 |
20 | @staticmethod
21 | async def create(
22 | channel_id: int, message_id: int, emoji: str, role_id: int, reverse: bool, auto_remove: bool
23 | ) -> ReactionRole:
24 | row = ReactionRole(
25 | channel_id=channel_id,
26 | message_id=message_id,
27 | emoji=emoji,
28 | role_id=role_id,
29 | reverse=reverse,
30 | auto_remove=auto_remove,
31 | )
32 | await db.add(row)
33 | return row
34 |
35 | @staticmethod
36 | async def get(channel_id: int, message_id: int, emoji: str) -> Optional[ReactionRole]:
37 | return await db.first(select(ReactionRole).filter_by(channel_id=channel_id, message_id=message_id, emoji=emoji))
38 |
--------------------------------------------------------------------------------
/general/reactionrole/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class ReactionRolePermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.reactionrole.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/general/reactionrole/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: read reactionrole configuration
3 | write: write reactionrole configuration
4 |
5 | commands:
6 | reactionrole: manage reactionrole
7 | reactionrole_list: list configured reactionrole links for a specific message
8 | reactionrole_add: add a new reactionrole link
9 | reactionrole_remove: remove a reactionrole link
10 | reactionrole_reinialize: reinialize reactions on a reactionrole message
11 |
12 | reactionrole: ReactionRole
13 | no_reactionrole_links: No ReactionRole links have been created yet.
14 | no_reactionrole_links_for_msg: No ReactionRole links have been created yet for this message.
15 | rr_link_already_exists: A link already exists for this reaction on this message.
16 | rr_link_created: "Link has been created successfully. :white_check_mark:"
17 | rr_link_not_created_no_permissions: >
18 | Link could not be created because I don't have `add_reactions` permission in this channel.
19 | log_rr_link_created: "**ReactionRole link** for {} -> <@&{}> has been **created** on [this message]({}) in {}"
20 | rr_link_not_found: Such a link does not exist.
21 | rr_link_removed: "Link has been removed successfully. :white_check_mark:"
22 | could_not_add_reactions: "Could not add reaction to message!"
23 | could_not_remove_reactions: "Could not remove reaction from message!"
24 | log_rr_link_removed: "**ReactionRole link** for {} -> <@&{}> has been **deleted** on [this message]({}) in {}"
25 | rr_link: "{} -> {}"
26 | auto_remove: auto remove
27 | reverse: reverse
28 | manage_role_error: "ReactionRole could not manage role {role.mention} on {member.mention} ({member.id}) on [this message]({message.jump_url})."
29 | remove_emoji_error: "ReactionRole could not remove {emoji} reaction from [this message]({message.jump_url})."
30 |
--------------------------------------------------------------------------------
/general/remind_me/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import RemindMeCog
2 |
3 |
4 | __all__ = ["RemindMeCog"]
5 |
--------------------------------------------------------------------------------
/general/remind_me/cog.py:
--------------------------------------------------------------------------------
1 | from discord import Forbidden, Member, Message, PartialEmoji
2 |
3 | from PyDrocsid.cog import Cog
4 | from PyDrocsid.emojis import name_to_emoji
5 | from PyDrocsid.events import StopEventHandling
6 | from PyDrocsid.translations import t
7 |
8 | from ...contributor import Contributor
9 | from ...pubsub import send_alert
10 |
11 |
12 | tg = t.g
13 | t = t.remind_me
14 |
15 | EMOJIS = {
16 | name_to_emoji["star"],
17 | name_to_emoji["mailbox"],
18 | name_to_emoji["e_mail"],
19 | name_to_emoji["envelope"],
20 | name_to_emoji["incoming_envelope"],
21 | name_to_emoji["envelope_with_arrow"],
22 | name_to_emoji["floppy_disk"],
23 | name_to_emoji["bookmark"],
24 | }
25 |
26 | WASTEBASKET = name_to_emoji["wastebasket"]
27 |
28 |
29 | async def remove_member_reaction(emoji, member, message):
30 | """Remove the RemindMe reaction from a member, if they do not allow private messages."""
31 | try:
32 | await message.remove_reaction(emoji, member)
33 | except Forbidden:
34 | await send_alert(message.guild, t.cannot_send(message.jump_url, message.channel.mention))
35 |
36 |
37 | class RemindMeCog(Cog, name="RemindMe"):
38 | """Add a "Remind Me"-functionality by sending the user a copy of the message they reacted on."""
39 |
40 | CONTRIBUTORS = [Contributor.Tristan]
41 |
42 | async def on_raw_reaction_add(self, message: Message, emoji: PartialEmoji, member: Member):
43 | """
44 | Check, if the reaction is in a list of set emojis. If so, the message is sent as a direct message to the user.
45 | Remove the user reaction, if the user does not accept direct messages.
46 | Send an alert in case of missing permissions.
47 | """
48 |
49 | if not message.guild and str(emoji) == WASTEBASKET and message.author == self.bot.user != member:
50 | await message.delete()
51 | raise StopEventHandling
52 |
53 | if str(emoji) not in EMOJIS or member.bot or message.guild is None:
54 | return
55 |
56 | if message.attachments:
57 | try:
58 | message_to_user = await member.send("\n".join(attachment.url for attachment in message.attachments))
59 | await message_to_user.add_reaction(WASTEBASKET)
60 | except Forbidden:
61 | await remove_member_reaction(emoji, member, message)
62 | return
63 |
64 | embed = message.embeds[0] if message.embeds else None
65 |
66 | if message.content or embed:
67 | try:
68 | message_to_user = await member.send(message.content, embed=embed)
69 | await message_to_user.add_reaction(WASTEBASKET)
70 | except Forbidden:
71 | await remove_member_reaction(emoji, member, message)
72 |
--------------------------------------------------------------------------------
/general/remind_me/documentation.md:
--------------------------------------------------------------------------------
1 | # RemindMe
2 |
3 | Contains a simple remind feature. When a user reacts on a message with one of the following emojis, the bot sends a copy of that message to the user as a direct message:
4 |
5 | | Emoji | Name |
6 | |:---------------------:|:------------------------|
7 | | :star: | `:star:` |
8 | | :mailbox: | `:mailbox:` |
9 | | :email: | `:email:` |
10 | | :envelope: | `:envelope:` |
11 | | :incoming_envelope: | `:incoming_envelope:` |
12 | | :envelope_with_arrow: | `:envelope_with_arrow:` |
13 | | :floppy_disk: | `:floppy_disk:` |
14 | | :bookmark: | `:bookmark:` |
15 |
--------------------------------------------------------------------------------
/general/remind_me/translations/en.yml:
--------------------------------------------------------------------------------
1 | cannot_send: Could not remove RemindMe reaction from [this message]({}) because I don't have `manage_messages` permission in {}.
2 |
--------------------------------------------------------------------------------
/general/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import UtilsCog
2 |
3 |
4 | __all__ = ["UtilsCog"]
5 |
--------------------------------------------------------------------------------
/general/utils/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Utils = MaterialColors.green["a700"]
6 |
--------------------------------------------------------------------------------
/general/utils/documentation.md:
--------------------------------------------------------------------------------
1 | # Utils
2 |
3 | Contains some utility commands for general use as well as for testing and development.
4 |
5 |
6 | ## `ping`
7 |
8 | Determines the latency of the bot to Discord in milliseconds.
9 |
10 | ```css
11 | .ping
12 | ```
13 |
14 |
15 | ## `snowflake`
16 |
17 | Extracts and displays the timestamp from any [Discord snowflake ID](https://discord.com/developers/docs/reference#snowflakes){target=_blank}. It can be used to find out the date and time of creation of any Discord user, guild, channel, message, role, custom emoji or anything else that has an ID.
18 |
19 | ```css
20 | .[snowflake|sf|time]
21 | ```
22 |
23 | Arguments:
24 |
25 | | Argument | Required | Description |
26 | |:--------:|:-------------------------:|:-----------------|
27 | | `ID` | :fontawesome-solid-check: | The snowflake ID |
28 |
29 |
30 | ## `encode`
31 |
32 | Applies Python's [`str.encode` function](https://docs.python.org/3/library/stdtypes.html#str.encode){target=_blank} to the username and nickname of a given user.
33 |
34 | ```css
35 | .[encode|enc]
36 | ```
37 |
38 | Argument:
39 |
40 | | Argument | Required | Description |
41 | |:--------:|:-------------------------:|:-------------------|
42 | | `user` | :fontawesome-solid-check: | The user or member |
43 |
44 |
45 | ## `suggest_role_color`
46 |
47 | Suggests the color for a new role, trying to avoid colors already in use. Optionally you can specify a list of colors to also avoid.
48 |
49 | ```css
50 | .[suggest_role_color|rc] [avoid...]
51 | ```
52 |
53 | Arguments:
54 |
55 | | Argument | Required | Description |
56 | |:--------:|:--------:|:-----------------------------------|
57 | | `avoid` | | A list of color hex codes to avoid |
58 |
59 | Required Permissions:
60 |
61 | - `utils.suggest_role_color`
62 |
--------------------------------------------------------------------------------
/general/utils/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class UtilsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.utils.permissions[self.name]
11 |
12 | suggest_role_color = auto()
13 |
--------------------------------------------------------------------------------
/general/utils/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | suggest_role_color: use the `suggest_role_color` command
3 |
4 | commands:
5 | ping: Display bot latency
6 | snowflake: Display snowflake timestamp
7 | encode: Apply Python's `str.encode()` function to a user's name
8 | suggest_role_color: Suggest a new role color based on the colors of all existing roles
9 |
10 | pong: Pong!
11 | pong_latency: ":hourglass: {:.0f} ms"
12 | invalid_snowflake: Invalid snowflake!
13 | could_not_generate_color: Could not generate a new role color.
14 | username: Username
15 | nickname: Nickname
16 |
--------------------------------------------------------------------------------
/general/voice_channel/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import VoiceChannelCog
2 |
3 |
4 | __all__ = ["VoiceChannelCog"]
5 |
--------------------------------------------------------------------------------
/general/voice_channel/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Voice = MaterialColors.blue["a700"]
6 | unlocked = MaterialColors.lightgreen["a700"]
7 | locked = MaterialColors.blue["a700"]
8 |
--------------------------------------------------------------------------------
/general/voice_channel/documentation.md:
--------------------------------------------------------------------------------
1 | # Voice Channels
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/general/voice_channel/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime
4 | from typing import Optional, Union
5 | from uuid import uuid4
6 |
7 | from discord.utils import utcnow
8 | from sqlalchemy import BigInteger, Boolean, Column, ForeignKey, String
9 | from sqlalchemy.orm import relationship
10 |
11 | from PyDrocsid.database import Base, UTCDateTime, db
12 |
13 |
14 | class DynGroup(Base):
15 | __tablename__ = "dynvoice_group"
16 |
17 | id: Union[Column, str] = Column(String(36), primary_key=True, unique=True)
18 | user_role: Union[Column, int] = Column(BigInteger)
19 | channels: list[DynChannel] = relationship("DynChannel", back_populates="group", cascade="all, delete")
20 |
21 | @staticmethod
22 | async def create(channel_id: int, user_role: int) -> DynGroup:
23 | group = DynGroup(id=str(uuid4()), user_role=user_role)
24 | group.channels.append(await DynChannel.create(channel_id, group.id))
25 | await db.add(group)
26 | return group
27 |
28 |
29 | class DynChannel(Base):
30 | __tablename__ = "dynvoice_channel"
31 |
32 | channel_id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
33 | text_id: Union[Column, int] = Column(BigInteger)
34 | locked: Union[Column, bool] = Column(Boolean)
35 | group_id: Union[Column, str] = Column(String(36), ForeignKey("dynvoice_group.id"))
36 | group: DynGroup = relationship("DynGroup", back_populates="channels")
37 | owner_id: Union[Column, str] = Column(String(36))
38 | owner_override: Union[Column, int] = Column(BigInteger)
39 | members: list[DynChannelMember] = relationship(
40 | "DynChannelMember", back_populates="channel", cascade="all, delete", order_by="DynChannelMember.timestamp"
41 | )
42 |
43 | @staticmethod
44 | async def create(channel_id: int, group_id: int) -> DynChannel:
45 | channel = DynChannel(channel_id=channel_id, text_id=None, locked=False, group_id=group_id)
46 | await db.add(channel)
47 | return channel
48 |
49 | @staticmethod
50 | async def get(**kwargs) -> Optional[DynChannel]:
51 | return await db.get(DynChannel, [DynChannel.group, DynGroup.channels], DynChannel.members, **kwargs)
52 |
53 |
54 | class DynChannelMember(Base):
55 | __tablename__ = "dynvoice_channel_member"
56 |
57 | id: Union[Column, str] = Column(String(36), primary_key=True, unique=True)
58 | member_id: Union[Column, int] = Column(BigInteger)
59 | channel_id: Union[Column, int] = Column(BigInteger, ForeignKey("dynvoice_channel.channel_id"))
60 | channel: DynChannel = relationship("DynChannel", back_populates="members")
61 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
62 |
63 | @staticmethod
64 | async def create(member_id: int, channel_id: int) -> DynChannelMember:
65 | member = DynChannelMember(id=str(uuid4()), member_id=member_id, channel_id=channel_id, timestamp=utcnow())
66 | await db.add(member)
67 | return member
68 |
69 |
70 | class RoleVoiceLink(Base):
71 | __tablename__ = "role_voice_link"
72 |
73 | role: Union[Column, int] = Column(BigInteger, primary_key=True)
74 | voice_channel: Union[Column, str] = Column(String(36), primary_key=True)
75 |
76 | @staticmethod
77 | async def create(role: int, voice_channel: str) -> RoleVoiceLink:
78 | link = RoleVoiceLink(role=role, voice_channel=voice_channel)
79 | await db.add(link)
80 | return link
81 |
--------------------------------------------------------------------------------
/general/voice_channel/names/computer_science_pioneers.txt:
--------------------------------------------------------------------------------
1 | Franz Alt
2 | Marc Andreessen
3 | John Atanasoff
4 | Charles Babbage
5 | John W. Backus
6 | Rudolf Bayer
7 | Andreas von Bechtolsheim
8 | Alexander Graham Bell
9 | Tim Berners-Lee
10 | Clifford Berry
11 | Barry W. Boehm
12 | George Boole
13 | Karlheinz Brandenburg
14 | Vannevar Bush
15 | Peter Chen
16 | Noam Chomsky
17 | Edgar F. Codd
18 | Stephen A. Cook
19 | Fernando José Corbató
20 | Seymour Cray
21 | Ole-Johan Dahl
22 | Edsger W. Dijkstra
23 | John Presper Eckert
24 | Douglas C. Engelbart
25 | Michael Fagan
26 | Christiane Floyd
27 | Jay Wright Forrester
28 | Erich Gamma
29 | Wolfgang Giloi
30 | Kurt Gödel
31 | Marcel J. E. Golay
32 | Emanuel Goldberg
33 | Charles F. Goldfarb
34 | James Gosling
35 | John Guttag
36 | Richard Hamming
37 | Ralph Hartley
38 | Tony Hoare
39 | Herman Hollerith
40 | Grace Hopper
41 | David A. Huffman
42 | Bill Joy
43 | Alan Kay
44 | Brian W. Kernighan
45 | Alonzo Church
46 | Donald E. Knuth
47 | Lawrence Landweber
48 | Nikolaus Joachim Lehmann
49 | Gottfried Wilhelm Leibniz
50 | J. C. R. Licklider
51 | Marvin Lee Minsky
52 | John William Mauchly
53 | Frieder Nake
54 | John von Neumann
55 | Heinz Nixdorf
56 | Harry Nyquist
57 | Ada Lovelace
58 | Ken Olsen
59 | Pier Giorgio Perotto
60 | Carl Adam Petri
61 | Dennis Ritchie
62 | Claude Shannon
63 | Richard Stallman
64 | Karl Steinbuch
65 | Ivan Sutherland
66 | Bjarne Stroustrup
67 | Andrew S. Tanenbaum
68 | Ken Thompson
69 | Ray Tomlinson
70 | Linus Torvalds
71 | Alan Turing
72 | An Wang
73 | Mark Weiser
74 | Joseph Weizenbaum
75 | Adriaan van Wijngaarden
76 | Niklaus Wirth
77 | Heinz Zemanek
78 | Konrad Zuse
79 | Corrado Böhm
80 | Guido van Rossum
81 | Joan Daemen
82 | Vincent Rijmen
83 |
--------------------------------------------------------------------------------
/general/voice_channel/names/data_structures.txt:
--------------------------------------------------------------------------------
1 | Boolean
2 | Float
3 | Integer
4 | Reference
5 | Enum
6 | Array
7 | List
8 | Tuple
9 | Map
10 | Multimap
11 | Set
12 | Multiset
13 | Stack
14 | Queue
15 | Deque
16 | Graph
17 | Tree
18 | Heap
19 | Bit array
20 | Bit field
21 | Bitboard
22 | Bitmap
23 | Circular buffer
24 | Control table
25 | Image
26 | Dope vector
27 | Dynamic array
28 | Gap buffer
29 | Hashed array tree
30 | Lookup table
31 | Matrix
32 | Parallel array
33 | Sorted array
34 | Sparse matrix
35 | Iliffe vector
36 | Variable-length array
37 | Doubly linked list
38 | Array list
39 | Linked list
40 | Association list
41 | Self-organizing list
42 | Skip list
43 | Unrolled linked list
44 | VList
45 | Conc-tree list
46 | Xor linked list
47 | Zipper
48 | Difference list
49 | Free list
50 | AA tree
51 | AVL tree
52 | Binary search tree
53 | Binary tree
54 | Cartesian tree
55 | Conc-tree list
56 | Order statistic tree
57 | Pagoda
58 | Red–black tree
59 | Rope
60 | Scapegoat tree
61 | Splay tree
62 | T-tree
63 | Tango tree
64 | Threaded binary tree
65 | Top tree
66 | Treap
67 | WAVL tree
68 | Weight-balanced tree
69 | B-tree
70 | B+ tree
71 | B*-tree
72 | B sharp tree
73 | Dancing tree
74 | 2–3 tree
75 | 2–3–4 tree
76 | Queap
77 | Fusion tree
78 | Bx-tree
79 | AList
80 | Heap
81 | Binary heap
82 | B-heap
83 | Weak heap
84 | Binomial heap
85 | Fibonacci heap
86 | AF-heap
87 | Leonardo heap
88 | 2–3 heap
89 | Soft heap
90 | Pairing heap
91 | Leftist heap
92 | Treap
93 | Beap
94 | Skew heap
95 | Ternary heap
96 | D-ary heap
97 | Brodal queue
98 | Radix tree
99 | Suffix tree
100 | Suffix array
101 | Compressed suffix array
102 | FM-index
103 | Generalised suffix tree
104 | B-tree
105 | Judy array
106 | X-fast trie
107 | Y-fast trie
108 | Merkle tree
109 | C tree
110 | Ternary tree
111 | K-ary tree
112 | And–or tree
113 | (a,b)-tree
114 | Link/cut tree
115 | SPQR-tree
116 | Spaghetti stack
117 | Union-find
118 | Fusion tree
119 | Enfilade
120 | Exponential tree
121 | Fenwick tree
122 | Van Emde Boas tree
123 | Rose tree
124 | Segment tree
125 | Interval tree
126 | Range tree
127 | Bin
128 | K-d tree
129 | Implicit k-d tree
130 | Min/max k-d tree
131 | Relaxed k-d tree
132 | Adaptive k-d tree
133 | Quadtree
134 | Octree
135 | Linear octree
136 | Z-order
137 | UB-tree
138 | R-tree
139 | R+ tree
140 | R* tree
141 | Hilbert R-tree
142 | X-tree
143 | Metric tree
144 | Cover tree
145 | M-tree
146 | VP-tree
147 | BK-tree
148 | Bounding volume hierarchy
149 | BSP tree
150 | Abstract syntax tree
151 | Parse tree
152 | Decision tree
153 | Alternating decision tree
154 | Minimax tree
155 | Expectiminimax tree
156 | Finger tree
157 | Expression tree
158 | Log-structured merge-tree
159 | Lexicographic Search Tree
160 | Bloom filter
161 | Count–min sketch
162 | Distributed hash table
163 | Double hashing
164 | Hash array mapped trie
165 | Hash list
166 | Hash table
167 | Hash tree
168 | Hash trie
169 | Koorde
170 | Prefix hash tree
171 | Rolling hash
172 | MinHash
173 | Quotient filter
174 | Ctrie
175 | Graph
176 | Adjacency list
177 | Adjacency matrix
178 | Graph-structured stack
179 | Scene graph
180 | Decision tree
181 | Binary decision diagram
182 | And-inverter graph
183 | Directed graph
184 | Directed acyclic graph
185 | Multigraph
186 | Hypergraph
187 | Lightmap
188 | Winged edge
189 | Quad-edge
190 | Routing table
191 | Symbol table
192 |
--------------------------------------------------------------------------------
/general/voice_channel/names/elements.txt:
--------------------------------------------------------------------------------
1 | hydrogen
2 | helium
3 | lithium
4 | beryllium
5 | boron
6 | carbon
7 | nitrogen
8 | oxygen
9 | fluorine
10 | neon
11 | sodium
12 | magnesium
13 | aluminium
14 | silicon
15 | phosphorus
16 | sulfur
17 | chlorine
18 | argon
19 | potassium
20 | calcium
21 | scandium
22 | titanium
23 | vanadium
24 | chromium
25 | manganese
26 | iron
27 | cobalt
28 | nickel
29 | copper
30 | zinc
31 | gallium
32 | germanium
33 | arsenic
34 | selenium
35 | bromine
36 | krypton
37 | rubidium
38 | strontium
39 | yttrium
40 | zirconium
41 | niobium
42 | molybdenum
43 | technetium
44 | ruthenium
45 | rhodium
46 | palladium
47 | silver
48 | cadmium
49 | indium
50 | tin
51 | antimony
52 | tellurium
53 | iodine
54 | xenon
55 | caesium
56 | barium
57 | lanthanum
58 | cerium
59 | praseodymium
60 | neodymium
61 | promethium
62 | samarium
63 | europium
64 | gadolinium
65 | terbium
66 | dysprosium
67 | holmium
68 | erbium
69 | thulium
70 | ytterbium
71 | lutetium
72 | hafnium
73 | tantalum
74 | tungsten
75 | rhenium
76 | osmium
77 | iridium
78 | platinum
79 | gold
80 | mercury
81 | thallium
82 | lead
83 | bismuth
84 | polonium
85 | astatine
86 | radon
87 | francium
88 | radium
89 | actinium
90 | thorium
91 | protactinium
92 | uranium
93 | neptunium
94 | plutonium
95 | americium
96 | curium
97 | berkelium
98 | californium
99 | einsteinium
100 | fermium
101 | mendelevium
102 | nobelium
103 | lawrencium
104 | rutherfordium
105 | dubnium
106 | seaborgium
107 | bohrium
108 | hassium
109 | meitnerium
110 | darmstadtium
111 | roentgenium
112 | copernicium
113 | nihonium
114 | flerovium
115 | moscovium
116 | livermorium
117 | tennessine
118 | oganesson
119 |
--------------------------------------------------------------------------------
/general/voice_channel/names/famous_mathematicians.txt:
--------------------------------------------------------------------------------
1 | Abraham De Moivre
2 | Ada Lovelace
3 | Al Khwarizmi
4 | Alan Turing
5 | Albert Einstein
6 | Albertus Magnus
7 | Alexandre Grothendieck
8 | Alfred North Whitehead
9 | Andrew Wiles
10 | Archimedes
11 | Aristotle
12 | Arthur Cayley
13 | Aryabhata
14 | Augustin-Louis Cauchy
15 | Augustus De Morgan
16 | Benjamin Banneker
17 | Bernhard Riemann
18 | Bertrand Russell
19 | Blaise Pascal
20 | Brahmagupta
21 | Brook Taylor
22 | Carl Friedrich Gauss
23 | Charles Babbage
24 | Charles Babbage
25 | Christiaan Huygens
26 | Daniel Bernoulli
27 | David Hilbert
28 | Democritus
29 | Diophantus
30 | Edmund Halley
31 | Edward Lorenz
32 | Edward Witten
33 | Emanuel Lasker
34 | Emmy Noether
35 | Eratosthenes
36 | Euclid
37 | Évariste Galois
38 | Felix Klein
39 | Fibonacci
40 | Filippo Brunelleschi
41 | G.H. Hardy
42 | Galileo Galilei
43 | Gaspard Monge
44 | Georg Cantor
45 | George Boole
46 | Giuseppe Peano
47 | Gottfried Leibniz
48 | Gottlob Frege
49 | Grace Murray Hopper
50 | Grigori Perelman
51 | Henri Poincaré
52 | Heron Of Alexandria
53 | Hipparchus
54 | Isaac Newton
55 | Jacob Bernoulli
56 | Johann Bernoulli
57 | Johannes Kepler
58 | John Napier
59 | John Nash
60 | John Venn
61 | John von Neumann
62 | John Wallis
63 | Joseph Fourier
64 | Joseph Louis Lagrange
65 | Julia Robinson
66 | Kurt Gödel
67 | Leonhard Euler
68 | Liu Hui
69 | Luca Pacioli
70 | Madhava
71 | Maurits Escher
72 | Niels Henrik Abel
73 | Omar Khayyam
74 | Paul Cohen
75 | Pierre de Fermat
76 | Pierre-Simon Laplace
77 | Plato
78 | Ptolemy
79 | Pythagoras
80 | René Descartes
81 | Sophie Germain
82 | Srinivasa Ramanujan
83 | Thales
84 | William Rowan Hamilton
85 |
--------------------------------------------------------------------------------
/general/voice_channel/names/fields_medalists.txt:
--------------------------------------------------------------------------------
1 | Akshay Venkatesh
2 | Alain Connes
3 | Alan Baker
4 | Alessio Figalli
5 | Alexander Grothendieck
6 | Andrei Okounkov
7 | Artur Avila
8 | Atle Selberg
9 | Caucher Birkar
10 | Cédric Villani
11 | Charles Louis Fefferman
12 | Curtis T. Mcmullen
13 | Daniel G. Quillen
14 | David Bryant Mumford
15 | Edward Witten
16 | Efim Zelmanov
17 | Elon Lindenstrauss
18 | Enrico Bombieri
19 | Gerd Faltings
20 | Gregori Alexandrovitch Margulis
21 | Heisuke Hironaka
22 | Jean Bourgain
23 | Jean-Christophe Yoccoz
24 | Jean-Pierre Serre
25 | Jesse Douglas
26 | John Griggs Thompson
27 | John Willard Milnor
28 | Klaus Friedrich Roth
29 | Kunihiko Kodaira
30 | Lars Hörmander
31 | Lars Valerian Ahlfors
32 | Laurent Lafforgue
33 | Laurent Schwartz
34 | Manjul Bhargava
35 | Martin Hairer
36 | Maryam Mirzakhani
37 | Maxim Kontsevich
38 | Michael Francis Atiyah
39 | Michael H. Freedman
40 | Ngô Bào Châu
41 | Paul Joseph Cohen
42 | Peter Scholze
43 | Pierre René Deligne
44 | Pierre-Louis Lions
45 | René Thom
46 | Richard E. Borcherds
47 | Serge Novikov
48 | Shigefumi Mori
49 | Shing-Tung Yau
50 | Simon K. Donaldson
51 | Stanislav Smirnov
52 | Stephen Smale
53 | Terence Tao
54 | Vaughan F.R. Jones
55 | Vladimir Drinfeld
56 | Vladimir Voevodsky
57 | W. Timothy Gowers
58 | Wendelin Werner
59 | William P. Thurston
60 |
--------------------------------------------------------------------------------
/general/voice_channel/names/greek_alphabet.txt:
--------------------------------------------------------------------------------
1 | Α
2 | α
3 | Β
4 | β
5 | Γ
6 | γ
7 | Δ
8 | δ
9 | Ε
10 | ε
11 | Ζ
12 | ζ
13 | Η
14 | η
15 | Θ
16 | θ
17 | Ι
18 | ι
19 | Κ
20 | κ
21 | Λ
22 | λ
23 | Μ
24 | μ
25 | Ν
26 | ν
27 | Ξ
28 | ξ
29 | Ο
30 | ο
31 | Π
32 | π
33 | Ρ
34 | ρ
35 | Σ
36 | σ
37 | Τ
38 | τ
39 | Υ
40 | υ
41 | Φ
42 | φ
43 | Χ
44 | χ
45 | Ψ
46 | ψ
47 | Ω
48 | ω
49 |
--------------------------------------------------------------------------------
/general/voice_channel/names/greek_alphabet_names.txt:
--------------------------------------------------------------------------------
1 | alpha
2 | beta
3 | gamma
4 | delta
5 | epsilon
6 | zeta
7 | eta
8 | theta
9 | iota
10 | kappa
11 | lambda
12 | mu
13 | nu
14 | xi
15 | omicron
16 | pi
17 | rho
18 | sigma
19 | tau
20 | upsilon
21 | phi
22 | chi
23 | psi
24 | omega
25 |
--------------------------------------------------------------------------------
/general/voice_channel/names/greek_gods_olymp.txt:
--------------------------------------------------------------------------------
1 | Zeus
2 | Poseidon
3 | Hestia
4 | Hades
5 | Demeter
6 | Hera
7 | Athene
8 | Apollon
9 | Artemis
10 | Hermes
11 | Dionysos
12 | Hephaistos
13 | Ares
14 | Aphrodite
15 | Hebe
16 | Persephone
17 | Herakles
18 |
--------------------------------------------------------------------------------
/general/voice_channel/names/intel_codenames.txt:
--------------------------------------------------------------------------------
1 | Apollo Lake
2 | Bakerville
3 | Basin Falls
4 | Bay Trail
5 | Braswell
6 | Briarwood
7 | Brickland
8 | Broadwell DE
9 | Broadwell H
10 | Broadwell U/Y
11 | Cascade Lake
12 | Cedar Trail
13 | Cherry Trail
14 | Cherry Trail CR
15 | Chief River
16 | Coffee Lake H
17 | Coffee Lake Refresh
18 | Coffee Lake S
19 | Coffee Lake U
20 | Comet Lake S
21 | Crystal Forest
22 | Denlow
23 | Denverton NS
24 | East Beach
25 | Edisonville
26 | Elkhart Lake
27 | Foxhollow
28 | Gemini Lake
29 | Glen Forest
30 | Gordon Peak
31 | Grangeville
32 | Grantley
33 | Greenlow
34 | Groveport
35 | Haswell DT R
36 | Ice Lake SP
37 | Kaby Lake H
38 | Kaby Lake S
39 | Kaby Lake U/Y
40 | Moorefield
41 | Oak Trail
42 | Purley
43 | Rangeley
44 | River Forest
45 | Romley
46 | Shark Bay
47 | Skylake H
48 | Skylake S
49 | Skylake U/Y
50 | SoFIA 3G
51 | SoFIA 3G-R
52 | Tiger Lake UP3
53 | Tylersburg/Westmere
54 | Whiskey Lake
55 |
--------------------------------------------------------------------------------
/general/voice_channel/names/np_complete_problems.txt:
--------------------------------------------------------------------------------
1 | 1-planarity
2 | 3-dim matching
3 | 3-partition
4 | 3SAT
5 | achromatic number
6 | bandwidth problem
7 | berth allocation problem
8 | betweenness
9 | bin packing
10 | bipartite dim
11 | block sorting
12 | capacitated MST
13 | circuit SAT
14 | clique
15 | clique cover
16 | closest string
17 | conjunctive Boolean query
18 | cyclic ordering
19 | degree-constrained ST
20 | domatic number
21 | dominating set
22 | exact cover
23 | feedback arc set
24 | feedback vertex set
25 | flow shop scheduling
26 | graph coloring
27 | graph homomorphism
28 | graph intersection number
29 | graph partition
30 | Hamiltonian completion
31 | Hamiltonian path
32 | integer programming
33 | k-Chinese postman
34 | knapsack problem
35 | Latin squares
36 | longest common subsequence
37 | longest path
38 | maximum 2-SAT
39 | maximum cut
40 | maximum independent set
41 | maximum induced path
42 | maximum volume submatrix
43 | metric graph dimension
44 | metric k-center
45 | min max independent set
46 | minimum degree ST
47 | minimum k-cut
48 | minimum k-ST
49 | modal logic S5-SAT
50 | modularity maximization
51 | monochromatic triangle
52 | open-shop scheduling
53 | pancake sorting distance
54 | partition problem
55 | pathwidth
56 | post correspondence
57 | quadratic assignment
58 | quadratic programming
59 | rank coloring
60 | route inspection
61 | SAT
62 | second order instantiation
63 | set cover
64 | set packing
65 | set splitting
66 | shortest common supersequence
67 | slope number two testing
68 | sparse approximation
69 | Steiner tree
70 | subgraph isomorphism problem
71 | subset sum
72 | traveling salesman
73 | treewidth
74 | treewidth
75 | upward planarity testing
76 | vehicle routing
77 | vertex cover
78 |
--------------------------------------------------------------------------------
/general/voice_channel/names/programming_languages.txt:
--------------------------------------------------------------------------------
1 | Java
2 | C
3 | C++
4 | C#
5 | F#
6 | Go
7 | Python
8 | Visual Basic
9 | PHP
10 | Delphi
11 | Pascal
12 | Swift
13 | Ruby
14 | Fortran
15 | Rust
16 | Prolog
17 | Matlab
18 | Objective-C
19 | Perl
20 | Haskell
21 | Lua
22 | Scala
23 | ActionScript
24 | JavaScript
25 | TypeScript
26 | CoffeeScript
27 | AppleScript
28 | Julia
29 | Kotlin
30 | Dart
31 | Erlang
32 | Lisp
33 | WhiteSpace
34 | ArnoldC
35 | Brainfuck
36 | Groovy
37 |
--------------------------------------------------------------------------------
/general/voice_channel/names/roman_gods_olymp.txt:
--------------------------------------------------------------------------------
1 | Jupiter
2 | Neptun
3 | Vesta
4 | Pluto
5 | Ceres
6 | Juno
7 | Minerva
8 | Apollo
9 | Diana
10 | Mercurius
11 | Bacchus
12 | Vulcanus
13 | Mars
14 | Venus
15 | Iuventas
16 | Proserpina
17 | Herkules
18 |
--------------------------------------------------------------------------------
/general/voice_channel/names/spaces.txt:
--------------------------------------------------------------------------------
1 | Affine space
2 | Algebraic space
3 | Baire space
4 | Banach space
5 | Base space
6 | Bergman space
7 | Berkovich space
8 | Besov space
9 | Borel space
10 | Calabi-Yau space
11 | Cantor space
12 | Cauchy space
13 | Cellular space
14 | Chu space
15 | Closure space
16 | Complex analytic space
17 | Conformal space
18 | Drinfeld's symmetric space
19 | Eilenberg–Mac Lane space
20 | Euclidean space
21 | Fiber space
22 | Finsler space
23 | First-countable space
24 | Fréchet space
25 | Function space
26 | G-space
27 | Green space
28 | Hardy space
29 | Hausdorff space
30 | Heisenberg space
31 | Hilbert space
32 | Homogeneous space
33 | Inner product space
34 | Kolmogorov space
35 | Lens space
36 | Liouville space
37 | Locally finite space
38 | Loop space
39 | Lorentz space
40 | Lp-space
41 | Mapping space
42 | Measure space
43 | Metric space
44 | Minkowski space
45 | Müntz space
46 | Normed space
47 | Paracompact space
48 | Perfectoid space
49 | Planar space
50 | Polish space
51 | Probability space
52 | Projective space
53 | Proximity space
54 | Quadratic space
55 | Quotient space
56 | Riemann's Moduli space
57 | Sample space
58 | Sequence space
59 | Sierpiński space
60 | Sobolev space
61 | Standard space
62 | State space
63 | Stone space
64 | Symplectic space
65 | T2-space
66 | Teichmüller space
67 | Tensor space
68 | Topological space
69 | Topological vector space
70 | Total space
71 | Uniform space
72 | Vector space
73 |
--------------------------------------------------------------------------------
/general/voice_channel/names/turing_award.txt:
--------------------------------------------------------------------------------
1 | Alan J. Perlis
2 | Maurice V. Wilkes
3 | Richard Hamming
4 | Marvin Minsky
5 | James H. Wilkinson
6 | John McCarthy
7 | Edsger W. Dijkstra
8 | Charles Bachman
9 | Donald E. Knuth
10 | Allen Newell
11 | Herbert A. Simon
12 | Michael O. Rabin
13 | Dana Scott
14 | John W. Backus
15 | Robert Floyd
16 | Kenneth E. Iverson
17 | Tony Hoare
18 | Edgar F. Codd
19 | Stephen A. Cook
20 | Ken Thompson
21 | Dennis Ritchie
22 | Niklaus Wirth
23 | Richard M. Karp
24 | John E. Hopcroft
25 | Robert Tarjan
26 | John Cocke
27 | Ivan Sutherland
28 | William Kahan
29 | Fernando José Corbató
30 | Robin Milner
31 | Butler Lampson
32 | Juris Hartmanis
33 | Richard E. Stearns
34 | Edward Feigenbaum
35 | Raj Reddy
36 | Manuel Blum
37 | Amir Pnueli
38 | Douglas C. Engelbart
39 | Jim Gray
40 | Frederick P. Brooks
41 | Andrew Yao
42 | Ole-Johan Dahl
43 | Kristen Nygaard
44 | Ronald L. Rivest
45 | Adi Shamir
46 | Leonard Adleman
47 | Alan Kay
48 | Vinton G. Cerf
49 | Robert E. Kahn
50 | Peter Naur
51 | Frances E. Allen
52 | Edmund M. Clarke
53 | E. Allen Emerson
54 | Joseph Sifakis
55 | Barbara Liskov
56 | Charles P. Thacker
57 | Leslie Valiant
58 | Judea Pearl
59 | Silvio Micali
60 | Shafi Goldwasser
61 | Leslie Lamport
62 | Michael Stonebraker
63 | Whitfield Diffie
64 | Martin E. Hellman
65 | Tim Berners-Lee
66 | John L. Hennessy
67 | David A. Patterson
68 | Yoshua Bengio
69 | Yann LeCun
70 | Geoffrey Hinton
71 | Edwin Catmull
72 | Pat Hanrahan
73 | Alfred Vaino Aho
74 | Jeffrey David Ullman
75 |
--------------------------------------------------------------------------------
/general/voice_channel/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class VoiceChannelPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.voice_channel.permissions[self.name]
11 |
12 | override_owner = auto()
13 | dyn_read = auto()
14 | dyn_write = auto()
15 | dyn_rename = auto()
16 | link_read = auto()
17 | link_write = auto()
18 |
--------------------------------------------------------------------------------
/information/__init__.py:
--------------------------------------------------------------------------------
1 | from .bot_info import BotInfoCog
2 | from .heartbeat import HeartbeatCog
3 | from .help import HelpCog
4 | from .inactivity import InactivityCog
5 | from .server_info import ServerInfoCog
6 | from .user_info import UserInfoCog
7 |
8 |
9 | __all__ = ["BotInfoCog", "HeartbeatCog", "HelpCog", "InactivityCog", "ServerInfoCog", "UserInfoCog"]
10 |
--------------------------------------------------------------------------------
/information/bot_info/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import BotInfoCog
2 |
3 |
4 | __all__ = ["BotInfoCog"]
5 |
--------------------------------------------------------------------------------
/information/bot_info/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | github = MaterialColors.teal[800]
6 | info = MaterialColors.indigo
7 | version = MaterialColors.indigo
8 |
--------------------------------------------------------------------------------
/information/bot_info/documentation.md:
--------------------------------------------------------------------------------
1 | # Bot Info
2 |
3 | Contains information about the bot and its tasks.
4 |
5 |
6 | ## `info`
7 |
8 | Shows information about the bot.
9 |
10 | ```css
11 | .[info|infos|about]
12 | ```
13 |
14 | The information given by this command includes:
15 |
16 | - Bot name and description
17 | - Author
18 | - Contributors
19 | - Version
20 | - Number of enabled cogs
21 | - Github repository
22 | - PyDrocsid [Discord](../../../discord){target=_blank} and [GitHub](https://github.com/PyDrocsid){target=_blank} links
23 | - Prefix
24 | - Help command
25 | - Where to submit bug reports and feature requests
26 |
27 |
28 | ## `version`
29 |
30 | Returns the bot's current version.
31 |
32 | ```css
33 | .[version|v]
34 | ```
35 |
36 |
37 | ## `github`
38 |
39 | Returns information about the bot's GitHub repository.
40 |
41 | ```css
42 | .[github|gh]
43 | ```
44 |
45 |
46 | ## `contributors`
47 |
48 | Returns a list of all people that contributed to the bot.
49 |
50 | ```css
51 | .[contributors|contri|con]
52 | ```
53 |
54 |
55 | ## `cogs`
56 |
57 | Returns a list of all cogs currently in use.
58 |
59 | ```css
60 | .cogs
61 | ```
62 |
63 |
64 | ## Status Message
65 |
66 | The bot displays a status message that is updated every 20 seconds. The list of status strings is defined under the `profile_status` [translation key](../../../library/translations/){target=_blank}.
67 |
--------------------------------------------------------------------------------
/information/bot_info/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | admininfo: view admininfo
3 |
4 | commands:
5 | github: return the github link
6 | version: show version
7 | info: show information about the bot
8 | admininfo: show information about the bot (admin view)
9 | contributors: show list of contributors
10 | cogs: show list of enabled cogs
11 |
12 | author_title: Author
13 | cnt_contributors:
14 | one: "{cnt} Contributor"
15 | many: "{cnt} Contributors"
16 | github_title: GitHub Repository
17 | version_title: Version
18 | prefix_title: Prefix
19 | help_command_title: Help Command
20 | bugs_features_title: Bug Reports / Feature Requests
21 |
22 | profile_status:
23 | - Developed by Defelo#2022
24 | - github.com/PyDrocsid
25 |
26 | bot_description: Default Bot Description
27 | bugs_features: Please [create an issue]({repo}/issues/new) in the GitHub repository.
28 |
29 | enabled_cogs: Enabled Cogs
30 | cnt_cogs_enabled:
31 | one: "{cnt} Cog"
32 | many: "{cnt} Cogs"
33 |
34 | pydrocsid: PyDrocsid
35 | pydrocsid_info: |
36 | **Discord Server:** https://discord.gg/{code}
37 | **GitHub Organization:** https://github.com/PyDrocsid
38 |
--------------------------------------------------------------------------------
/information/heartbeat/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import HeartbeatCog
2 |
3 |
4 | __all__ = ["HeartbeatCog"]
5 |
--------------------------------------------------------------------------------
/information/heartbeat/cog.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from pathlib import Path
3 | from typing import Optional
4 |
5 | from discord import Forbidden, User
6 | from discord.ext import tasks
7 | from discord.utils import format_dt, utcnow
8 |
9 | from PyDrocsid.cog import Cog
10 | from PyDrocsid.config import Config
11 | from PyDrocsid.environment import OWNER_ID
12 | from PyDrocsid.translations import t
13 | from PyDrocsid.util import send_editable_log
14 |
15 | from ...contributor import Contributor
16 |
17 |
18 | tg = t.g
19 | t = t.heartbeat
20 |
21 |
22 | class HeartbeatCog(Cog, name="Heartbeat"):
23 | CONTRIBUTORS = [Contributor.Defelo, Contributor.wolflu]
24 |
25 | def __init__(self):
26 | super().__init__()
27 |
28 | self.initialized = False
29 |
30 | def get_owner(self) -> Optional[User]:
31 | return self.bot.get_user(OWNER_ID)
32 |
33 | @tasks.loop(seconds=20)
34 | async def status_loop(self):
35 | if (owner := self.get_owner()) is None:
36 | return
37 | try:
38 | await send_editable_log(
39 | owner,
40 | t.online_status,
41 | t.status_description(Config.NAME, Config.VERSION),
42 | t.heartbeat,
43 | format_dt(now := utcnow(), style="D") + " " + format_dt(now, style="T"),
44 | )
45 | Path("health").write_text(str(int(datetime.now().timestamp())))
46 | except Forbidden:
47 | pass
48 |
49 | async def on_ready(self):
50 | if (owner := self.get_owner()) is not None:
51 | try:
52 | await send_editable_log(
53 | owner,
54 | t.online_status,
55 | t.status_description(Config.NAME, Config.VERSION),
56 | t.logged_in,
57 | format_dt(now := utcnow(), style="D") + " " + format_dt(now, style="T"),
58 | force_resend=True,
59 | force_new_embed=not self.initialized,
60 | )
61 | except Forbidden:
62 | pass
63 |
64 | if owner is not None:
65 | try:
66 | self.status_loop.start()
67 | except RuntimeError:
68 | self.status_loop.restart()
69 |
70 | self.initialized = True
71 |
--------------------------------------------------------------------------------
/information/heartbeat/documentation.md:
--------------------------------------------------------------------------------
1 | # Heartbeat
2 |
3 | Contains the heartbeat function, which sends a status embed to the bot owner every 20 seconds.
4 |
5 | The information given is when the bot was last started and when it last updated the embed. With each restart, a new embed is sent, which the bot edits every 20 seconds until it is switched off. This is helpful for troubleshooting, for example if the bot freezes.
6 |
7 | This cog is also responsible for updating the Docker healthcheck. If this cog is not loaded, the healthcheck will constantly fail (if not explicitly disabled).
8 |
9 | !!! Note
10 | To set the owner, you have to set `OWNER_ID` in the `.env` file.
11 |
--------------------------------------------------------------------------------
/information/heartbeat/translations/en.yml:
--------------------------------------------------------------------------------
1 | online_status: ":vertical_traffic_light: Online Status"
2 | status_description: ":label: **{} v{}**"
3 | logged_in: ":robot: Logged in"
4 | heartbeat: ":heartbeat: Heartbeat"
5 |
--------------------------------------------------------------------------------
/information/help/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import HelpCog
2 |
3 |
4 | __all__ = ["HelpCog"]
5 |
--------------------------------------------------------------------------------
/information/help/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | help = 0x008080
6 |
--------------------------------------------------------------------------------
/information/help/documentation.md:
--------------------------------------------------------------------------------
1 | # Help
2 |
3 | Contains the `.help` command, which can be used to get either a list of all available commands or detailed information about a specific command.
4 |
5 |
6 | ## `.help`
7 |
8 | ```css
9 | .help [cog|command]
10 | ```
11 |
12 | Arguments:
13 |
14 | | Argument | Required | Description |
15 | |:---------:|:--------:|:------------------------------------------------------------|
16 | | `cog` | | The cog whose command list is requested |
17 | | `command` | | The bot command for which detailed information is requested |
18 |
19 |
20 | ### Command List
21 |
22 | You can either use `.help` to get a list of all commands grouped by cogs or `.help ` to list all commands of a given cog.
23 |
24 | !!! note
25 | The command list does not include commands that cannot be executed by the requesting user (e.g. due to missing permissions).
26 |
27 |
28 | ### Detailed Information
29 |
30 | Detailed information about a given command is provided by executing `.help `.
31 |
32 | The information given by this command includes:
33 |
34 | - Aliases
35 | - Parameters
36 | - Description
37 | - Subcommands
38 | - Required/Optional permissions
39 | - Link to the PyDrocsid documentation
40 |
--------------------------------------------------------------------------------
/information/help/translations/en.yml:
--------------------------------------------------------------------------------
1 | commands:
2 | help: shows this message
3 |
4 | cog_or_command_not_found: This cog or command does not exist.
5 | description: Description
6 | subcommands: Subcommands
7 | no_category: No category
8 | help: Help
9 | help_usage: |
10 | Type `{0}help ` for more info on a command.
11 | Type `{0}help ` for more info on a cog.
12 | required_permissions: Required Permissions
13 | required_permission_level: Required Permission Level
14 | optional_permissions: Optional Permissions
15 | documentation: Documentation
16 |
--------------------------------------------------------------------------------
/information/inactivity/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import InactivityCog
2 |
3 |
4 | __all__ = ["InactivityCog"]
5 |
--------------------------------------------------------------------------------
/information/inactivity/documentation.md:
--------------------------------------------------------------------------------
1 | # Inactivity
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/information/inactivity/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime
4 | from typing import Union
5 |
6 | from sqlalchemy import BigInteger, Column
7 |
8 | from PyDrocsid.database import Base, UTCDateTime, db
9 |
10 |
11 | class Activity(Base):
12 | __tablename__ = "activity"
13 |
14 | id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
15 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
16 |
17 | @staticmethod
18 | async def create(object_id: int, timestamp: datetime) -> Activity:
19 | row = Activity(id=object_id, timestamp=timestamp)
20 | await db.add(row)
21 | return row
22 |
23 | @staticmethod
24 | async def update(object_id: int, timestamp: datetime) -> Activity:
25 | if not (row := await db.get(Activity, id=object_id)):
26 | row = await Activity.create(object_id, timestamp)
27 | elif timestamp > row.timestamp:
28 | row.timestamp = timestamp
29 | return row
30 |
--------------------------------------------------------------------------------
/information/inactivity/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class InactivityPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.inactivity.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 | scan = auto()
15 |
--------------------------------------------------------------------------------
/information/inactivity/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class InactivitySettings(Settings):
5 | inactive_days = 14
6 |
--------------------------------------------------------------------------------
/information/inactivity/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: list inactive users and read inactivity configuration
3 | write: write inactivity configuration
4 | scan: scan all channels for latest message of each user
5 |
6 | scanning: "Scanning..."
7 | scanning_channel:
8 | one: "Scanning {cnt} channel ({} / {} done):"
9 | many: "Scanning {cnt} channels ({} / {} done):"
10 | scan_complete:
11 | one: "Scanned {cnt} channel."
12 | many: "Scanned {cnt} channels."
13 | updating_members: "Updating members..."
14 | updated_members:
15 | one: "Updated {cnt} member."
16 | many: "Updated {cnt} members."
17 | activity: Activity
18 | status:
19 | active: ":white_check_mark: Active ({})"
20 | inactive: ":warning: Inactive"
21 | inactive_since: ":warning: Inactive ({})"
22 | inactive_users: Inactive Users
23 | inactive_users_cnt: Inactive Users ({})
24 | no_inactive_users: "No inactive users :white_check_mark:"
25 | user_inactive: "{} {} ({})"
26 | user_inactive_since: "{} {} ({}, {})"
27 | inactive_duration:
28 | one: Users that have not sent a message for {cnt} day are marked as inactive.
29 | many: Users that have not sent a message for {cnt} days are marked as inactive.
30 | inactive_duration_set:
31 | one: Inactivity duration has been set to {cnt} day.
32 | many: Inactivity duration has been set to {cnt} days.
33 |
--------------------------------------------------------------------------------
/information/server_info/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import ServerInfoCog
2 |
3 |
4 | __all__ = ["ServerInfoCog"]
5 |
--------------------------------------------------------------------------------
/information/server_info/cog.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | from discord import Embed, Guild, Member, Status
4 | from discord.ext import commands
5 | from discord.ext.commands import Context, UserInputError, guild_only
6 |
7 | from PyDrocsid.cog import Cog
8 | from PyDrocsid.command import docs, reply
9 | from PyDrocsid.embeds import send_long_embed
10 | from PyDrocsid.translations import t
11 |
12 | from .colors import Colors
13 | from ...contributor import Contributor
14 |
15 |
16 | tg = t.g
17 | t = t.server_info
18 |
19 |
20 | class ServerInfoCog(Cog, name="Server Information"):
21 | CONTRIBUTORS = [Contributor.Defelo]
22 |
23 | async def get_users(self, guild: Guild) -> list[tuple[str, list[Member]]]:
24 | return []
25 |
26 | async def get_additional_fields(self, guild: Guild) -> list[tuple[str, str]]:
27 | return []
28 |
29 | @commands.group()
30 | @guild_only()
31 | @docs(t.commands.server)
32 | async def server(self, ctx: Context):
33 | if ctx.subcommand_passed is not None:
34 | if ctx.invoked_subcommand is None:
35 | raise UserInputError
36 | return
37 |
38 | guild: Guild = ctx.guild
39 | embed = Embed(title=guild.name, description=t.info_description, color=Colors.ServerInformation)
40 | if guild.icon:
41 | embed.set_thumbnail(url=guild.icon.url)
42 | created = guild.created_at.date()
43 | embed.add_field(name=t.creation_date, value=f"{created.day}.{created.month}.{created.year}")
44 | online_count = sum([m.status != Status.offline for m in guild.members])
45 | embed.add_field(name=t.cnt_members(cnt=guild.member_count), value=t.cnt_online(online_count))
46 | embed.add_field(name=t.owner, value=guild.owner.mention)
47 |
48 | for title, users in await self.get_users(guild):
49 | embed.add_field(name=title, value="\n".join(":small_orange_diamond: " + m.mention for m in users))
50 |
51 | bots = [m for m in guild.members if m.bot]
52 | bots_online = sum([m.status != Status.offline for m in bots])
53 | embed.add_field(name=t.cnt_bots(cnt=len(bots)), value=t.cnt_online(bots_online))
54 |
55 | for name, value in await self.get_additional_fields(guild):
56 | embed.add_field(name=name, value=value)
57 |
58 | await send_long_embed(ctx, embed)
59 |
60 | @server.command(name="bots")
61 | @docs(t.commands.bots)
62 | async def server_bots(self, ctx: Context):
63 | guild: Guild = ctx.guild
64 | online: List[Member] = []
65 | offline: List[Member] = []
66 | for member in guild.members: # type: Member
67 | if member.bot:
68 | [offline, online][member.status != Status.offline].append(member)
69 |
70 | cnt = len(online) + len(offline)
71 | embed = Embed(title=t.cnt_bots(cnt=cnt), color=Colors.ServerInformation)
72 |
73 | if not cnt:
74 | embed.colour = Colors.error
75 | embed.description = t.no_bots
76 | await reply(ctx, embed=embed)
77 | return
78 |
79 | if cnt := len(online):
80 | embed.add_field(
81 | name=t.online(cnt=cnt), value="\n".join(":small_orange_diamond: " + m.mention for m in online)
82 | )
83 | if cnt := len(offline):
84 | embed.add_field(
85 | name=t.offline(cnt=cnt), value="\n".join(":small_blue_diamond: " + m.mention for m in offline)
86 | )
87 |
88 | await send_long_embed(ctx, embed=embed)
89 |
--------------------------------------------------------------------------------
/information/server_info/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | ServerInformation = MaterialColors.indigo
6 |
--------------------------------------------------------------------------------
/information/server_info/documentation.md:
--------------------------------------------------------------------------------
1 | # Server Info
2 |
3 | Contains commands to show information about the server and its bots.
4 |
5 |
6 | ## `server`
7 |
8 | Shows information about the server the command is executed in.
9 |
10 | ```css
11 | .server
12 | ```
13 |
14 | The server information given by this command include:
15 |
16 | - Server name and icon
17 | - Creation date
18 | - Member count (total and online counts)
19 | - Owner
20 | - Team roles and their members
21 | - Bot count (total and online counts)
22 | - Custom information about the server
23 |
24 |
25 | ### `bots`
26 |
27 | Shows a list of all bots in this server and their online statuses.
28 |
29 | ```css
30 | .server bots
31 | ```
32 |
--------------------------------------------------------------------------------
/information/server_info/translations/en.yml:
--------------------------------------------------------------------------------
1 | commands:
2 | server: displays information about this discord server
3 | bots: list all bots on the server
4 |
5 | info_description: Information about this Discord Server
6 | creation_date: Creation Date
7 | cnt_members:
8 | one: "{cnt} Member"
9 | many: "{cnt} Members"
10 | cnt_online: "{} online"
11 | owner: Owner
12 | cnt_bots:
13 | one: "{cnt} Bot"
14 | many: "{cnt} Bots"
15 | no_bots: No bots found.
16 | online: Online ({cnt})
17 | offline: Offline ({cnt})
18 |
--------------------------------------------------------------------------------
/information/user_info/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import UserInfoCog
2 |
3 |
4 | __all__ = ["UserInfoCog"]
5 |
--------------------------------------------------------------------------------
/information/user_info/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | UserInfo = MaterialColors.green[800]
6 | stats = MaterialColors.green
7 | userlog = MaterialColors.green["a400"]
8 | joined = 0x339204
9 |
--------------------------------------------------------------------------------
/information/user_info/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime, timedelta
4 | from typing import Optional, Union
5 |
6 | from discord.utils import utcnow
7 | from sqlalchemy import BigInteger, Boolean, Column, Integer, Text
8 |
9 | from PyDrocsid.database import Base, UTCDateTime, db, filter_by
10 |
11 |
12 | class Join(Base):
13 | __tablename__ = "join"
14 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
15 | member: Union[Column, int] = Column(BigInteger)
16 | member_name: Union[Column, str] = Column(Text)
17 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
18 | join_msg_channel_id: Union[Column, int] = Column(BigInteger, nullable=True)
19 | join_msg_id: Union[Column, int] = Column(BigInteger, nullable=True)
20 |
21 | @staticmethod
22 | async def create(member: int, member_name: str, timestamp: Optional[datetime] = None) -> Join:
23 | row = Join(member=member, member_name=member_name, timestamp=timestamp or utcnow())
24 | await db.add(row)
25 | await db.session.flush()
26 | return row
27 |
28 | @staticmethod
29 | async def update(member: int, member_name: str, joined_at: datetime):
30 | if await db.exists(filter_by(Join, member=member).filter(Join.timestamp >= joined_at - timedelta(minutes=1))):
31 | return
32 |
33 | await Join.create(member, member_name, joined_at)
34 |
35 |
36 | class Leave(Base):
37 | __tablename__ = "leave"
38 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
39 | member: Union[Column, int] = Column(BigInteger)
40 | member_name: Union[Column, str] = Column(Text)
41 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
42 |
43 | @staticmethod
44 | async def create(member: int, member_name: str) -> Leave:
45 | row = Leave(member=member, member_name=member_name, timestamp=utcnow())
46 | await db.add(row)
47 | return row
48 |
49 |
50 | class UsernameUpdate(Base):
51 | __tablename__ = "username_update"
52 |
53 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
54 | member: Union[Column, int] = Column(BigInteger)
55 | member_name: Union[Column, str] = Column(Text)
56 | new_name: Union[Column, str] = Column(Text)
57 | nick: Union[Column, bool] = Column(Boolean)
58 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
59 |
60 | @staticmethod
61 | async def create(member: int, member_name: str, new_name: str, nick: bool) -> UsernameUpdate:
62 | row = UsernameUpdate(member=member, member_name=member_name, new_name=new_name, nick=nick, timestamp=utcnow())
63 | await db.add(row)
64 | return row
65 |
66 |
67 | class Verification(Base):
68 | __tablename__ = "verification"
69 |
70 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
71 | member: Union[Column, int] = Column(BigInteger)
72 | member_name: Union[Column, str] = Column(Text)
73 | accepted: Union[Column, bool] = Column(Boolean)
74 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
75 |
76 | @staticmethod
77 | async def create(member: int, member_name: str, accepted: bool) -> Verification:
78 | row = Verification(member=member, member_name=member_name, accepted=accepted, timestamp=utcnow())
79 | await db.add(row)
80 | return row
81 |
--------------------------------------------------------------------------------
/information/user_info/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class UserInfoPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.user_info.permissions[self.name]
11 |
12 | view_userinfo = auto()
13 | view_userlog = auto()
14 | init_join_log = auto()
15 |
--------------------------------------------------------------------------------
/information/user_info/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | view_userinfo: view other user's information
3 | view_userlog: view other user's log
4 | init_join_log: initialize join log
5 |
6 | not_allowed: You are not allowed to access this information.
7 |
8 | could_not_send_dm: Message could not be sent. Please enable direct messages from this server!
9 | userinfo: User Information
10 | userlogs: User Logs
11 | membership: Membership
12 | member_since: ":clock2: Member since {}"
13 | not_a_member: ":x: User is not a member of this server."
14 |
15 | ulog:
16 | created: ":sparkles: **Created** this account."
17 | joined: ":tada: **Joined** this server as `{}`."
18 | left: ":door: **Left** this server."
19 | nick:
20 | set: ":label: **Nickname set** to `{}`"
21 | updated: ":label: **Nickname changed** from `{}` to `{}`"
22 | cleared: ":label: **Nickname** `{}` **cleared**"
23 | username_updated: ":label: **Username changed** from `{}` to `{}`"
24 | verification:
25 | accepted: ":green_circle: **Completed verification**"
26 | revoked: ":red_circle: **Revoked verification**"
27 |
28 | init_join_log: Join Log Initialization
29 | filling_join_log:
30 | one: ":hourglass_flowing_sand: Creating join log entries for {cnt} member. This may take a while."
31 | many: ":hourglass_flowing_sand: Creating join log entries for {cnt} members. This may take a while."
32 | join_log_filled: "Join log has been initialized successfully. :white_check_mark:"
33 |
34 | joined_days: "joined less than a week ago"
35 | joined_weeks:
36 | one: "joined {cnt} week ago"
37 | many: "joined {cnt} weeks ago"
38 | joined_months:
39 | one: "joined {cnt} month ago"
40 | many: "joined {cnt} months ago"
41 | joined_years:
42 | one: "joined {cnt} year ago"
43 | many: "joined {cnt} years ago"
44 |
--------------------------------------------------------------------------------
/integrations/__init__.py:
--------------------------------------------------------------------------------
1 | from .adventofcode import AdventOfCodeCog
2 | from .cleverbot import CleverBotCog
3 | from .python_docs import PythonDocsCog
4 | from .reddit import RedditCog
5 | from .run_code import RunCodeCog
6 |
7 |
8 | __all__ = ["AdventOfCodeCog", "CleverBotCog", "PythonDocsCog", "RedditCog", "RunCodeCog"]
9 |
--------------------------------------------------------------------------------
/integrations/adventofcode/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import AdventOfCodeCog
2 |
3 |
4 | __all__ = ["AdventOfCodeCog"]
5 |
--------------------------------------------------------------------------------
/integrations/adventofcode/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | AdventOfCode = 0x009900 # advent of code color
6 |
--------------------------------------------------------------------------------
/integrations/adventofcode/documentation.md:
--------------------------------------------------------------------------------
1 | # Advent of Code Integration
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/integrations/adventofcode/models.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy import BigInteger, Column, Text
4 |
5 | from PyDrocsid.database import Base, db
6 |
7 |
8 | class AOCLink(Base):
9 | __tablename__ = "aoc_link"
10 |
11 | discord_id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
12 | aoc_id: Union[Column, str] = Column(Text, unique=True)
13 | solutions: Union[Column, str] = Column(Text, nullable=True)
14 |
15 | @staticmethod
16 | async def create(discord_id: int, aoc_id: str) -> "AOCLink":
17 | link = AOCLink(discord_id=discord_id, aoc_id=aoc_id)
18 | await db.add(link)
19 | return link
20 |
21 | @staticmethod
22 | async def publish(discord_id: int, url: str):
23 | row = await db.get(AOCLink, discord_id=discord_id)
24 | row.solutions = url
25 |
26 | @staticmethod
27 | async def unpublish(discord_id: int):
28 | row = await db.get(AOCLink, discord_id=discord_id)
29 | row.solutions = None
30 |
--------------------------------------------------------------------------------
/integrations/adventofcode/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class AdventOfCodePermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.adventofcode.permissions[self.name]
11 |
12 | clear = auto()
13 | link_read = auto()
14 | link_write = auto()
15 | role_read = auto()
16 | role_write = auto()
17 |
--------------------------------------------------------------------------------
/integrations/adventofcode/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class AdventOfCodeSettings(Settings):
5 | role = -1
6 | rank = 10
7 |
--------------------------------------------------------------------------------
/integrations/adventofcode/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | clear: clear aoc leaderboard cache
3 | link_read: read aoc links
4 | link_write: write aoc links
5 | role_read: read aoc role configuration
6 | role_write: write aoc role configuration
7 |
8 | join_title: How to Join the Private Leaderboard
9 | join_instructions: |
10 | 1. Log in at https://adventofcode.com/
11 | 2. Go to https://adventofcode.com/leaderboard/private
12 | 3. Enter the code `{}`
13 | Good Luck, Have Fun! :christmas_tree: :gift:
14 |
15 | leaderboard_header: "**Private Leaderboard (Advent of Code {})**"
16 | last_update: "Last Update:"
17 |
18 | links: Advent of Code - User Links
19 | no_links: No member has been linked yet.
20 | link_already_exists: A link already exists for this user.
21 | link_created: Link has been created.
22 | link_not_found: Link not found.
23 | link_removed: Link has been removed.
24 |
25 | aoc_role: Advent of Code - Role
26 | min_rank: Minimum Rank
27 | role_set: Advent of Code role has been configured.
28 | log_role_set: "Advent of Code role has been set to `@{}` ({})."
29 | role_disabled: Advent of Code role has been disabled.
30 | rank_set: Minimum rank for Advent of Code role has been configured.
31 | log_rank_set: Minimum rank for Advent of Code role has been set to {}.
32 | invalid_rank: Invalid rank.
33 |
34 | not_verified: You have to be verified to use this command.
35 | invalid_url: Invalid URL. Only GitHub repositories are allowed.
36 | published: Repository has been published successfully.
37 | not_published: No repository has been published yet.
38 | unpublished: Repository has been removed from your profile.
39 |
40 | solutions: Advent of Code - Solution Repositories
41 | no_solutions: No solutions have been published yet.
42 |
--------------------------------------------------------------------------------
/integrations/cleverbot/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import CleverBotCog
2 |
3 |
4 | __all__ = ["CleverBotCog"]
5 |
--------------------------------------------------------------------------------
/integrations/cleverbot/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | CleverBot = 0x8EBBF6 # clever bot color
6 |
--------------------------------------------------------------------------------
/integrations/cleverbot/documentation.md:
--------------------------------------------------------------------------------
1 | # CleverBot
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/integrations/cleverbot/models.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy import BigInteger, Column
4 |
5 | from PyDrocsid.database import Base, db
6 |
7 |
8 | class CleverBotChannel(Base):
9 | __tablename__ = "cleverbot_channel"
10 |
11 | channel: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
12 |
13 | @staticmethod
14 | async def create(channel: int) -> "CleverBotChannel":
15 | row = CleverBotChannel(channel=channel)
16 | await db.add(row)
17 | return row
18 |
--------------------------------------------------------------------------------
/integrations/cleverbot/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class CleverBotPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.cleverbot.permissions[self.name]
11 |
12 | list = auto()
13 | manage = auto()
14 | reset = auto()
15 |
--------------------------------------------------------------------------------
/integrations/cleverbot/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | list: list cleverbot channels
3 | manage: manage cleverbot
4 | reset: reset cleverbot sessions
5 |
6 | cleverbot: Cleverbot
7 | whitelisted_channels: Whitelisted Channels
8 | no_whitelisted_channels: No whitelisted channels.
9 | channel_already_whitelisted: Channel is already whitelisted.
10 | channel_whitelisted: "Channel has been whitelisted. :white_check_mark:"
11 | log_channel_whitelisted: "**Channel** {} has been **whitelisted** for CleverBot."
12 | channel_not_whitelisted: ":x: Channel is not whitelisted."
13 | channel_removed: "Channel has been removed from the whitelist. :white_check_mark:"
14 | log_channel_removed: "**Channel** {} has been **removed** from the CleverBot whitelist."
15 | session_reset: Session for {} was successfully reset.
16 |
--------------------------------------------------------------------------------
/integrations/python_docs/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import PythonDocsCog
2 |
3 |
4 | __all__ = ["PythonDocsCog"]
5 |
--------------------------------------------------------------------------------
/integrations/python_docs/colors.py:
--------------------------------------------------------------------------------
1 | from discord import Color
2 |
3 | from PyDrocsid.material_colors import MaterialColors
4 |
5 |
6 | class Colors(MaterialColors):
7 | PythonDocs = Color.blurple()
8 |
--------------------------------------------------------------------------------
/integrations/python_docs/documentation.md:
--------------------------------------------------------------------------------
1 | # Python Documentation
2 |
3 | Contains commands to access and search the documentation of [Python](https://docs.python.org/3/){target=_blank} and [pycord](https://docs.pycord.dev/en/master/){target=_blank}. When using these commands to search for Python entities (e.g. functions, classes, objects, modules), the documentation is downloaded into the Redis cache to avoid repetitive queries.
4 |
5 |
6 | ## `python_docs`
7 |
8 | Searches the [Python documentation](https://docs.python.org/3/){target=_blank} for any Python entity.
9 |
10 | ```css
11 | .[python_docs|py] [entity]
12 | ```
13 |
14 | Arguments:
15 |
16 | | Argument | Required |Description |
17 | |:--------:|:--------:|:----------------------------------------------------------------------------------------------------------------------|
18 | | `entity` | | The name of the Python entity to search for. If omitted, you get the direct link to the Python documentation instead. |
19 |
20 |
21 | ## `pycord_docs`
22 |
23 | Searches the [pycord documentation](https://docs.pycord.dev/en/master/){target=_blank} for any pycord entity.
24 |
25 | ```css
26 | .[pycord_docs|pycord|pyc|dpy] [entity]
27 | ```
28 |
29 | Arguments:
30 |
31 | | Argument | Required | Description |
32 | |:--------:|:--------:|:----------------------------------------------------------------------------------------------------------------------|
33 | | `entity` | | The name of the pycord entity to search for. If omitted, you get the direct link to the pycord documentation instead. |
34 |
--------------------------------------------------------------------------------
/integrations/python_docs/translations/en.yml:
--------------------------------------------------------------------------------
1 | no_results: Could not find anything. Sorry.
2 | documentation: "{} Documentation"
3 |
--------------------------------------------------------------------------------
/integrations/reddit/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import RedditCog
2 |
3 |
4 | __all__ = ["RedditCog"]
5 |
--------------------------------------------------------------------------------
/integrations/reddit/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Reddit = 0xFF4500 # Reddit color
6 |
--------------------------------------------------------------------------------
/integrations/reddit/documentation.md:
--------------------------------------------------------------------------------
1 | # Reddit
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/integrations/reddit/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime, timedelta
4 | from typing import Union
5 |
6 | from discord.utils import utcnow
7 | from sqlalchemy import BigInteger, Column, String
8 |
9 | from PyDrocsid.database import Base, UTCDateTime, db, delete, filter_by
10 |
11 |
12 | class RedditChannel(Base):
13 | __tablename__ = "reddit_channel"
14 |
15 | subreddit: Union[Column, str] = Column(String(32), primary_key=True)
16 | channel: Union[Column, int] = Column(BigInteger, primary_key=True)
17 |
18 | @staticmethod
19 | async def create(subreddit: str, channel: int) -> RedditChannel:
20 | row = RedditChannel(subreddit=subreddit, channel=channel)
21 | await db.add(row)
22 | return row
23 |
24 |
25 | class RedditPost(Base):
26 | __tablename__ = "reddit_post"
27 |
28 | post_id: Union[Column, str] = Column(String(16), primary_key=True, unique=True)
29 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
30 |
31 | @staticmethod
32 | async def create(post_id: str) -> RedditPost:
33 | row = RedditPost(post_id=post_id, timestamp=utcnow())
34 | await db.add(row)
35 | return row
36 |
37 | @staticmethod
38 | async def clean():
39 | drop_before_timestamp = utcnow() - timedelta(weeks=1)
40 | await db.exec(delete(RedditPost).filter(RedditPost.timestamp < drop_before_timestamp))
41 |
42 | @staticmethod
43 | async def post(post_id: str) -> bool:
44 | if await db.exists(filter_by(RedditPost, post_id=post_id)):
45 | return False
46 |
47 | await RedditPost.create(post_id)
48 | return True
49 |
--------------------------------------------------------------------------------
/integrations/reddit/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class RedditPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.reddit.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 | trigger = auto()
15 |
--------------------------------------------------------------------------------
/integrations/reddit/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class RedditSettings(Settings):
5 | interval = 4
6 | limit = 4
7 | filter_nsfw = True
8 |
--------------------------------------------------------------------------------
/integrations/reddit/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: read reddit integration configuration
3 | write: write reddit integration configuration
4 | trigger: manually pull hot posts and reset timer
5 |
6 | reddit: Reddit
7 | interval: Interval
8 | x_hours:
9 | one: "{cnt} hour"
10 | many: "{cnt} hours"
11 | limit: Limit
12 | reddit_links: Reddit links
13 | no_reddit_links: No reddit links have been created yet.
14 | subreddit_not_found: Subreddit does not exist.
15 | reddit_link_not_created_permission: >
16 | Reddit link could not be created because I don't have `send_messages` permission in this channel.
17 | reddit_link_already_exists: Reddit link already exists.
18 | reddit_link_created: Reddit link has been created.
19 | log_reddit_link_created: "**Reddit link** `r/{}` -> {} has been **created**."
20 | reddit_link_not_found: Reddit link does not exist.
21 | reddit_link_removed: "Reddit link has been removed. :white_check_mark:"
22 | log_reddit_link_removed: "**Reddit link** `r/{}` -> {} has been **removed**."
23 | done: "Done :white_check_mark:"
24 | reddit_interval:
25 | one: "Reddit lookup interval: {cnt} hour"
26 | many: "Reddit lookup interval: {cnt} hours"
27 | invalid_interval: Invalid interval.
28 | reddit_interval_set: "Reddit lookup interval has been updated. :white_check_mark:"
29 | log_reddit_interval_set:
30 | one: "**Reddit lookup interval** has been **set** to {cnt} hour."
31 | many: "**Reddit lookup interval** has been **set** to {cnt} hours."
32 | invalid_limit: Invalid limit.
33 | reddit_limit: "Reddit limit: {}"
34 | reddit_limit_set: "Reddit limit has been updated. :white_check_mark:"
35 | log_reddit_limit_set: "**Reddit limit** has been **set** to {}."
36 | could_not_fetch: Could not fetch reddit posts of r/{}.
37 | cannot_send: Reddit integration cannot send messages to {}.
38 | nsfw_filter: "NSFW filter"
39 | nsfw_filter_now_enabled: "Reddit NSFW filter has been enabled."
40 | log_nsfw_filter_now_enabled: "**Reddit NSFW filter** has been **enabled**."
41 | nsfw_filter_now_disabled: "Reddit NSFW filter has been disabled."
42 | log_nsfw_filter_now_disabled: "**Reddit NSFW filter** has been **disabled**."
43 |
--------------------------------------------------------------------------------
/integrations/run_code/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import RunCodeCog
2 |
3 |
4 | __all__ = ["RunCodeCog"]
5 |
--------------------------------------------------------------------------------
/integrations/run_code/api.py:
--------------------------------------------------------------------------------
1 | from aiohttp import ClientSession
2 |
3 |
4 | class PistonException(BaseException):
5 | @property
6 | def error(self) -> str:
7 | return self.args[0]["message"]
8 |
9 |
10 | class PistonAPI:
11 | ENVIRONMENTS_URL = "https://emkc.org/api/v2/piston/runtimes"
12 | EXECUTE_URL = "https://emkc.org/api/v2/piston/execute"
13 |
14 | def __init__(self):
15 | self.environments: dict[str, str] = {}
16 | self.aliases: dict[str, str] = {}
17 |
18 | def get_language(self, language: str) -> str | None:
19 | if language in self.environments:
20 | return language
21 |
22 | return self.aliases.get(language)
23 |
24 | async def load_environments(self):
25 | async with ClientSession() as session, session.get(self.ENVIRONMENTS_URL) as response:
26 | if response.status != 200:
27 | raise PistonException
28 |
29 | environments = await response.json()
30 |
31 | self.environments = {env["language"]: env["version"] for env in environments}
32 | self.aliases = {alias: env["language"] for env in environments for alias in env["aliases"]}
33 |
34 | async def run_code(self, language: str, source: str, stdin: str = "") -> dict:
35 | async with ClientSession() as session, session.post(
36 | PistonAPI.EXECUTE_URL,
37 | json={
38 | "language": language,
39 | "version": self.environments[language],
40 | "stdin": stdin,
41 | "files": [{"content": source}],
42 | },
43 | ) as response:
44 | if response.status != 200:
45 | raise PistonException(await response.json())
46 |
47 | return await response.json()
48 |
--------------------------------------------------------------------------------
/integrations/run_code/cog.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from aiohttp import ClientError
4 | from discord import Embed
5 | from discord.ext import commands
6 | from discord.ext.commands import CommandError, Context, UserInputError
7 | from sentry_sdk import capture_exception
8 |
9 | from PyDrocsid.cog import Cog
10 | from PyDrocsid.command import docs
11 | from PyDrocsid.embeds import send_long_embed
12 | from PyDrocsid.logger import get_logger
13 | from PyDrocsid.material_colors import MaterialColors
14 | from PyDrocsid.translations import t
15 |
16 | from .api import PistonAPI, PistonException
17 | from ...contributor import Contributor
18 |
19 |
20 | logger = get_logger(__name__)
21 |
22 | tg = t.g
23 | t = t.run_code
24 |
25 | N_CHARS = 1000
26 |
27 |
28 | class RunCodeCog(Cog, name="Run Code"):
29 | CONTRIBUTORS = [Contributor.Florian, Contributor.Defelo]
30 |
31 | def __init__(self):
32 | self.api = PistonAPI()
33 |
34 | async def on_ready(self):
35 | logger.info("Loading piston environments")
36 | try:
37 | await self.api.load_environments()
38 | except PistonException:
39 | logger.error("Could not load piston environments!")
40 | return
41 |
42 | self.run.help = (
43 | t.commands.run + "\n\n" + t.supported_languages(", ".join(f"`{lang}`" for lang in self.api.environments))
44 | )
45 |
46 | async def execute(self, ctx: Context, language: str, source: str, stdin: str = ""):
47 | if not (language := self.api.get_language(language)):
48 | raise CommandError(t.error_unsupported_language(language))
49 |
50 | await ctx.trigger_typing()
51 |
52 | try:
53 | result: dict = await self.api.run_code(language, source, stdin)
54 | except PistonException as e:
55 | capture_exception()
56 | raise CommandError(f"{t.error_run_code}: {e.error}")
57 | except ClientError:
58 | capture_exception()
59 | raise CommandError(t.error_run_code)
60 |
61 | output: str = result["run"]["output"]
62 | if len(output) > N_CHARS:
63 | newline = output.find("\n", N_CHARS, N_CHARS + 20)
64 | if newline == -1:
65 | newline = N_CHARS
66 | output = output[:newline] + "\n..."
67 |
68 | description = "```\n" + output.replace("`", "`\u200b") + "\n```"
69 |
70 | lang = result["language"]
71 | version = result["version"]
72 | embed = Embed(title=t.run_output(lang, version), color=MaterialColors.green, description=description)
73 | if result["run"]["code"] != 0:
74 | embed.colour = MaterialColors.error
75 |
76 | embed.set_footer(text=tg.requested_by(ctx.author, ctx.author.id), icon_url=ctx.author.display_avatar.url)
77 |
78 | await send_long_embed(ctx, embed)
79 |
80 | @commands.command(usage=t.run_usage)
81 | @docs(t.commands.run)
82 | async def run(self, ctx: Context, *, code: str):
83 | if not (match := re.fullmatch(r"(```)([a-zA-Z\d]+)\n(.+?)\1(\n(.+?))?", code, re.DOTALL)):
84 | raise UserInputError
85 |
86 | _, lang, source, _, stdin = match.groups()
87 | await self.execute(ctx, lang, source, stdin)
88 |
89 | @commands.command(aliases=["="])
90 | @docs(t.commands.eval)
91 | async def eval(self, ctx: Context, *, expr: str):
92 | if not (match := re.fullmatch(r"(`*)([a-zA-Z\d]*\n)?(.+?)\1", expr, re.DOTALL)):
93 | raise UserInputError
94 |
95 | code = (
96 | "from functools import reduce\n"
97 | "from itertools import *\n"
98 | "from operator import *\n"
99 | "from random import *\n"
100 | "from string import *\n"
101 | "from math import *\n"
102 | "print(eval(open(0).read()))"
103 | )
104 |
105 | *_, expr = match.groups()
106 | await self.execute(ctx, "python", code, expr.strip())
107 |
--------------------------------------------------------------------------------
/integrations/run_code/documentation.md:
--------------------------------------------------------------------------------
1 | # Run Code
2 |
3 | Contains commands to run code using the [Piston API](https://github.com/engineer-man/piston){target=_blank}.
4 |
5 |
6 | ## `run`
7 |
8 | Executes code using the [Piston API](https://github.com/engineer-man/piston){target=_blank}.
9 |
10 | ````css
11 | .run ```
12 |
13 | ```
14 | [stdin]
15 | ````
16 |
17 | Arguments:
18 |
19 | | Argument | Required | Description |
20 | |:-----------:|:-------------------------:|:--------------------------------------------------|
21 | | `language` | :fontawesome-solid-check: | The language to use for execution |
22 | | `your code` | :fontawesome-solid-check: | The code you want to execute |
23 | | `stdin` | | The text to pass as standard input to the program |
24 |
25 | !!! info
26 | You can use the [`/runtimes`](https://emkc.org/api/v2/piston/runtimes){target=_blank} endpoint to get a list of all supported programming languages. This list is also shown in the help embed of the `run` command (`.help run`).
27 |
28 |
29 | ## `eval`
30 |
31 | Evaluates a Python expression using the [Piston API](https://github.com/engineer-man/piston){target=_blank}.
32 |
33 | ```css
34 | .[eval|=]
35 | ```
36 |
37 | Arguments:
38 |
39 | | Argument | Required | Description |
40 | |:--------:|:-------------------------:|:----------------------------------|
41 | | `expr` | :fontawesome-solid-check: | The Python expression to evaluate |
42 |
--------------------------------------------------------------------------------
/integrations/run_code/translations/en.yml:
--------------------------------------------------------------------------------
1 | commands:
2 | run: Run some code using the [Piston API](https://github.com/engineer-man/piston)
3 | eval: Evaluate a Python expression using the [Piston API](https://github.com/engineer-man/piston)
4 |
5 | error_run_code: An error occurred while executing the code
6 | error_unsupported_language: "Unsupported language: {}"
7 | run_usage: |
8 | ```
9 |
10 | ```
11 | [stdin]
12 | run_output: Run Output ({} {})
13 | supported_languages: "Supported languages: {}"
14 |
--------------------------------------------------------------------------------
/moderation/__init__.py:
--------------------------------------------------------------------------------
1 | from .autoclear import AutoClearCog
2 | from .automod import AutoModCog
3 | from .autorole import AutoRoleCog
4 | from .content_filter import ContentFilterCog
5 | from .invites import InvitesCog
6 | from .logging import LoggingCog
7 | from .mediaonly import MediaOnlyCog
8 | from .message import MessageCog
9 | from .mod import ModCog
10 | from .role_notifications import RoleNotificationsCog
11 | from .spam_detection import SpamDetectionCog
12 | from .threads import ThreadsCog
13 | from .user_notes import UserNoteCog
14 | from .verification import VerificationCog
15 |
16 |
17 | __all__ = [
18 | "AutoClearCog",
19 | "AutoModCog",
20 | "AutoRoleCog",
21 | "ContentFilterCog",
22 | "InvitesCog",
23 | "LoggingCog",
24 | "MediaOnlyCog",
25 | "MessageCog",
26 | "ModCog",
27 | "RoleNotificationsCog",
28 | "SpamDetectionCog",
29 | "ThreadsCog",
30 | "UserNoteCog",
31 | "VerificationCog",
32 | ]
33 |
--------------------------------------------------------------------------------
/moderation/autoclear/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import AutoClearCog
2 |
3 |
4 | __all__ = ["AutoClearCog"]
5 |
--------------------------------------------------------------------------------
/moderation/autoclear/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | AutoClear = MaterialColors.pink[900]
6 |
--------------------------------------------------------------------------------
/moderation/autoclear/documentation.md:
--------------------------------------------------------------------------------
1 | # AutoClear
2 |
3 | Contains functionality to delete messages that have exceeded a certain age from configured channels. Pinned messages are excluded from this and are not deleted.
4 |
5 |
6 | ## `autoclear`
7 |
8 | Contains subcommands to manage the automatic message deletion.
9 | If not subcommand is given, this command shows a list of all configured channels with their respective TTLs (time to live) in minutes. Unpinned messages in one of those channels will be deleted when their age exceeds this TTL.
10 |
11 | Usage:
12 |
13 | ```css
14 | .[autoclear|ac]
15 | ```
16 |
17 | Required Permissions:
18 |
19 | - `autoclear.read`
20 |
21 |
22 | ### `set`
23 |
24 | Configures the TTL for a given channel.
25 |
26 | ```css
27 | .autoclear [set|s|add|a|+|=]
28 | ```
29 |
30 | Arguments:
31 |
32 | | Argument | Required | Description |
33 | |:---------:|:-------------------------:|:----------------------------------------------------|
34 | | `channel` | :fontawesome-solid-check: | The channel for which AutoClear is to be configured |
35 | | `minutes` | :fontawesome-solid-check: | The TTL in minutes |
36 |
37 | Required Permissions:
38 |
39 | - `autoclear.read`
40 | - `autoclear.write`
41 |
42 | !!! note
43 | Messages cannot be deleted exactly after the configured time has elapsed, since the task for deleting old messages only runs every 5 minutes. In the worst case, a message will therefore be deleted 5 minutes after the configured TTL has been exceeded.
44 |
45 |
46 | ### `disable`
47 |
48 | Disables AutoClear in a given channel.
49 |
50 | ```css
51 | .autoclear [disable|d|delete|del|remove|r|-]
52 | ```
53 |
54 | Arguments:
55 |
56 | | Argument | Required | Description |
57 | |:---------:|:-------------------------:|:-------------------------------------------------|
58 | | `channel` | :fontawesome-solid-check: | The channel in which AutoClear is to be disabled |
59 |
60 | Required Permissions:
61 |
62 | - `autoclear.read`
63 | - `autoclear.write`
64 |
--------------------------------------------------------------------------------
/moderation/autoclear/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Union
4 |
5 | from sqlalchemy import BigInteger, Column, Integer
6 |
7 | from PyDrocsid.database import Base, db
8 |
9 |
10 | class AutoClearChannel(Base):
11 | __tablename__ = "autoclear_channel"
12 |
13 | channel: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
14 | minutes: Union[Column, int] = Column(Integer)
15 |
16 | @staticmethod
17 | async def create(channel: int, minutes: int) -> AutoClearChannel:
18 | row = AutoClearChannel(channel=channel, minutes=minutes)
19 | await db.add(row)
20 | return row
21 |
--------------------------------------------------------------------------------
/moderation/autoclear/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class AutoClearPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.autoclear.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/moderation/autoclear/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: Read AutoClear configuration
3 | write: Write AutoClear configuration
4 |
5 | commands:
6 | autoclear: Manage AutoClear channels
7 | set: Configure message ttl of a given channel
8 | disable: Disable AutoClear in a given channel
9 |
10 | autoclear: AutoClear
11 | no_autoclear_channels: ":x: No AutoClear channels."
12 | cannot_read: >
13 | AutoClear could not delete some messages in {} because I don't have `read_message_history` permission there.
14 | not_deleted: >
15 | AutoClear could not delete some messages in {} because I don't have `manage_messages` permission there.
16 | not_configured: AutoClear has not been configured for this channel.
17 | cannot_add: Channel could not be configured because I don't have `manage_messages` permission there.
18 | set:
19 | one: "**Message TTL** for {} has been **set** to {cnt} minute."
20 | many: "**Message TTL** for {} has been **set** to {cnt} minutes."
21 | disabled: "**AutoClear** has been **disabled** for {}."
22 |
23 | minutes:
24 | one: "{cnt} minute"
25 | many: "{cnt} minutes"
26 |
--------------------------------------------------------------------------------
/moderation/automod/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import AutoModCog
2 |
3 |
4 | __all__ = ["AutoModCog"]
5 |
--------------------------------------------------------------------------------
/moderation/automod/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | AutoMod = MaterialColors.blue["a700"]
6 |
--------------------------------------------------------------------------------
/moderation/automod/documentation.md:
--------------------------------------------------------------------------------
1 | # AutoMod
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/moderation/automod/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class AutoModPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.automod.permissions[self.name]
11 |
12 | autokick_read = auto()
13 | autokick_write = auto()
14 | instantkick_read = auto()
15 | instantkick_write = auto()
16 |
--------------------------------------------------------------------------------
/moderation/automod/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class AutoKickMode:
5 | off = 0
6 | normal = 1
7 | reverse = 2
8 |
9 |
10 | class AutoModSettings(Settings):
11 | autokick_mode = AutoKickMode.off
12 | autokick_delay = 30
13 | autokick_role = -1
14 | instantkick_role = -1
15 |
--------------------------------------------------------------------------------
/moderation/automod/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | autokick_read: read autokick configuration
3 | autokick_write: write autokick configuration
4 | instantkick_read: read instantkick configuration
5 | instantkick_write: write instantkick configuration
6 |
7 | cannot_kick: AutoMod could not kick {} ({}) because I don't have `kick_members` permission.
8 |
9 | # autokick
10 | autokick: AutoKick
11 | autokick_mode_configured:
12 | - "**AutoKick** has been **disabled**. :white_check_mark:"
13 | - "**AutoKick mode** has been **set to normal**. :white_check_mark:"
14 | - "**AutoKick mode** has been **set to reverse**. :white_check_mark:"
15 | autokick_delay_configured: "AutoKick delay has been configured. :white_check_mark:"
16 | log_autokick_delay_configured:
17 | one: "**AutoKick delay** has been **set to {cnt} second**."
18 | many: "**AutoKick delay** has been **set to {cnt} seconds**."
19 | autokick_role_configured: "AutoKick role has been configured. :white_check_mark:"
20 | log_autokick_role_configured: "**AutoKick role** has been **set to** {} ({})"
21 | autokick_disabled: AutoKick is disabled.
22 | autokick_mode:
23 | - Mode is set to **normal**.
24 | - Mode is set to **reverse**.
25 | x_seconds:
26 | one: "{cnt} second"
27 | many: "{cnt} seconds"
28 | autokicked: "You have been automatically kicked from {}."
29 | log_autokicked: "Kicked automatically."
30 |
31 | # instantkick
32 | instantkick: InstantKick
33 | instantkick_disabled: ":x: InstantKick is disabled."
34 | instantkick_enabled: "InstantKick is enabled. :white_check_mark:"
35 | instantkick_set_disabled: "**InstantKick** has been **disabled**. :white_check_mark:"
36 | instantkick_role_configured: "InstantKick role has been configured. :white_check_mark:"
37 | log_instantkick_role_configured: "**InstantKick role** has been **set to** {} ({})"
38 | instantkick_cannot_kick: Members with this role cannot be kicked.
39 |
--------------------------------------------------------------------------------
/moderation/autorole/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import AutoRoleCog
2 |
3 |
4 | __all__ = ["AutoRoleCog"]
5 |
--------------------------------------------------------------------------------
/moderation/autorole/cog.py:
--------------------------------------------------------------------------------
1 | from typing import Optional
2 |
3 | from discord import Embed, Member, Role
4 | from discord.ext import commands
5 | from discord.ext.commands import CommandError, Context, UserInputError, guild_only
6 |
7 | from PyDrocsid.cog import Cog
8 | from PyDrocsid.config import Contributor
9 | from PyDrocsid.embeds import send_long_embed
10 | from PyDrocsid.emojis import name_to_emoji
11 | from PyDrocsid.translations import t
12 | from PyDrocsid.util import check_role_assignable
13 |
14 | from .colors import Colors
15 | from .models import AutoRole
16 | from .permissions import AutoRolePermission
17 | from ...pubsub import send_to_changelog
18 |
19 |
20 | tg = t.g
21 | t = t.autorole
22 |
23 |
24 | class AutoRoleCog(Cog, name="AutoRole"):
25 | CONTRIBUTORS = [Contributor.Defelo]
26 |
27 | async def on_member_join(self, member: Member):
28 | roles: list[Role] = []
29 | invalid: list[Role] = []
30 |
31 | role: Role
32 | for role in map(member.guild.get_role, await AutoRole.all()):
33 | if not role:
34 | continue
35 |
36 | try:
37 | check_role_assignable(role)
38 | except CommandError:
39 | invalid.append(role)
40 | else:
41 | roles.append(role)
42 |
43 | await member.add_roles(*roles)
44 |
45 | if invalid:
46 | raise PermissionError(
47 | member.guild,
48 | t.cannot_assign(cnt=len(invalid), member=member, roles=", ".join(role.mention for role in invalid)),
49 | )
50 |
51 | @commands.group(aliases=["ar"])
52 | @AutoRolePermission.read.check
53 | @guild_only()
54 | async def autorole(self, ctx: Context):
55 | """
56 | configure autorole
57 | """
58 |
59 | if ctx.subcommand_passed is not None:
60 | if ctx.invoked_subcommand is None:
61 | raise UserInputError
62 | return
63 |
64 | embed = Embed(title=t.autorole, colour=Colors.AutoRole)
65 | out = []
66 | for role_id in await AutoRole.all():
67 | role: Optional[Role] = ctx.guild.get_role(role_id)
68 | if role is None:
69 | await AutoRole.remove(role_id)
70 | else:
71 | out.append(f":small_orange_diamond: {role.mention}")
72 | if not out:
73 | embed.description = t.no_autorole
74 | embed.colour = Colors.error
75 | else:
76 | embed.description = "\n".join(out)
77 | await send_long_embed(ctx, embed)
78 |
79 | @autorole.command(name="add", aliases=["a", "+"])
80 | @AutoRolePermission.write.check
81 | async def autorole_add(self, ctx: Context, *, role: Role):
82 | """
83 | add a role
84 | """
85 |
86 | if await AutoRole.exists(role.id):
87 | raise CommandError(t.ar_already_set)
88 |
89 | check_role_assignable(role)
90 |
91 | await AutoRole.add(role.id)
92 | await ctx.message.add_reaction(name_to_emoji["white_check_mark"])
93 | await send_to_changelog(ctx.guild, t.log_ar_added(role))
94 |
95 | @autorole.command(name="remove", aliases=["r", "del", "d", "-"])
96 | @AutoRolePermission.write.check
97 | async def autorole_remove(self, ctx: Context, *, role: Role):
98 | """
99 | remove a role
100 | """
101 |
102 | if not await AutoRole.exists(role.id):
103 | raise CommandError(t.ar_not_set)
104 |
105 | await AutoRole.remove(role.id)
106 | await ctx.message.add_reaction(name_to_emoji["white_check_mark"])
107 | await send_to_changelog(ctx.guild, t.log_ar_removed(role))
108 |
--------------------------------------------------------------------------------
/moderation/autorole/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | AutoRole = MaterialColors.grey[900]
6 |
--------------------------------------------------------------------------------
/moderation/autorole/documentation.md:
--------------------------------------------------------------------------------
1 | # AutoRole
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/moderation/autorole/models.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy import BigInteger, Column
4 |
5 | from PyDrocsid.async_thread import lock_deco
6 | from PyDrocsid.database import Base, db, delete, select
7 | from PyDrocsid.environment import CACHE_TTL
8 | from PyDrocsid.redis import redis
9 |
10 |
11 | @lock_deco
12 | async def load_cache():
13 | if await redis.exists(load_key := "autorole_loaded"):
14 | return
15 |
16 | roles = [row.role_id async for row in await db.stream(select(AutoRole))]
17 |
18 | async with redis.pipeline() as pipe:
19 | await pipe.delete(role_key := "autorole_roles")
20 | if roles:
21 | await pipe.sadd(role_key, *roles)
22 | await pipe.expire(role_key, CACHE_TTL)
23 | await pipe.setex(load_key, CACHE_TTL, 1)
24 | await pipe.execute()
25 |
26 |
27 | class AutoRole(Base):
28 | __tablename__ = "autorole"
29 |
30 | role_id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
31 |
32 | @staticmethod
33 | async def add(role: int):
34 | await load_cache()
35 | await redis.sadd(key := "autorole_roles", role)
36 | await redis.expire(key, CACHE_TTL)
37 | await db.add(AutoRole(role_id=role))
38 |
39 | @staticmethod
40 | async def exists(role: int) -> bool:
41 | await load_cache()
42 | return await redis.sismember("autorole_roles", role)
43 |
44 | @staticmethod
45 | async def all() -> list[int]:
46 | await load_cache()
47 | return list(map(int, await redis.smembers("autorole_roles")))
48 |
49 | @staticmethod
50 | async def remove(role: int):
51 | await load_cache()
52 | await redis.srem(key := "autorole_roles", role)
53 | await redis.expire(key, CACHE_TTL)
54 | await db.exec(delete(AutoRole).filter_by(role_id=role))
55 |
--------------------------------------------------------------------------------
/moderation/autorole/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class AutoRolePermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.autorole.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/moderation/autorole/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: read autorole configuration
3 | write: write autorole configuration
4 |
5 | autorole: AutoRole
6 | no_autorole: No roles have been configured yet.
7 | ar_already_set: Role already set.
8 | ar_added: AutoRole has been added successfully.
9 | log_ar_added: AutoRole `@{}` has been added successfully.
10 | ar_not_set: Role not set.
11 | ar_removed: AutoRole has been removed successfully.
12 | log_ar_removed: AutoRole `@{}` has been removed successfully.
13 | role_not_added_too_high: Role could not be added because `@{}` is higher than `@{}`.
14 | role_not_added_managed_role: Role could not be added because `@{}` cannot be assigned manually.
15 | cannot_assign:
16 | one: AutoRole could not assign {roles} to {member.mention} ({member.id}).
17 | many: "AutoRole could not assign the following roles to {member.mention} ({member.id}): {roles}"
18 |
--------------------------------------------------------------------------------
/moderation/content_filter/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import ContentFilterCog
2 |
3 |
4 | __all__ = ["ContentFilterCog"]
5 |
--------------------------------------------------------------------------------
/moderation/content_filter/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | ContentFilter = MaterialColors.bluegrey[400]
6 |
--------------------------------------------------------------------------------
/moderation/content_filter/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime
4 | from typing import Union
5 |
6 | from discord.utils import utcnow
7 | from sqlalchemy import BigInteger, Boolean, Column, Integer, Text
8 |
9 | from PyDrocsid.database import Base, UTCDateTime, db, select
10 | from PyDrocsid.environment import CACHE_TTL
11 | from PyDrocsid.redis import redis
12 |
13 |
14 | async def sync_redis() -> list[str]:
15 | out = []
16 |
17 | async with redis.pipeline() as pipe:
18 | await pipe.delete(key := "content_filter")
19 |
20 | regex: BadWord
21 | async for regex in await db.stream(select(BadWord)):
22 | out.append(regex.regex)
23 | await pipe.lpush(key, regex.regex)
24 |
25 | await pipe.lpush(key, "")
26 | await pipe.expire(key, CACHE_TTL)
27 |
28 | await pipe.execute()
29 |
30 | return out
31 |
32 |
33 | class BadWord(Base):
34 | __tablename__ = "bad_word_list"
35 |
36 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
37 | regex: Union[Column, str] = Column(Text, unique=True)
38 | description: Union[Column, str] = Column(Text)
39 | delete: Union[Column, bool] = Column(Boolean)
40 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
41 |
42 | @staticmethod
43 | async def create(regex: str, description: str, delete: bool) -> BadWord:
44 | row = BadWord(regex=regex, description=description, delete=delete, timestamp=utcnow())
45 | await db.add(row)
46 | await sync_redis()
47 | return row
48 |
49 | async def remove(self) -> None:
50 | await db.delete(self)
51 | await sync_redis()
52 |
53 | @staticmethod
54 | async def get_all_redis() -> list[str]:
55 | if out := await redis.lrange("content_filter", 0, -1):
56 | return [x for x in out if x]
57 |
58 | return await sync_redis()
59 |
60 | @staticmethod
61 | async def get_all_db() -> list[BadWord]:
62 | return await db.all(select(BadWord))
63 |
64 |
65 | class BadWordPost(Base):
66 | __tablename__ = "bad_word_post"
67 |
68 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
69 | member: Union[Column, int] = Column(BigInteger)
70 | member_name: Union[Column, str] = Column(Text)
71 | channel: Union[Column, int] = Column(BigInteger)
72 | content: Union[Column, str] = Column(Text)
73 | deleted_message: Union[Column, bool] = Column(Boolean)
74 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
75 |
76 | @staticmethod
77 | async def create(member: int, member_name: str, channel: int, content: str, deleted: bool) -> BadWordPost:
78 | row = BadWordPost(
79 | member=member,
80 | member_name=member_name,
81 | channel=channel,
82 | content=content,
83 | deleted_message=deleted,
84 | timestamp=utcnow(),
85 | )
86 | await db.add(row)
87 | return row
88 |
--------------------------------------------------------------------------------
/moderation/content_filter/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class ContentFilterPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.content_filter.permissions[self.name]
11 |
12 | bypass = auto()
13 | read = auto()
14 | write = auto()
15 |
--------------------------------------------------------------------------------
/moderation/content_filter/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | bypass: bypass content filter checks
3 | read: read content filter configuration
4 | write: write content filter configuration
5 |
6 | commands:
7 | content_filter: "manage content filter settings"
8 | add: "add a regex to the blacklist"
9 | remove: "remove a regex from the blacklist"
10 | check: "check if a string matches a regex \n(set `regex` to `-1` to check against all existing patterns)"
11 | update: "update a content filter pattern"
12 | update_description: "update the description of a pattern"
13 | update_regex: "update regex of a pattern"
14 | delete_message: "change whether to delete messages that match a pattern"
15 |
16 | ulog_message: ":stop_sign: **Sent** a message with the forbidden string `{}` in <#{}> (not deleted)."
17 | ulog_message_deleted: ":stop_sign: **Sent** a message with the forbidden string `{}` in <#{}> (deleted)."
18 |
19 | already_blacklisted: "This regex is already in the list!"
20 | not_blacklisted: "This regex is not in the blacklist!"
21 | description_length: "The description has to be 500 or less characters long!"
22 | invalid_regex: "Not a valid regular expression!"
23 |
24 | log_content_filter_added: "**Regex** `{}` was **added** to **Blacklist** by {}"
25 | confirm_text: "Are you sure that you want to remove the filter `{}` ({})?"
26 | log_content_filter_removed: "**Regex** `{}` was **removed** from **Blacklist** by {}"
27 | log_description_updated: "**Description** was **updated** for regex *{}*\nfrom: `{}`\nto: `{}`"
28 | log_regex_updated: "**Regex** was **updated**\nfrom: `{}`\nto: `{}`"
29 | log_delete_updated: "**Delete** was set to **{}** for `{}`"
30 | log_forbidden_posted: |
31 | {} sent a **[message]({})** in {}, which contained one or more new **forbidden expressions**: `{}` (no delete required)
32 | All matched ID's: `{}`
33 | log_forbidden_posted_deleted: |
34 | {}'s **[message]({})** in {} was **deleted** because it contained one or more new **forbidden expressions**: `{}`
35 | All matched ID's: `{}`
36 | log_forbidden_posted_not_deleted: |
37 | {} sent a **[message]({})** in {}, which contained one or more new **forbidden expressions**: `{}`
38 | All matched ID's: `{}`
39 | **The message could not be deleted** because I do not have `manage_messages` permission in this channel.
40 |
41 | bad_word_list_header: "Blacklisted Expressions"
42 | no_pattern_listed: "No blacklisted patterns yet!"
43 |
44 | embed_field_name: ":dna: ID `{}` - {}"
45 | embed_field_value: "Regex: `{}`\nDelete: *{}*"
46 | delete: "True"
47 | not_delete: "False"
48 |
49 | checked_expressions: "Checked Expressions"
50 | matches: "Matches:"
51 | no_matches: "No matches found!"
52 | invalid_pattern: "Invalid pattern!"
53 |
--------------------------------------------------------------------------------
/moderation/invites/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import InvitesCog
2 |
3 |
4 | __all__ = ["InvitesCog"]
5 |
--------------------------------------------------------------------------------
/moderation/invites/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Invites = MaterialColors.lightgreen["a700"]
6 |
--------------------------------------------------------------------------------
/moderation/invites/documentation.md:
--------------------------------------------------------------------------------
1 | # Invite Whitelist
2 |
3 | Contains commands to manage a whitelist for discord invites that may be sent in the chat. Invites not on the list will be deleted automatically.
4 |
5 |
6 | ## `invites`
7 |
8 | Contains subcommands to manage the whitelist of allowed discord servers.
9 |
10 | ```css
11 | .[invites|i]
12 | ```
13 |
14 |
15 | ### `list`
16 |
17 | Returns a list of all Discord servers on the whitelist.
18 |
19 | ```css
20 | .invites [list|l|?]
21 | ```
22 |
23 |
24 | ### `show`
25 |
26 | Shows detailed information about a given server on the whitelist.
27 |
28 | ```css
29 | .invites [show|info|s|i]
30 | ```
31 |
32 | Arguments:
33 |
34 | | Argument | Required | Description |
35 | |:--------:|:-------------------------:|:--------------------------------|
36 | | `server` | :fontawesome-solid-check: | The server's name, id or invite |
37 |
38 |
39 | ### `add`
40 |
41 | Adds a Discord server to the whitelist.
42 |
43 | ```css
44 | .invites [add|+|a]
45 | ```
46 |
47 | Arguments:
48 |
49 | | Argument | Required | Description |
50 | |:-----------:|:-------------------------:|:------------------------------------------------------------|
51 | | `invite` | :fontawesome-solid-check: | The invite link (should be permanent with unlimited usages) |
52 | | `applicant` | :fontawesome-solid-check: | The user who wants to add the server to the list |
53 |
54 | Required Permissions:
55 |
56 | - `invites.manage`
57 |
58 |
59 | ### `remove`
60 |
61 | Removes a server from the whitelist.
62 |
63 | ```css
64 | .invites [remove|r|del|d|-]
65 | ```
66 |
67 | Arguments:
68 |
69 | | Argument | Required | Description |
70 | |:--------:|:-------------------------:|:--------------------------------|
71 | | `server` | :fontawesome-solid-check: | The server's name, id or invite |
72 |
73 | Required Permissions:
74 |
75 | - `invites.manage`
76 |
77 |
78 | ### `update`
79 |
80 | Contains subcommands to update the invite link and description of a server on the whitelist.
81 |
82 | ```css
83 | .invites [update|u]
84 | ```
85 |
86 | !!! note
87 | These commands can be used by the applicant of the respective server as well as by anyone who has the `invites.manage` permission.
88 |
89 |
90 | #### `description`
91 |
92 | Changes the description of a server on the whitelist.
93 |
94 | ```css
95 | .invites update [description|d] [description]
96 | ```
97 |
98 | Arguments:
99 |
100 | | Argument | Required | Description |
101 | |:-------------:|:--------:|:---------------------------------------------------------------------|
102 | | `description` | | The new description. If omitted, the current description is cleared. |
103 |
104 |
105 | #### `invite`
106 |
107 | Updates the invite link to a server on the whitelist.
108 |
109 | ```css
110 | .invites update [invite|i]
111 | ```
112 |
113 | Arguments:
114 |
115 | | Argument | Required | Description |
116 | |:--------:|:-------------------------:|:----------------------------------------------------------------|
117 | | `invite` | :fontawesome-solid-check: | The new invite link (should be permanent with unlimited usages) |
118 |
--------------------------------------------------------------------------------
/moderation/invites/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime
4 | from typing import Optional, Union
5 |
6 | from discord.utils import utcnow
7 | from sqlalchemy import BigInteger, Boolean, Column, Integer, String, Text
8 |
9 | from PyDrocsid.database import Base, UTCDateTime, db
10 |
11 |
12 | class AllowedInvite(Base):
13 | __tablename__ = "allowed_invite"
14 |
15 | guild_id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
16 | code: Union[Column, str] = Column(String(16))
17 | guild_name: Union[Column, str] = Column(String(128))
18 | applicant: Union[Column, int] = Column(BigInteger)
19 | approver: Union[Column, int] = Column(BigInteger)
20 | description: Union[Column, Optional[str]] = Column(Text, nullable=True)
21 | created_at: Union[Column, datetime] = Column(UTCDateTime)
22 |
23 | @staticmethod
24 | async def create(guild_id: int, code: str, guild_name: str, applicant: int, approver: int) -> AllowedInvite:
25 | row = AllowedInvite(
26 | guild_id=guild_id,
27 | code=code,
28 | guild_name=guild_name,
29 | applicant=applicant,
30 | approver=approver,
31 | description=None,
32 | created_at=utcnow(),
33 | )
34 | await db.add(row)
35 | return row
36 |
37 | @staticmethod
38 | async def update(guild_id: int, code: str, guild_name: str):
39 | row: AllowedInvite = await db.get(AllowedInvite, guild_id=guild_id)
40 | row.code = code
41 | row.guild_name = guild_name
42 |
43 |
44 | class InviteLog(Base):
45 | __tablename__ = "invite_log"
46 |
47 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
48 | guild_id: Union[Column, int] = Column(BigInteger)
49 | guild_name: Union[Column, str] = Column(String(128))
50 | applicant: Union[Column, int] = Column(BigInteger)
51 | mod: Union[Column, int] = Column(BigInteger)
52 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
53 | approved: Union[Column, bool] = Column(Boolean)
54 |
55 | @staticmethod
56 | async def create(guild_id: int, guild_name: str, applicant: int, mod: int, approved: bool) -> InviteLog:
57 | row = InviteLog(
58 | guild_id=guild_id,
59 | guild_name=guild_name,
60 | applicant=applicant,
61 | mod=mod,
62 | timestamp=utcnow(),
63 | approved=approved,
64 | )
65 | await db.add(row)
66 | return row
67 |
68 |
69 | class IllegalInvitePost(Base):
70 | __tablename__ = "illegal_invite_post"
71 |
72 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
73 | member: Union[Column, int] = Column(BigInteger)
74 | member_name: Union[Column, str] = Column(Text)
75 | channel: Union[Column, int] = Column(BigInteger)
76 | name: Union[Column, str] = Column(Text)
77 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
78 |
79 | @staticmethod
80 | async def create(member: int, member_name: str, channel: int, name: str) -> IllegalInvitePost:
81 | row = IllegalInvitePost(member=member, member_name=member_name, timestamp=utcnow(), channel=channel, name=name)
82 | await db.add(row)
83 | return row
84 |
--------------------------------------------------------------------------------
/moderation/invites/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class InvitesPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.invites.permissions[self.name]
11 |
12 | bypass = auto()
13 | manage = auto()
14 |
--------------------------------------------------------------------------------
/moderation/invites/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | bypass: bypass invite checks
3 | manage: manage allowed invites
4 |
5 | illegal_invite_link: >
6 | :x: Illegal discord invite link!
7 | Please contact a team member to submit a request for whitelisting the invitation.
8 | Use the command `{}` to get a list of all allowed discord servers.
9 | log_illegal_invite: |
10 | **Deleted** a message of {} in {} because it contained one or more **illegal discord invite** links: {}
11 | log_illegal_invite_not_deleted: |
12 | {} sent a **message** in {} which contained one or more **illegal discord invite** links: {}
13 | **The message could not be deleted** because I don't have `manage_messages` permission in this channel.
14 | allowed_servers_title: Allowed Discord Servers
15 | allowed_servers_description: "Any link to these servers is allowed to post:\n\n"
16 | no_server_allowed: ":x: No discord servers allowed."
17 | invalid_invite: Invalid invite.
18 | allowed_server_not_found: Allowed discord server not found.
19 | allowed_server: Allowed Discord Server
20 | server_name: Server Name
21 | server_id: Server ID
22 | invite_link: Invite Link
23 | invite_link_expired: Invite Link (expired)
24 | applicant: Applicant
25 | approver: Approver
26 | date: Date
27 | server_already_whitelisted: This server has already been whitelisted.
28 | server_whitelisted: "Server has been whitelisted successfully. :white_check_mark:"
29 | log_server_whitelisted: "**Discord Server** `{}` has been **added** to the whitelist. :white_check_mark:"
30 | server_not_whitelisted: Server is not whitelisted.
31 | server_removed: "Server has been removed from the whitelist successfully. :white_check_mark:"
32 | log_server_removed: "**Discord Server** `{}` has been **removed** from the whitelist."
33 | invite_updated: "Invite link for `{}` has been updated. :white_check_mark:"
34 | log_invite_updated: "{} just **updated** the **invite link** for `{}`."
35 | invites: Invites
36 | ulog_invite_approved: ":+1: **Invite approved** by {}: {}"
37 | ulog_invite_removed: ":-1: **Invite removed** by {}: {}"
38 | ulog_illegal_post: ":no_entry_sign: **Posted an illegal invite** in {}: {}"
39 | description: Description
40 | confirm: "Confirm"
41 | clear_description: "Are you sure that you want to delete the description?"
42 | description_cleared: "Description for `{}` was cleared!"
43 | log_description_cleared: "{} just **cleared** the **description** for `{}`"
44 | description_updated: "Updated description from `{}` to `{}` for `{}`!"
45 | log_description_updated: "{} just **updated** the **description** for `{}` from `{}` to `{}`"
46 | description_too_long: "The length of the description has to be smaller than 500 characters!"
47 |
--------------------------------------------------------------------------------
/moderation/logging/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import LoggingCog
2 |
3 |
4 | __all__ = ["LoggingCog"]
5 |
--------------------------------------------------------------------------------
/moderation/logging/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Logging = MaterialColors.blue["a700"]
6 | edit = MaterialColors.yellow[800]
7 | delete = MaterialColors.red[900]
8 | changelog = MaterialColors.teal
9 |
--------------------------------------------------------------------------------
/moderation/logging/documentation.md:
--------------------------------------------------------------------------------
1 | # Logging
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/moderation/logging/models.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from sqlalchemy import BigInteger, Column
4 |
5 | from PyDrocsid.database import Base, db, delete, filter_by, select
6 |
7 |
8 | class LogExclude(Base):
9 | __tablename__ = "log_exclude"
10 |
11 | channel_id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
12 |
13 | @staticmethod
14 | async def add(channel_id: int):
15 | await db.add(LogExclude(channel_id=channel_id))
16 |
17 | @staticmethod
18 | async def exists(channel_id: int) -> bool:
19 | return await db.exists(filter_by(LogExclude, channel_id=channel_id))
20 |
21 | @staticmethod
22 | async def all() -> list[int]:
23 | return [le.channel_id async for le in await db.stream(select(LogExclude))]
24 |
25 | @staticmethod
26 | async def remove(channel_id: int):
27 | await db.exec(delete(LogExclude).filter_by(channel_id=channel_id))
28 |
--------------------------------------------------------------------------------
/moderation/logging/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class LoggingPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.logging.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/moderation/logging/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class LoggingSettings(Settings):
5 | maxage = -1
6 | edit_mindiff = 1
7 |
8 | edit_channel = -1
9 | delete_channel = -1
10 | alert_channel = -1
11 | changelog_channel = -1
12 | member_join_channel = -1
13 | member_leave_channel = -1
14 |
--------------------------------------------------------------------------------
/moderation/mediaonly/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import MediaOnlyCog
2 |
3 |
4 | __all__ = ["MediaOnlyCog"]
5 |
--------------------------------------------------------------------------------
/moderation/mediaonly/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | MediaOnly = MaterialColors.yellow["a200"]
6 |
--------------------------------------------------------------------------------
/moderation/mediaonly/documentation.md:
--------------------------------------------------------------------------------
1 | # MediaOnly
2 |
3 | Contains the `mediaonly` command to set up and manage channels only pictures can be sent to.
4 |
5 |
6 | ## `mediaonly`
7 |
8 | Contains subcommands to manage media-only channels.
9 | If no subcommand is given, this command shows a list of all media-only channels.
10 |
11 | ```css
12 | .[mediaonly|mo]
13 | ```
14 |
15 | Required Permissions:
16 |
17 | - `mediaonly.read`
18 |
19 |
20 | ### `add`
21 |
22 | Sets the media-only flag for a given text channel.
23 |
24 | ```css
25 | .mediaonly [add|a|+]
26 | ```
27 |
28 | Arguments:
29 |
30 | | Argument | Required | Description |
31 | |:---------:|:-------------------------:|:------------|
32 | | `channel` | :fontawesome-solid-check: | The channel |
33 |
34 | Required Permissions:
35 |
36 | - `mediaonly.read`
37 | - `mediaonly.write`
38 |
39 | !!! note
40 | Responses to [slash commands](https://blog.discord.com/slash-commands-are-here-8db0a385d9e6){target=_blank} are ignored by the bot, so you should disable slash commands manually in media-only channels.
41 |
42 |
43 | ### `remove`
44 |
45 | Removes the media-only flag from a given text channel.
46 |
47 | ```css
48 | .mediaonly [remove|del|r|d|-]
49 | ```
50 |
51 | Arguments:
52 |
53 | | Argument | Required | Description |
54 | |:---------:|:-------------------------:|:------------|
55 | | `channel` | :fontawesome-solid-check: | The channel |
56 |
57 | Required Permissions:
58 |
59 | - `mediaonly.read`
60 | - `mediaonly.write`
61 |
--------------------------------------------------------------------------------
/moderation/mediaonly/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime
4 | from typing import AsyncIterable, Union
5 |
6 | from discord.utils import utcnow
7 | from sqlalchemy import BigInteger, Column, Integer, Text
8 |
9 | from PyDrocsid.database import Base, UTCDateTime, db, delete, filter_by, select
10 | from PyDrocsid.environment import CACHE_TTL
11 | from PyDrocsid.redis import redis
12 |
13 |
14 | class MediaOnlyChannel(Base):
15 | __tablename__ = "mediaonly_channel"
16 |
17 | channel: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
18 |
19 | @staticmethod
20 | async def add(channel: int):
21 | await redis.setex(f"mediaonly:channel={channel}", CACHE_TTL, 1)
22 | await db.add(MediaOnlyChannel(channel=channel))
23 |
24 | @staticmethod
25 | async def exists(channel: int) -> bool:
26 | if result := await redis.get(key := f"mediaonly:channel={channel}"):
27 | return result == "1"
28 |
29 | result = await db.exists(filter_by(MediaOnlyChannel, channel=channel))
30 | await redis.setex(key, CACHE_TTL, int(result))
31 | return result
32 |
33 | @staticmethod
34 | async def stream() -> AsyncIterable[int]:
35 | row: MediaOnlyChannel
36 | async with redis.pipeline() as pipe:
37 | async for row in await db.stream(select(MediaOnlyChannel)):
38 | channel = row.channel
39 | await pipe.setex(f"mediaonly:channel={channel}", CACHE_TTL, 1)
40 | yield channel
41 | await pipe.execute()
42 |
43 | @staticmethod
44 | async def remove(channel: int):
45 | await redis.setex(f"mediaonly:channel={channel}", CACHE_TTL, 0)
46 | await db.exec(delete(MediaOnlyChannel).filter_by(channel=channel))
47 |
48 |
49 | class MediaOnlyDeletion(Base):
50 | __tablename__ = "mediaonly_deletion"
51 |
52 | id: Union[Column, int] = Column(Integer, primary_key=True, unique=True, autoincrement=True)
53 | member: Union[Column, int] = Column(BigInteger)
54 | member_name: Union[Column, str] = Column(Text)
55 | channel: Union[Column, int] = Column(BigInteger)
56 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
57 |
58 | @staticmethod
59 | async def create(member: int, member_name: str, channel: int) -> MediaOnlyDeletion:
60 | row = MediaOnlyDeletion(member=member, member_name=member_name, timestamp=utcnow(), channel=channel)
61 | await db.add(row)
62 | return row
63 |
--------------------------------------------------------------------------------
/moderation/mediaonly/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class MediaOnlyPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.mediaonly.permissions[self.name]
11 |
12 | bypass = auto()
13 | read = auto()
14 | write = auto()
15 |
--------------------------------------------------------------------------------
/moderation/mediaonly/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | bypass: bypass mediaonly checks
3 | read: read mediaonly configuration
4 | write: write mediaonly configuration
5 |
6 | commands:
7 | mediaonly: manage media only channels
8 | list: list media only channels
9 | add: add a media only channel
10 | remove: remove a media only channel
11 |
12 | mediaonly: MediaOnly
13 | deleted_nomedia: >
14 | Only pictures are allowed in this channel.
15 | For conversations please use the channels designated for this purpose.
16 | log_deleted_nomedia: "**Deleted a message** of {} in **media only channel** {} because it did not contain an image."
17 | log_nomedia_not_deleted: >
18 | MediaOnly could not delete a message of {} in {} because I don't have `manage_messages` permission there.
19 | media_only_channels_header: "Media only channels"
20 | no_media_only_channels: ":x: No media only channels."
21 | channel_already_media_only: Channel is already a media only channel.
22 | channel_now_media_only: "Channel is now a media only channel. :white_check_mark:"
23 | log_channel_now_media_only: "**Channel** {} has been **added** to the list of **media only channels**."
24 | channel_not_media_only: Channel is not a media only channel.
25 | channel_not_media_only_anymore: "Channel is not a media only channel anymore. :white_check_mark:"
26 | log_channel_not_media_only_anymore: "**Channel** {} has been **removed** from the list of **media only channels**."
27 | media_only_not_changed_no_permissions: >
28 | Media only channel could not be added because I don't have `manage_messages` permission there.
29 | ulog_deletion: ":frame_photo: **Sent** a message without a picture to the mediaonly channel {}."
30 |
--------------------------------------------------------------------------------
/moderation/message/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import MessageCog
2 |
3 |
4 | __all__ = ["MessageCog"]
5 |
--------------------------------------------------------------------------------
/moderation/message/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | MessageCommands = MaterialColors.blue["a700"]
6 |
--------------------------------------------------------------------------------
/moderation/message/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class MessagePermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.message.permissions[self.name]
11 |
12 | send = auto()
13 | edit = auto()
14 | delete = auto()
15 | clear = auto()
16 |
--------------------------------------------------------------------------------
/moderation/message/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | send: send a message as the bot
3 | edit: edit messages sent by the bot
4 | delete: delete a message
5 | clear: delete lots of messages at once
6 |
7 | commands:
8 | send: send messages as the bot
9 | send_text: send a normal message
10 | send_embed: send an embed
11 | send_copy: copy a message (specify message link)
12 | send_discohook: |
13 | send a discohook message
14 | Go to {}, compose your message, click on `Share Message` and copy the link.
15 | edit: edit messages sent by the bot
16 | edit_text: edit a normal message (specify message link)
17 | edit_embed: edit an embed (specify message link)
18 | edit_copy: copy a message into another message (specify message links)
19 | edit_discohook: |
20 | edit a discohook message
21 | Go to {}, compose your message, click on `Share Message` and copy the link.
22 | delete: delete a message (specify message link)
23 | clear: "delete the last messages in this channel"
24 | discohook: create a discohook link for an existing message
25 |
26 | messages: Messages
27 | send_embed_title: "Send me the title of the embed! Send {} to cancel."
28 | send_embed_content: "Ok, now send me the content of the embed! Send {} to cancel."
29 | could_not_send_message: Message could not be sent because I don't have `send_messages` permission in this channel.
30 | send_message: "Now send me the message! Send {} to cancel."
31 | msg_could_not_be_sent: Message could not be sent.
32 | msg_sent: "Message has been sent successfully. :white_check_mark:"
33 | msg_send_cancel: "Sending a message has been cancelled :x:"
34 | could_not_send_embed: Message could not be sent because I don't have `embed_links` permission in this channel.
35 | could_not_edit: This message cannot be edited because the bot is not the author of the message.
36 | send_new_message: "Now send me the new message! Send {} to cancel!"
37 | msg_edited: "Message has been edited successfully. :white_check_mark:"
38 | cannot_delete_dm: Private messages cannot be deleted.
39 | could_not_delete: Message could not be deleted because I don't have `manage_messages` permission in this channel.
40 | msg_deleted: "Message has been deleted successfully. :white_check_mark:"
41 | title_too_long: Title is too long.
42 | cannot_edit_files: Files cannot be edited.
43 | cancel: CANCEL
44 |
45 | confirmation: Confirmation
46 | clear_channel: Clean Channel
47 | count_between: You cannot delete less than 1 or more than 100 messages.
48 | confirm:
49 | one: Are you sure you want to delete the last message in {}?
50 | many: Are you sure you want to delete the last {cnt} messages in {}?
51 | canceled: ":x: Canceled"
52 | confirmed: "Confirmed :white_check_mark:"
53 | clearing: Deleting messages in {} ({}/{})
54 | msg_not_deleted: Some messages could not be deleted!
55 | deleted_messages:
56 | one: "{cnt} message has been deleted in {}!"
57 | many: "{cnt} messages have been deleted in {}!"
58 | log_cleared:
59 | one: "{} deleted {cnt} message in {}."
60 | many: "{} deleted {cnt} messages in {}."
61 |
62 | cannot_read_messages: You cannot read messages in {}.
63 | discohook_empty: This discohook link does not contain any messages.
64 | discohook_multiple_messages: This discohook link contains more than one message.
65 | discohook_multiple_embeds: This discohook link contains more than one embed.
66 | discohook_create_failed: Discohook link could not be created.
67 | discohook_invalid: Invalid discohook link
68 |
--------------------------------------------------------------------------------
/moderation/mod/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import ModCog
2 |
3 |
4 | __all__ = ["ModCog"]
5 |
--------------------------------------------------------------------------------
/moderation/mod/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | ModTools = MaterialColors.blue["a700"]
6 |
7 | report = MaterialColors.yellow[800]
8 | warn = MaterialColors.yellow["a200"]
9 | mute = MaterialColors.yellow[600]
10 | unmute = MaterialColors.green["a700"]
11 | kick = MaterialColors.orange[700]
12 | ban = MaterialColors.red[500]
13 | unban = MaterialColors.green["a700"]
14 |
--------------------------------------------------------------------------------
/moderation/mod/documentation.md:
--------------------------------------------------------------------------------
1 | # Mod Tools
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/moderation/mod/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class ModPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.mod.permissions[self.name]
11 |
12 | warn = auto()
13 | mute = auto()
14 | kick = auto()
15 | ban = auto()
16 |
--------------------------------------------------------------------------------
/moderation/role_notifications/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import RoleNotificationsCog
2 |
3 |
4 | __all__ = ["RoleNotificationsCog"]
5 |
--------------------------------------------------------------------------------
/moderation/role_notifications/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | RoleNotifications = MaterialColors.indigo[400]
6 |
--------------------------------------------------------------------------------
/moderation/role_notifications/documentation.md:
--------------------------------------------------------------------------------
1 | # Role Notifications
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/moderation/role_notifications/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Union
4 |
5 | from sqlalchemy import BigInteger, Boolean, Column
6 |
7 | from PyDrocsid.database import Base, db
8 |
9 |
10 | class RoleNotification(Base):
11 | __tablename__ = "role_notification"
12 |
13 | role_id: Union[Column, int] = Column(BigInteger, primary_key=True)
14 | channel_id: Union[Column, int] = Column(BigInteger, primary_key=True)
15 | ping_role: Union[Column, bool] = Column(Boolean)
16 | ping_user: Union[Column, bool] = Column(Boolean)
17 |
18 | @staticmethod
19 | async def create(role_id: int, channel_id: int, ping_role: bool, ping_user: bool) -> RoleNotification:
20 | row = RoleNotification(role_id=role_id, channel_id=channel_id, ping_role=ping_role, ping_user=ping_user)
21 | await db.add(row)
22 | return row
23 |
--------------------------------------------------------------------------------
/moderation/role_notifications/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class RoleNotificationsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.role_notifications.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/moderation/role_notifications/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: read role notification links
3 | write: write role notification links
4 |
5 | rn_created: Role notification link has been created.
6 | log_rn_created: Role notification link `@{}` -> {} has been created.
7 | rn_removed: Role notification link has been removed.
8 | log_rn_removed: Role notification link `@{}` -> {} has been removed.
9 | rn_links: Role Notification Links
10 | rn_no_links: No role notification links have been created yet.
11 | rn_role_added: Role {} has been added to {}.
12 | rn_role_removed: Role {} has been removed from {}.
13 | rn_ping_role: ping role
14 | rn_ping_user: ping user
15 | cannot_send: RoleNotifications cannot send messages to {}.
16 |
--------------------------------------------------------------------------------
/moderation/spam_detection/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import SpamDetectionCog
2 |
3 |
4 | __all__ = ["SpamDetectionCog"]
5 |
--------------------------------------------------------------------------------
/moderation/spam_detection/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | SpamDetection = MaterialColors.deeporange[700]
6 |
--------------------------------------------------------------------------------
/moderation/spam_detection/documentation.md:
--------------------------------------------------------------------------------
1 | # Spam Detection
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/moderation/spam_detection/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class SpamDetectionPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.spam_detection.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 | bypass = auto()
15 |
--------------------------------------------------------------------------------
/moderation/spam_detection/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class SpamDetectionSettings(Settings):
5 | max_hops_alert = 0
6 | max_hops_warning = 0
7 | max_hops_temp_mute = 0
8 | temp_mute_duration = 10
9 |
--------------------------------------------------------------------------------
/moderation/spam_detection/translations/en.yml:
--------------------------------------------------------------------------------
1 | commands:
2 | spam_detection: view and change spam detection settings
3 | channel_hopping: edit spam detection settings
4 | alert: set the number of allowed hops before a alert-channel notification is used (0 for disabling)
5 | warning: set the number of allowed hops before a dm notification is used (0 for disabling)
6 | temp_mute: edit the channel hopping mute settings
7 | temp_mute_hops: set the number of allowed hops before a alert-channel notification is used (0 for disabling)
8 | temp_mute_duration: set the number of seconds a user is muted (0 for disabling)
9 |
10 | permissions:
11 | read: read spam detection configuration
12 | write: write spam detection configuration
13 | bypass: bypass the channel hopping check
14 |
15 | spam_detection: Spam Detection
16 |
17 | max_x_hops:
18 | one: "Max.: `{cnt}` channel hop per minute"
19 | many: "Max.: `{cnt}` channel hops per minute"
20 | seconds_muted:
21 | one: "{cnt} second"
22 | many: "{cnt} seconds"
23 |
24 | member_id: Member ID
25 |
26 | channel_hopping: Channel hopping
27 | channel_hopping_warning: Warning via DM
28 | channel_hopping_alert: Alert message
29 | channel_hopping_mute: Temporare mute
30 | mute_duration: Mute duration
31 | reason: was timouted because of channelhopping
32 |
33 | cant_mute: "Cannot mute {} ({}) for channel hopping because of missing permissions"
34 |
35 | current_channel: Current Channel
36 |
37 | new_amount: New amount
38 | hop_amount_set: "The **maximum amount** of **channel hops per minute** has been **set to {}** for **{}**."
39 | mute_time_set: "The **muted time** for channel hooping has been **set to {} seconds**."
40 | hop_detection_disabled: "**Channel Hopping Detection** has been **disabled** for **{}**."
41 | change_types:
42 | warnings: warnings
43 | alerts: alerts
44 | mutes: mutes
45 |
46 | hops_in_last_minute:
47 | one: "{cnt} hop in the last minute"
48 | many: "{cnt} hops in the last minute"
49 |
50 | channel_hopping_warning_sent: "Please stop channel hopping!"
51 |
--------------------------------------------------------------------------------
/moderation/threads/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import ThreadsCog
2 |
3 |
4 | __all__ = ["ThreadsCog"]
5 |
--------------------------------------------------------------------------------
/moderation/threads/cog.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from datetime import datetime
3 | from typing import Optional
4 |
5 | from discord import Embed, Forbidden, Role, TextChannel, Thread
6 | from discord.ext import commands
7 | from discord.ext.commands import Context, guild_only
8 | from discord.utils import format_dt, snowflake_time
9 |
10 | from PyDrocsid.cog import Cog
11 | from PyDrocsid.command import docs
12 | from PyDrocsid.embeds import send_long_embed
13 | from PyDrocsid.redis import redis
14 | from PyDrocsid.settings import RoleSettings
15 | from PyDrocsid.translations import t
16 |
17 | from .colors import Colors
18 | from .permissions import ThreadsPermission
19 | from ...contributor import Contributor
20 | from ...pubsub import send_alert
21 |
22 |
23 | tg = t.g
24 | t = t.threads
25 |
26 |
27 | class ThreadsCog(Cog, name="Thread Utils"):
28 | CONTRIBUTORS = [Contributor.Defelo]
29 |
30 | async def on_thread_create(self, thread: Thread):
31 | await self.on_thread_join(thread)
32 |
33 | async def on_thread_join(self, thread: Thread):
34 | if await redis.exists(key := f"thread_created:{thread.id}"):
35 | return
36 | await redis.setex(key, 24 * 3600, "1")
37 |
38 | role: Optional[Role] = thread.guild.get_role(await RoleSettings.get("thread_auto_join"))
39 | if not role:
40 | return
41 |
42 | try:
43 | await asyncio.sleep(1)
44 | msg = await thread.send(role.mention)
45 | await msg.delete()
46 | except Forbidden:
47 | await send_alert(thread.guild, t.new_thread_alert(thread.mention, thread.parent.mention))
48 |
49 | @commands.command(aliases=["t"])
50 | @ThreadsPermission.list.check
51 | @guild_only()
52 | @docs(t.commands.threads)
53 | async def threads(self, ctx: Context):
54 |
55 | await ctx.trigger_typing()
56 |
57 | async def get_threads(channel: TextChannel) -> list[tuple[Thread, bool]]:
58 | out_ = channel.threads.copy()
59 | out_ += await channel.archived_threads(limit=None).flatten()
60 | if channel.permissions_for(ctx.guild.me).manage_threads and not channel.is_news():
61 | out_ += await channel.archived_threads(limit=None, private=True).flatten()
62 | return [
63 | (thread_, any(member.id == ctx.author.id for member in await thread_.fetch_members()))
64 | for thread_ in out_
65 | ]
66 |
67 | def last_timestamp(thread: Thread) -> datetime:
68 | return snowflake_time(thread.last_message_id)
69 |
70 | threads = [
71 | *{
72 | thread
73 | for threads in await asyncio.gather(
74 | *[
75 | get_threads(channel)
76 | for channel in ctx.guild.text_channels
77 | if channel.permissions_for(ctx.guild.me).read_message_history
78 | and channel.permissions_for(ctx.author).view_channel
79 | ]
80 | )
81 | for thread in threads
82 | }
83 | ]
84 | threads.sort(key=lambda x: last_timestamp(x[0]), reverse=True)
85 |
86 | out = []
87 | thread: Thread
88 | for thread, joined in threads:
89 | if not thread.permissions_for(ctx.author).view_channel:
90 | continue
91 |
92 | line = f":small_{'blue' if thread.archived else 'orange'}_diamond: "
93 | line += ":white_check_mark: " if joined else ":x: "
94 | if thread.archived:
95 | line += f"[#{thread.name}](https://discord.com/channels/{thread.guild.id}/{thread.id})"
96 | else:
97 | line += f"{thread.mention}"
98 | line += f" ({thread.parent.mention}, {format_dt(last_timestamp(thread), style='R')})"
99 | out.append(line)
100 |
101 | embed = Embed(title=t.threads, description="\n".join(out), color=Colors.Threads)
102 | if not out:
103 | embed.description = t.no_threads
104 | embed.colour = Colors.error
105 |
106 | await send_long_embed(ctx, embed, paginate=True)
107 |
--------------------------------------------------------------------------------
/moderation/threads/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Threads = MaterialColors.indigo[900]
6 |
--------------------------------------------------------------------------------
/moderation/threads/documentation.md:
--------------------------------------------------------------------------------
1 | # Thread Utils
2 |
3 |
4 | *Work in Progress*
5 |
--------------------------------------------------------------------------------
/moderation/threads/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class ThreadsPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.threads.permissions[self.name]
11 |
12 | list = auto()
13 |
--------------------------------------------------------------------------------
/moderation/threads/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | list: list all existing threads
3 |
4 | commands:
5 | threads: list all existing threads
6 |
7 | threads: Threads
8 | no_threads: No threads have been created yet!
9 | new_thread_alert: Thread {} has been created in {} but auto join members could not be added, because I don't have permission to send messages there!
--------------------------------------------------------------------------------
/moderation/user_notes/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import UserNoteCog
2 |
3 |
4 | __all__ = ["UserNoteCog"]
5 |
--------------------------------------------------------------------------------
/moderation/user_notes/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | user_notes = MaterialColors.lime["a700"]
6 |
--------------------------------------------------------------------------------
/moderation/user_notes/documentation.md:
--------------------------------------------------------------------------------
1 | # User Notes
2 |
3 | Contains the `.user_notes` command to manage user notes.
4 |
5 |
6 | ## `user_notes`
7 |
8 | Contains subcommands to manage user notes.
9 |
10 | ```css
11 | .[user_notes|un]
12 | ```
13 |
14 | Required Permissions:
15 |
16 | - `user_notes.read`
17 |
18 |
19 | ### `show`
20 |
21 | Shows all notes for a specific user.
22 |
23 | ```css
24 | .user_notes [show|s|list|l]
25 | ```
26 |
27 | Arguments:
28 |
29 | | Argument | Required | Description |
30 | |:--------:|:-------------------------:|:------------|
31 | | `user` | :fontawesome-solid-check: | A user |
32 |
33 | Required Permissions:
34 |
35 | - `user_notes.read`
36 |
37 |
38 | ### `add`
39 |
40 | Adds a note to a specific user.
41 |
42 | ```css
43 | .user_notes [add|a|+]
44 | ```
45 |
46 | Arguments:
47 |
48 | | Argument | Required | Description |
49 | |:---------:|:-------------------------:|:----------------|
50 | | `user` | :fontawesome-solid-check: | A user |
51 | | `content` | :fontawesome-solid-check: | The note to add |
52 |
53 | Required Permissions:
54 |
55 | - `user_notes.read`
56 | - `user_notes.write`
57 |
58 |
59 | ### `remove`
60 |
61 | Removes a user note by note id.
62 |
63 | ```css
64 | .user_notes [remove|r|delete|d|-]
65 | ```
66 |
67 | Arguments:
68 |
69 | | Argument | Required | Description |
70 | |:---------:|:-------------------------:|:------------|
71 | | `note_id` | :fontawesome-solid-check: | A note id |
72 |
73 | Required Permissions:
74 |
75 | - `user_notes.read`
76 | - `user_notes.write`
77 |
--------------------------------------------------------------------------------
/moderation/user_notes/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from datetime import datetime
4 | from typing import Union
5 |
6 | from discord.utils import utcnow
7 | from sqlalchemy import BigInteger, Column, Text
8 |
9 | from PyDrocsid.database import Base, UTCDateTime, db
10 |
11 |
12 | class UserNote(Base):
13 | __tablename__ = "user_notes"
14 |
15 | id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True, autoincrement=True)
16 | member_id: Union[Column, int] = Column(BigInteger)
17 | author_id: Union[Column, int] = Column(BigInteger)
18 | content: Union[Column, str] = Column(Text)
19 | timestamp: Union[Column, datetime] = Column(UTCDateTime)
20 |
21 | @staticmethod
22 | async def create(member_id: int, author_id: int, content: str) -> UserNote:
23 | row = UserNote(member_id=member_id, author_id=author_id, content=content, timestamp=utcnow())
24 | await db.add(row)
25 | return row
26 |
--------------------------------------------------------------------------------
/moderation/user_notes/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class UserNotePermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.user_notes.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/moderation/user_notes/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: read user notes
3 | write: create and delete user notes
4 |
5 | commands:
6 | user_notes: mange notes for users
7 | user_notes_show: display notes for a specific user
8 | user_notes_add: add a note for a specific user
9 | user_notes_remove: delete a user note by specifying its id
10 |
11 | user_notes: "User Notes"
12 | too_long: Note content is too long.
13 | no_notes: No notes have been created yet for this user.
14 | user_note_entry: |
15 | ID: `{id}`, Author: {author}
16 | {content}
17 | note_not_found: "The note was not found."
18 | new_note: |
19 | {} added a new note for {}:
20 | {}
21 | removed_note: |
22 | {} removed a note from {}:
23 | {}
24 | content: Content
25 | ulog_entry: ":notepad_spiral: **User note created** (by {}): {}"
26 |
27 | confirmation: "Confirmation"
28 | confirm: |
29 | Are you sure you want to delete this note from {}?
30 | {}
31 | canceled: ":x: Canceled"
32 | confirmed: "Confirmed :white_check_mark:"
33 |
--------------------------------------------------------------------------------
/moderation/verification/__init__.py:
--------------------------------------------------------------------------------
1 | from .cog import VerificationCog
2 |
3 |
4 | __all__ = ["VerificationCog"]
5 |
--------------------------------------------------------------------------------
/moderation/verification/colors.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.material_colors import MaterialColors
2 |
3 |
4 | class Colors(MaterialColors):
5 | Verification = MaterialColors.green["a100"]
6 |
--------------------------------------------------------------------------------
/moderation/verification/documentation.md:
--------------------------------------------------------------------------------
1 | # Verification
2 |
3 | Contains a verification system that adds or removes certain roles from a member when they send the bot a command with a pre-set password via direct messages.
4 |
5 |
6 | ## `verification`
7 |
8 | Shows the current verification configuration. This includes the roles that will be added to or removed from the member (verification roles), the configured password and the amount of time a member has to be in the server before they can verify.
9 |
10 | ```css
11 | .[verification|vf]
12 | ```
13 |
14 | Required Permissions:
15 |
16 | - `verification.read`
17 |
18 |
19 | ### `add`
20 |
21 | Adds a verification role. If you set `reverse` to `true`, the role will be removed from the member instead of being added. Note that verification will fail if the member does not have all reverse verification roles!
22 |
23 | ```css
24 | .verification [add|a|+] [reverse=False]
25 | ```
26 |
27 | Arguments:
28 |
29 | | Argument | Required | Description |
30 | |:---------:|:-------------------------:|:-----------------------------------------------------|
31 | | `role` | :fontawesome-solid-check: | The verification role |
32 | | `reverse` | | Remove this role instead of adding it to the member. |
33 |
34 | Required Permissions:
35 |
36 | - `verification.read`
37 | - `verification.write`
38 |
39 |
40 | ### `remove`
41 |
42 | Removes an existing verification role.
43 |
44 | ```css
45 | .verification [remove|r|-]
46 | ```
47 |
48 | Arguments:
49 |
50 | | Argument | Required | Description |
51 | |:--------:|:-------------------------:|:----------------------|
52 | | `role` | :fontawesome-solid-check: | The verification role |
53 |
54 | Required Permissions:
55 |
56 | - `verification.read`
57 | - `verification.write`
58 |
59 |
60 | ### `password`
61 |
62 | Sets the *secret* password the member will need to verify with.
63 |
64 | ```css
65 | .verification [password|p]
66 | ```
67 |
68 | Arguments:
69 |
70 | | Argument | Required | Description |
71 | |:----------:|:-------------------------:|:-------------|
72 | | `password` | :fontawesome-solid-check: | The password |
73 |
74 | Required Permissions:
75 |
76 | - `verification.read`
77 | - `verification.write`
78 |
79 | !!! note
80 | The password has a maximum length of 256 characters.
81 |
82 |
83 | ### `delay`
84 |
85 | Sets the amount of time a member has to be in the server before they can verify.
86 |
87 | ```css
88 | .verification [delay|d]
89 | ```
90 |
91 | Arguments:
92 |
93 | | Argument | Required | Description |
94 | |:--------:|:-------------------------:|:------------------------------|
95 | | `delay` | :fontawesome-solid-check: | The amount of time in seconds |
96 |
97 | Required Permissions:
98 |
99 | - `verification.read`
100 | - `verification.write`
101 |
102 |
103 | ## `verify`
104 |
105 | Allows server members to verify themselves. If the specified password is correct and the configured delay has elapsed, the configured verification roles will be added to or removed from the member.
106 |
107 | ```css
108 | verify
109 | ```
110 |
111 | Arguments:
112 |
113 | | Argument | Required | Description |
114 | |:----------:|:-------------------------:|:--------------------------|
115 | | `password` | :fontawesome-solid-check: | The verification password |
116 |
117 | !!! note
118 | As this command can only be used in direct messages, it does not start with the configured bot prefix! So, for example, if the configured password is `Tr0ub4dor&3`, a member would have to send this exact message to the bot to complete verification:
119 |
120 | ```
121 | verify Tr0ub4dor&3
122 | ```
123 |
--------------------------------------------------------------------------------
/moderation/verification/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Union
4 |
5 | from sqlalchemy import BigInteger, Boolean, Column
6 |
7 | from PyDrocsid.database import Base, db
8 |
9 |
10 | class VerificationRole(Base):
11 | __tablename__ = "verification_role"
12 |
13 | role_id: Union[Column, int] = Column(BigInteger, primary_key=True, unique=True)
14 | reverse: Union[Column, bool] = Column(Boolean)
15 |
16 | @staticmethod
17 | async def create(role_id: int, reverse: bool) -> VerificationRole:
18 | row = VerificationRole(role_id=role_id, reverse=reverse)
19 | await db.add(row)
20 | return row
21 |
--------------------------------------------------------------------------------
/moderation/verification/permissions.py:
--------------------------------------------------------------------------------
1 | from enum import auto
2 |
3 | from PyDrocsid.permission import BasePermission
4 | from PyDrocsid.translations import t
5 |
6 |
7 | class VerificationPermission(BasePermission):
8 | @property
9 | def description(self) -> str:
10 | return t.verification.permissions[self.name]
11 |
12 | read = auto()
13 | write = auto()
14 |
--------------------------------------------------------------------------------
/moderation/verification/settings.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.settings import Settings
2 |
3 |
4 | class VerificationSettings(Settings):
5 | password = "" # noqa: S105
6 | delay = -1
7 |
--------------------------------------------------------------------------------
/moderation/verification/translations/en.yml:
--------------------------------------------------------------------------------
1 | permissions:
2 | read: read verification command configuration
3 | write: write verification command configuration
4 |
5 | verification: Verification
6 | verification_role_added: "Verification role has been added. :white_check_mark:"
7 | log_verification_role_added: "**Verification role** has been **added**: `@{}` ({}) (normal)"
8 | log_verification_role_added_reverse: "**Verification role** has been **added**: `@{}` ({}) (reverse)"
9 | verification_role_removed: " Verification role has been removed. :white_check_mark:"
10 | log_verification_role_removed: "**Verification role** has been **removed**: `@{}` ({})"
11 | password_too_long: Password is too long, sorry.
12 | verification_password_configured: "Verification password has been configured. :white_check_mark:"
13 | log_verification_password_configured: "**Verification password** has been **set** to `{}`"
14 | verification_delay_configured: "Verification delay has been configured. :white_check_mark:"
15 | verification_delay_disabled: "**Verification delay** has been **disabled**. :white_check_mark:"
16 | log_verification_delay_configured:
17 | one: "**Verification delay** has been **set** to {cnt} second."
18 | many: "**Verification delay** has been **set** to {cnt} seconds."
19 | verification_disabled: Verification command is disabled.
20 | verification_enabled: Verification command is enabled.
21 | password: Password
22 | roles_normal: Roles (normal)
23 | roles_reverse: Roles (reverse)
24 | verification_role: " - `@{}` ({}) (normal)"
25 | verification_role_reverse: " - `@{}` ({}) (reverse)"
26 | private_only: This command can only be used in direct messages.
27 | already_verified: You are already verified.
28 | password_incorrect: Password is incorrect!
29 | too_soon: Wow, you must have read the rules pretty quickly! Maybe just read the rules again more thoroughly and try again later...
30 | verified: "Verified successfully :white_check_mark:"
31 | verification_role_already_set: Role already set.
32 | verification_role_not_set: Role not set.
33 | verification_failed: Verification failed!
34 | x_seconds:
35 | one: "{cnt} second"
36 | many: "{cnt} seconds"
37 | role_not_set_too_high: Role could not be set because `@{}` is higher than `@{}`.
38 | role_not_set_managed_role: Role could not be set because `@{}` cannot be assigned manually.
39 | cannot_assign:
40 | one: Verification could not assign {roles} to {member.mention} ({member.id}).
41 | many: "Verification could not assign the following roles to {member.mention} ({member.id}): {roles}"
42 |
--------------------------------------------------------------------------------
/pre-commit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # install via ./pre-commit.sh install
4 |
5 | SELF=$(realpath $0)
6 | SRC=$(realpath pre-commit.sh)
7 | _HOOK=$(git rev-parse --git-path hooks)/pre-commit
8 | HOOK=$(realpath $_HOOK)
9 |
10 | install() {
11 | echo Installing pre-commit hook...
12 | rm -f $_HOOK
13 | cp $SRC $HOOK && chmod +x $HOOK && echo Pre-Commit hook has been installed successfully!
14 | }
15 |
16 | uninstall() {
17 | echo Uninstalling pre-commit hook...
18 | rm -f $_HOOK && echo Pre-Commit hook has been uninstalled successfully!
19 | }
20 |
21 | save() {
22 | git submodule foreach "git name-rev --name-only --always --exclude='tags/*' HEAD > .head.ref && /bin/bash $SELF save" && git submodule update
23 |
24 | git diff > .unstaged.patch
25 | git diff --staged > .staged.patch
26 | git restore -WS .
27 | }
28 |
29 | restore() {
30 | git submodule foreach "git restore . && git checkout \$(cat .head.ref) && rm .head.ref && /bin/bash $SELF restore"
31 |
32 | git apply -3 --allow-empty .staged.patch && rm .staged.patch || echo "Warning: Could not restore staged changes in $(pwd)"
33 | git apply --allow-empty .unstaged.patch && rm .unstaged.patch || echo "Warning: Could not restore unstaged changes in $(pwd)"
34 | }
35 |
36 | if [[ "$1" =~ ^(install|uninstall|save|restore)$ ]]; then
37 | $1
38 | exit $?
39 | fi
40 |
41 | if ! cmp -s $SRC $HOOK; then
42 | set -e
43 | echo Updating pre-commit hook...
44 | rm -f $_HOOK
45 | cp $SRC $HOOK && chmod +x $HOOK && echo Pre-Commit hook has been updated successfully!
46 | /bin/bash $HOOK "$@"
47 | exit $?
48 | elif [[ "$SELF" != "$HOOK" ]]; then
49 | /bin/bash $HOOK "$@"
50 | exit $?
51 | fi
52 |
53 | save
54 | git apply --allow-empty --index .staged.patch && rm .staged.patch && touch .staged.patch
55 | git add -u
56 |
57 | $HOME/.local/bin/poe pre-commit
58 | code=$?
59 |
60 | git add -u
61 |
62 | restore
63 |
64 | exit $code
65 |
--------------------------------------------------------------------------------
/pubsub.py:
--------------------------------------------------------------------------------
1 | from PyDrocsid.pubsub import PubSubChannel
2 |
3 |
4 | send_to_changelog = PubSubChannel()
5 | send_alert = PubSubChannel()
6 | log_auto_kick = PubSubChannel()
7 | get_user_info_entries = PubSubChannel()
8 | get_user_status_entries = PubSubChannel()
9 | get_userlog_entries = PubSubChannel()
10 | revoke_verification = PubSubChannel()
11 | can_respond_on_reaction = PubSubChannel()
12 | ignore_message_edit = PubSubChannel()
13 | ignore_message_delete = PubSubChannel()
14 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "cogs"
3 | version = "0"
4 | description = ""
5 | authors = ["Defelo "]
6 | readme = "README.md"
7 | license = "GPL-3.0-only"
8 | homepage = "https://github.com/PyDrocsid/cogs"
9 | repository = "https://github.com/PyDrocsid/cogs"
10 |
11 | [tool.poetry.dependencies]
12 | python = "^3.10"
13 | PyDrocsid = "^2.3.4"
14 | SQLAlchemy = "^1.4.32"
15 | aiohttp = "^3.8.1"
16 |
17 | # general: custom_commands
18 | # integrations: adventofcode, cleverbot
19 | # moderation: invites
20 | requests = "^2.27.1"
21 |
22 | # information: user_info
23 | python-dateutil = "^2.8.2"
24 |
25 |
26 | [tool.poetry.dev-dependencies]
27 | flake8 = "^4.0.1"
28 | isort = "^5.10.1"
29 | black = "^22.3.0"
30 | wemake-python-styleguide = "^0.16.1"
31 | mypy = "^0.942"
32 | SQLAlchemy = { extras = ["mypy"], version = "^1.4.32" }
33 | types-python-dateutil = "^2.8.10"
34 | types-requests = "^2.27.15"
35 | types-PyYAML = "^6.0.5"
36 |
37 | [tool.poe.tasks]
38 | flake8 = "flake8 . --count --statistics --show-source"
39 | isort = "isort ."
40 | black = "black ."
41 | format = ["isort", "black"]
42 | mypy = "mypy ."
43 | lint = ["format", "flake8"]
44 | pre-commit = ["lint"]
45 |
46 | [tool.poe.tasks.setup]
47 | shell = """
48 | set -ex
49 | poetry install --no-root
50 | ./pre-commit.sh install
51 | """
52 | interpreter = "bash"
53 |
54 | [tool.black]
55 | target-version = ["py310"]
56 | line-length = 120
57 | skip-magic-trailing-comma = true
58 |
59 | [tool.isort]
60 | profile = "black"
61 | py_version = 310
62 | line_length = 120
63 | lines_after_imports = 2
64 | reverse_relative = true
65 | known_pydrocsid = ["PyDrocsid"]
66 | sections = ["FUTURE", "STDLIB", "FIRSTPARTY", "THIRDPARTY", "PYDROCSID", "LOCALFOLDER"]
67 |
68 | [tool.mypy]
69 | strict = true
70 | ignore_missing_imports = true
71 | no_incremental = true
72 | namespace_packages = true
73 | plugins = ["sqlalchemy.ext.mypy.plugin"]
74 |
75 | [build-system]
76 | requires = ["setuptools", "poetry-core>=1.0.0"]
77 | build-backend = "poetry.core.masonry.api"
78 |
--------------------------------------------------------------------------------
/translations.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | from PyDrocsid.translations import load_translations
4 |
5 |
6 | load_translations(Path(__file__).parent)
7 |
--------------------------------------------------------------------------------