├── tuxbot ├── py.typed ├── cogs │ ├── Math │ │ ├── images │ │ │ ├── __init__.py │ │ │ └── load_fail.png │ │ ├── commands │ │ │ ├── Graph │ │ │ │ └── __init__.py │ │ │ ├── Wolf │ │ │ │ └── __init__.py │ │ │ ├── Factor │ │ │ │ └── __init__.py │ │ │ └── __init__.py │ │ ├── converters │ │ │ ├── __init__.py │ │ │ └── ExprConverter.py │ │ └── __init__.py │ ├── Auto │ │ ├── commands │ │ │ ├── AutoPin │ │ │ │ ├── ui │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── modals │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ └── PinThresholdModal.py │ │ │ │ │ ├── pages │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── Embed.py │ │ │ │ │ │ └── GlobalEmbed.py │ │ │ │ │ ├── panels.py │ │ │ │ │ └── buttons │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── DeleteButton.py │ │ │ │ │ │ ├── ToggleButton.py │ │ │ │ │ │ └── ThresholdButton.py │ │ │ │ ├── models │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── AutoPin.py │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ ├── AutoQuote │ │ │ │ ├── ui │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── pages │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── GlobalEmbed.py │ │ │ │ │ │ └── Embed.py │ │ │ │ │ ├── panels.py │ │ │ │ │ └── buttons │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── DeleteButton.py │ │ │ │ │ │ └── ToggleButton.py │ │ │ │ ├── models │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── AutoQuote.py │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ └── __init__.py │ │ └── listeners │ │ │ ├── __init__.py │ │ │ ├── Message │ │ │ ├── __init__.py │ │ │ ├── workers │ │ │ │ └── __init__.py │ │ │ └── listener.py │ │ │ └── RawReactionAdd │ │ │ ├── __init__.py │ │ │ ├── workers │ │ │ ├── __init__.py │ │ │ └── AutoPin.py │ │ │ └── listener.py │ ├── Tags │ │ └── commands │ │ │ ├── Tag │ │ │ ├── ui │ │ │ │ ├── __init__.py │ │ │ │ ├── modals │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── TagEditionModal.py │ │ │ │ └── paginator.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ └── Tags.py │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ └── exceptions.py │ ├── Utils │ │ ├── commands │ │ │ ├── Avatar │ │ │ │ ├── ui │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── panels.py │ │ │ │ │ ├── buttons │ │ │ │ │ │ ├── __init__.py │ │ │ │ │ │ ├── JPGButton.py │ │ │ │ │ │ ├── PNGButton.py │ │ │ │ │ │ ├── WEBPButton.py │ │ │ │ │ │ ├── GIFButton.py │ │ │ │ │ │ └── DeleteButton.py │ │ │ │ │ └── ViewController.py │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ ├── UI │ │ │ │ └── __init__.py │ │ │ ├── Info │ │ │ │ └── __init__.py │ │ │ ├── Ping │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ ├── Quote │ │ │ │ ├── __init__.py │ │ │ │ └── converters │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── QuoteConverter.py │ │ │ ├── Credits │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ ├── Invite │ │ │ │ └── __init__.py │ │ │ ├── Source │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ ├── __init__.py │ │ │ └── exceptions.py │ │ └── converters │ │ │ ├── __init__.py │ │ │ └── MemberOrUserConverter.py │ ├── Polls │ │ ├── commands │ │ │ ├── Poll │ │ │ │ ├── models │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── Choices.py │ │ │ │ │ └── Polls.py │ │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ └── exceptions.py │ │ └── listeners │ │ │ ├── __init__.py │ │ │ ├── RawReactionAdd │ │ │ ├── __init__.py │ │ │ └── listener.py │ │ │ └── RawReactionRemove │ │ │ ├── __init__.py │ │ │ └── listener.py │ ├── Network │ │ └── commands │ │ │ ├── Iplocalise │ │ │ ├── ui │ │ │ │ ├── __init__.py │ │ │ │ ├── pages │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── Embed.py │ │ │ │ ├── panels.py │ │ │ │ └── buttons │ │ │ │ │ ├── BGPButton.py │ │ │ │ │ ├── IPInfoButton.py │ │ │ │ │ ├── DeleteButton.py │ │ │ │ │ ├── GeoButton.py │ │ │ │ │ ├── GlobalButton.py │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── WhoisButton.py │ │ │ │ │ ├── RawButton.py │ │ │ │ │ └── ASNButton.py │ │ │ ├── __init__.py │ │ │ ├── converters │ │ │ │ ├── __init__.py │ │ │ │ ├── IPConverter.py │ │ │ │ └── InetConverter.py │ │ │ ├── exceptions.py │ │ │ └── providers │ │ │ │ ├── abc.py │ │ │ │ ├── __init__.py │ │ │ │ ├── IPGeolocationProvider.py │ │ │ │ ├── MapProvider.py │ │ │ │ ├── HostnameProvider.py │ │ │ │ ├── OpenCageDataProvider.py │ │ │ │ ├── IPInfoProvider.py │ │ │ │ ├── IPWhoisProvider.py │ │ │ │ └── base.py │ │ │ ├── Dig │ │ │ └── __init__.py │ │ │ ├── __init__.py │ │ │ ├── Getheaders │ │ │ ├── __init__.py │ │ │ └── exceptions.py │ │ │ ├── Peeringdb │ │ │ ├── __init__.py │ │ │ ├── converters │ │ │ │ ├── __init__.py │ │ │ │ └── ASConverter.py │ │ │ └── exceptions.py │ │ │ └── exceptions.py │ ├── __init__.py │ ├── Dev │ │ ├── commands │ │ │ ├── HTTP │ │ │ │ ├── __init__.py │ │ │ │ ├── converters │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── HttpCodeConverter.py │ │ │ │ ├── exceptions.py │ │ │ │ ├── HTTPs │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── OneXX.py │ │ │ │ │ ├── ThreeXX.py │ │ │ │ │ ├── TwoXX.py │ │ │ │ │ ├── HttpCode.py │ │ │ │ │ └── FiveXX.py │ │ │ │ ├── http.py │ │ │ │ └── command.py │ │ │ ├── __init__.py │ │ │ └── exceptions.py │ │ └── __init__.py │ ├── Help │ │ ├── commands │ │ │ ├── __init__.py │ │ │ └── Help │ │ │ │ └── __init__.py │ │ └── __init__.py │ ├── Admin │ │ ├── commands │ │ │ ├── Sync │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ ├── __init__.py │ │ │ ├── Restart │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ └── Update │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ └── __init__.py │ ├── Linux │ │ ├── commands │ │ │ ├── CNF │ │ │ │ ├── __init__.py │ │ │ │ └── exceptions.py │ │ │ ├── __init__.py │ │ │ └── exceptions.py │ │ └── __init__.py │ ├── Logs │ │ ├── commands │ │ │ ├── Stats │ │ │ │ ├── __init__.py │ │ │ │ └── command.py │ │ │ └── __init__.py │ │ └── listeners │ │ │ ├── Ready │ │ │ ├── __init__.py │ │ │ └── listener.py │ │ │ ├── __init__.py │ │ │ ├── Message │ │ │ ├── __init__.py │ │ │ └── listener.py │ │ │ ├── GuildJoin │ │ │ ├── __init__.py │ │ │ └── listener.py │ │ │ ├── GuildRemove │ │ │ ├── __init__.py │ │ │ └── listener.py │ │ │ ├── CommandError │ │ │ └── __init__.py │ │ │ ├── AppCommandError │ │ │ └── __init__.py │ │ │ ├── SocketRawReceive │ │ │ ├── __init__.py │ │ │ └── listener.py │ │ │ ├── CommandCompletion │ │ │ ├── __init__.py │ │ │ └── listener.py │ │ │ └── AppCommandCompletion │ │ │ ├── __init__.py │ │ │ └── listener.py │ └── Random │ │ └── commands │ │ ├── Cat │ │ ├── __init__.py │ │ └── command.py │ │ ├── Dog │ │ ├── __init__.py │ │ └── command.py │ │ ├── Coin │ │ ├── __init__.py │ │ └── command.py │ │ ├── Duck │ │ ├── __init__.py │ │ └── command.py │ │ ├── Koala │ │ ├── __init__.py │ │ └── command.py │ │ ├── Panda │ │ ├── __init__.py │ │ └── command.py │ │ ├── __init__.py │ │ ├── RedPanda │ │ ├── __init__.py │ │ └── command.py │ │ └── exceptions.py ├── core │ ├── models │ │ ├── __init__.py │ │ ├── fields │ │ │ ├── __init__.py │ │ │ └── BigIntArrayField.py │ │ ├── Tuxbot.py │ │ └── Guild.py │ ├── collections │ │ └── __init__.py │ ├── utils │ │ ├── Debug │ │ │ ├── __init__.py │ │ │ └── TimeSpent.py │ │ ├── Paginator │ │ │ ├── __init__.py │ │ │ └── SimplePages.py │ │ ├── Colors.py │ │ ├── Emotes.py │ │ ├── __init__.py │ │ └── Generators.py │ ├── config.py │ ├── __init__.py │ └── redis.py ├── abc │ ├── __init__.py │ └── ModuleABC.py ├── misc │ └── logo.txt ├── __init__.py └── start.py ├── settings ├── __init__.py ├── local.py └── production.py ├── .flake8 ├── .isort.cfg ├── .idea ├── whitesource │ └── PyCharmUsageMetrics.txt ├── codeStyles │ └── codeStyleConfig.xml ├── whiteSource │ └── projectService.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── modules.xml ├── markdown.xml ├── sshConfigs.xml ├── statistic.xml ├── discord.xml ├── runConfigurations │ └── Rewrite.xml ├── webResources.xml ├── misc.xml ├── vcs.xml ├── dataSources.xml ├── webServers.xml ├── deployment.xml └── tuxbot_bot.iml ├── docker ├── local │ └── bot │ │ ├── start │ │ ├── entrypoint │ │ └── Dockerfile └── production │ ├── bot │ ├── start │ ├── entrypoint │ └── Dockerfile │ └── postgres │ ├── maintenance │ ├── _sourced │ │ ├── constants.sh │ │ ├── yes_no.sh │ │ ├── countdown.sh │ │ └── messages.sh │ ├── backups │ ├── backup │ └── restore │ └── Dockerfile ├── .dockerignore ├── .deepsource.toml ├── .envs.example ├── .local │ ├── .postgres │ ├── .cogs │ └── .bot └── .production │ ├── .postgres │ ├── .cogs │ └── .bot ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── issue_template.md ├── dependabot.yml └── workflows │ └── ci.yml ├── Makefile ├── .pylintrc ├── scripts ├── merge_dotenvs_to_dotenv.py └── test_database.py ├── .gitignore ├── local.yml ├── production.yml ├── README.rst ├── .pre-commit-config.yaml └── pyproject.toml /tuxbot/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/images/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/Tag/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = "docker/scripts/" 3 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/commands/Poll/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/Tag/ui/modals/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/modals/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/pages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tuxbot/core/models/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of global models for tuxbot 3 | """ 4 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile=black 3 | line_length=79 4 | lines_after_imports=2 5 | -------------------------------------------------------------------------------- /tuxbot/core/collections/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of collections for Tuxbot 3 | """ 4 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/Tag/models/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of Tags models for tuxbot 3 | """ 4 | -------------------------------------------------------------------------------- /tuxbot/core/models/fields/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of global fields for tuxbot models 3 | """ 4 | -------------------------------------------------------------------------------- /tuxbot/core/utils/Debug/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of utils debugging functions and classes 3 | """ 4 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/models/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of Settings models for AutoPin 3 | """ 4 | -------------------------------------------------------------------------------- /tuxbot/cogs/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs 3 | ~~~~~~~~~~~~ 4 | 5 | Set of all Tuxbot cogs. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/models/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of Settings models for AutoQuote 3 | """ 4 | -------------------------------------------------------------------------------- /tuxbot/abc/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.abc 3 | ~~~~~~~~~~~ 4 | 5 | A module who contains all Abstract Classes. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/images/load_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rom1-J/tuxbot-bot/HEAD/tuxbot/cogs/Math/images/load_fail.png -------------------------------------------------------------------------------- /.idea/whitesource/PyCharmUsageMetrics.txt: -------------------------------------------------------------------------------- 1 | {"dataValues":{"C3":"false","C4":"false","VULN_INFO_CL":"0","C1":"false","C2":"L"}} 2 | -------------------------------------------------------------------------------- /docker/local/bot/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | poetry run start 9 | -------------------------------------------------------------------------------- /docker/production/bot/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | poetry run start 9 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev.commands.HTTP.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Help/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Help.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Tuxbot help command. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/Tag/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Tags.commands.Tag.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/UI/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.UI.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/commands/Sync/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin.commands.Sync.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of owner only command. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Help/commands/Help/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Help.commands.Help.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Linux/commands/CNF/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Linux.commands.CNF.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/commands/Stats/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.commands.Stats.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/commands/Graph/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Math.commands.Graph.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/commands/Wolf/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Math.commands.Wolf.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/commands/Poll/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Tags.commands.Tag.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Cat/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Cat.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Dog/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Dog.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Info/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Info.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Ping/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Ping.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /docker/production/postgres/maintenance/_sourced/constants.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | BACKUP_DIR_PATH='/backups' 5 | BACKUP_FILE_PREFIX='backup' 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful automatics commands. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful statistics commands. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/Ready/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.Ready.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/commands/Factor/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Math.commands.factor.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Math.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for maths. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Dig/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.Dig.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Coin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Coin.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Duck/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Duck.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Tags.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for tags. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Quote/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Quote.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/commands/Restart/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin.commands.Restart.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/commands/Update/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin.commands.Update.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.commands.AutoPin.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful automatic workers. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for developers. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful statistics workers. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Polls.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for polls. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/listeners/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Polls.listeners 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of listeners for Polls cog. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Koala/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Koala.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Panda/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Panda.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for tuxbot. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Avatar.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Credits/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Credits.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Invite/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Invite.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Source/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Source.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for tuxbot. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.commands.AutoQuote.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/Message/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners.Message.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/Message/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.Message.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/RedPanda/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.RedPanda.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | .gitignore 3 | .idea 4 | .pre-commit-config.yaml 5 | .isort.cfg 6 | .pylintrc 7 | dump.rdb 8 | Tuxbot_bot.egg-info 9 | .mypy_cache 10 | -------------------------------------------------------------------------------- /tuxbot/cogs/Linux/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Linux.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for GNU/Linux users. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/GuildJoin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.GuildJoin.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for networking. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/GuildRemove/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.GuildRemove.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Getheaders/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.Peeringdb.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.Iplocalise.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Peeringdb/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.Peeringdb.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/converters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Useful global converters for Quote command. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/converters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Useful global converters for Dev module. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/CommandError/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.CommandError.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/RawReactionAdd/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners.RawReactionAdd.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/AppCommandError/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.AppCommandError.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/listeners/RawReactionAdd/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Polls.listeners.RawReactionAdd.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/SocketRawReceive/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.SocketRawReceive.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Quote/converters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Useful global converters for Quote command. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/CommandCompletion/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.CommandCompletion.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/listeners/RawReactionRemove/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Polls.listeners.RawReactionRemove.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/AppCommandCompletion/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.AppCommandCompletion.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/converters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Math.commands.Graph.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Useful global converters for Math module. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/converters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Useful global converters for Network module. 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Peeringdb/converters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Useful global converters for Network module. 6 | """ 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "shell" 5 | enabled = true 6 | 7 | [[analyzers]] 8 | name = "python" 9 | enabled = true 10 | 11 | [analyzers.meta] 12 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /.envs.example/.local/.postgres: -------------------------------------------------------------------------------- 1 | # PostgreSQL 2 | # ------------------------------------------------------------------------------ 3 | POSTGRES_HOST= 4 | POSTGRES_PORT= 5 | POSTGRES_DB= 6 | POSTGRES_USER= 7 | POSTGRES_PASSWORD= 8 | -------------------------------------------------------------------------------- /tuxbot/core/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tuxbot core module: config 3 | 4 | Contains all config workers 5 | """ 6 | import importlib 7 | import os 8 | 9 | 10 | config = importlib.import_module(os.environ["SETTINGS_MODULE"]) 11 | -------------------------------------------------------------------------------- /.envs.example/.production/.postgres: -------------------------------------------------------------------------------- 1 | # PostgreSQL 2 | # ------------------------------------------------------------------------------ 3 | POSTGRES_HOST= 4 | POSTGRES_PORT= 5 | POSTGRES_DB= 6 | POSTGRES_USER= 7 | POSTGRES_PASSWORD= 8 | -------------------------------------------------------------------------------- /tuxbot/core/utils/Paginator/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of utils paginator functions and classes 3 | 4 | adapted from: 5 | https://github.com/Rapptz/RoboDanny/blob/cd23371cee697d861182a037f90896d9d4a082dd/cogs/utils/paginator.py 6 | """ 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/panels.py: -------------------------------------------------------------------------------- 1 | from .buttons import ButtonType, DeleteButton, ToggleButton 2 | 3 | 4 | class ViewPanel: 5 | buttons: list[list[ButtonType]] = [ 6 | [ToggleButton, DeleteButton], 7 | ] 8 | -------------------------------------------------------------------------------- /.idea/whiteSource/projectService.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/panels.py: -------------------------------------------------------------------------------- 1 | from .buttons import ButtonType, DeleteButton, ThresholdButton, ToggleButton 2 | 3 | 4 | class ViewPanel: 5 | buttons: list[list[ButtonType]] = [ 6 | [ToggleButton, ThresholdButton, DeleteButton], 7 | ] 8 | -------------------------------------------------------------------------------- /docker/production/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:14 2 | 3 | COPY ./docker/production/postgres/maintenance /usr/local/bin/maintenance 4 | RUN chmod +x /usr/local/bin/maintenance/* 5 | RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ 6 | && rmdir /usr/local/bin/maintenance 7 | -------------------------------------------------------------------------------- /tuxbot/misc/logo.txt: -------------------------------------------------------------------------------- 1 | ______ __ __ __ __ 2 | /_ __/_ ___ __/ /_ ____ / /_ _ __/ // / 3 | / / / / / / |/_/ __ \/ __ \/ __/ | | / / // /_ 4 | / / / /_/ /> 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /tuxbot/core/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.core 3 | ~~~~~~~~~~~~ 4 | 5 | A module who contains all tuxbot core components. 6 | """ 7 | 8 | from tuxbot import VersionInfo, __version__, version_info 9 | 10 | 11 | __all__ = ( 12 | "__version__", 13 | "version_info", 14 | "VersionInfo", 15 | ) 16 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev.commands.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Dev module exceptions. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | 11 | class DevException(commands.BadArgument): 12 | """Base Dev module exceptions""" 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Tags.commands.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Tags module exceptions. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | 11 | class TagsException(commands.BadArgument): 12 | """Base Tags module exceptions""" 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Linux/commands/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Linux.commands.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Linux module exceptions. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | 11 | class LinuxException(commands.BadArgument): 12 | """Base Linux module exceptions""" 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/commands/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Polls.commands.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Polls module exceptions. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | 11 | class PollsException(commands.BadArgument): 12 | """Base Polls module exceptions""" 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev.commands.HTTP.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Dev module exceptions. 6 | """ 7 | 8 | from ...commands.exceptions import DevException 9 | 10 | 11 | class UnknownHttpCode(DevException): 12 | """Unknown HTTP code exception""" 13 | -------------------------------------------------------------------------------- /.idea/markdown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/buttons/__init__.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from .DeleteButton import DeleteButton 4 | from .ToggleButton import ToggleButton 5 | 6 | 7 | __all__ = [ 8 | "ButtonType", 9 | "DeleteButton", 10 | "ToggleButton", 11 | ] 12 | 13 | 14 | ButtonType = typing.Union[type[DeleteButton], type[ToggleButton]] 15 | -------------------------------------------------------------------------------- /tuxbot/cogs/Linux/commands/CNF/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Linux.commands.CNF.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Linux module exceptions. 6 | """ 7 | 8 | from ...commands.exceptions import LinuxException 9 | 10 | 11 | class CNFException(LinuxException): 12 | """Failed to fetch from command-not-found.com""" 13 | -------------------------------------------------------------------------------- /.idea/sshConfigs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/statistic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Peeringdb/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.Peeringdb.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Network module exceptions. 6 | """ 7 | 8 | from ..exceptions import NetworkException 9 | 10 | 11 | class InvalidAsn(NetworkException): 12 | """Given ASN doesn't respect any format""" 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/panels.py: -------------------------------------------------------------------------------- 1 | from .buttons import ( 2 | ButtonType, 3 | DeleteButton, 4 | GIFButton, 5 | JPGButton, 6 | PNGButton, 7 | WEBPButton, 8 | ) 9 | 10 | 11 | class ViewPanel: 12 | buttons: list[list[ButtonType]] = [ 13 | [GIFButton, JPGButton, PNGButton, WEBPButton], 14 | [DeleteButton], 15 | ] 16 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Getheaders/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.Getheaders.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Network module exceptions. 6 | """ 7 | 8 | from ..exceptions import NetworkException 9 | 10 | 11 | class UnreachableAddress(NetworkException): 12 | """Given address is unreachable""" 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.Iplocalise.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Network module exceptions. 6 | """ 7 | 8 | from ..exceptions import NetworkException 9 | 10 | 11 | class VersionNotFound(NetworkException): 12 | """Domain not reachable with given inet""" 13 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /tuxbot/core/redis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tuxbot core module: redis 3 | 4 | Manage redis instances 5 | """ 6 | from aioredis.client import Redis 7 | 8 | from tuxbot.core.config import config 9 | 10 | 11 | def connect() -> Redis: 12 | """Connector for redis instance 13 | 14 | Returns 15 | ------- 16 | aioredis.Redis 17 | """ 18 | return Redis().from_url(config.REDIS["default"]) 19 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Rewrite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Utils module exceptions. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | 11 | class UtilsException(commands.BadArgument): 12 | """Base Utils module exceptions""" 13 | 14 | 15 | class UserNotFound(UtilsException): 16 | """Failed find user""" 17 | -------------------------------------------------------------------------------- /docker/production/postgres/maintenance/_sourced/yes_no.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | yes_no() { 5 | declare desc="Prompt for confirmation. \$\"\{1\}\": confirmation message." 6 | local arg1="${1}" 7 | 8 | local response= 9 | read -r -p "${arg1} (y/[n])? " response 10 | if [[ "${response}" =~ ^[Yy]$ ]] 11 | then 12 | exit 0 13 | else 14 | exit 1 15 | fi 16 | } 17 | -------------------------------------------------------------------------------- /docker/production/postgres/maintenance/_sourced/countdown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | countdown() { 5 | declare desc="A simple countdown. Source: https://superuser.com/a/611582" 6 | local seconds="${1}" 7 | local d=$(($(date +%s) + "${seconds}")) 8 | while [ "$d" -ge `date +%s` ]; do 9 | echo -ne "$(date -u --date @$(($d - `date +%s`)) +%H:%M:%S)\r"; 10 | sleep 0.1 11 | done 12 | } 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Random module exceptions. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | 11 | class RandomException(commands.BadArgument): 12 | """Base Random module exceptions""" 13 | 14 | 15 | class APIException(RandomException): 16 | """Failed to fetch from API""" 17 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.commands.exceptions 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Throwable Network module exceptions. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | 11 | class NetworkException(commands.BadArgument): 12 | """Base Network module exceptions""" 13 | 14 | 15 | class RFCReserved(NetworkException): 16 | """Ip reserved as special use""" 17 | -------------------------------------------------------------------------------- /.idea/webResources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/buttons/__init__.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from .DeleteButton import DeleteButton 4 | from .ThresholdButton import ThresholdButton 5 | from .ToggleButton import ToggleButton 6 | 7 | 8 | __all__ = [ 9 | "ButtonType", 10 | "DeleteButton", 11 | "ThresholdButton", 12 | "ToggleButton", 13 | ] 14 | 15 | 16 | ButtonType = typing.Union[ 17 | type[DeleteButton], type[ThresholdButton], type[ToggleButton] 18 | ] 19 | -------------------------------------------------------------------------------- /tuxbot/core/utils/Colors.py: -------------------------------------------------------------------------------- 1 | """ 2 | Discord status colors 3 | """ 4 | 5 | import discord 6 | 7 | 8 | class Colors: 9 | """Enum of all different discord status colors""" 10 | 11 | ONLINE: discord.Color = discord.Color(0x3BA55D) 12 | IDLE: discord.Color = discord.Color(0xFAA81A) 13 | DND: discord.Color = discord.Color(0xED4245) 14 | STREAMING: discord.Color = discord.Color(0x593696) 15 | EMBED_BORDER: discord.Color = discord.Color(0x2F3136) 16 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/panels.py: -------------------------------------------------------------------------------- 1 | from .buttons import ( 2 | ASNButton, 3 | BGPButton, 4 | ButtonType, 5 | DeleteButton, 6 | GeoButton, 7 | GlobalButton, 8 | IPInfoButton, 9 | RawButton, 10 | WhoisButton, 11 | ) 12 | 13 | 14 | class ViewPanel: 15 | buttons: list[list[ButtonType]] = [ 16 | [GlobalButton, GeoButton, RawButton, IPInfoButton, BGPButton], 17 | [WhoisButton, ASNButton, DeleteButton], 18 | ] 19 | -------------------------------------------------------------------------------- /tuxbot/core/utils/Emotes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Emotes used by TuxBot 3 | """ 4 | 5 | 6 | class Emotes: 7 | """Enum of all different emotes used""" 8 | 9 | ALPHABET: tuple[str, ...] = tuple(chr(0x1F1E6 + i) for i in range(26)) 10 | 11 | # https://cdn.discordapp.com/emojis/596577462335307777.webp 12 | PYTHON: str = "<:python:596577462335307777>" 13 | 14 | # https://cdn.discordapp.com/emojis/851473526992797756.webp 15 | WOLFRAMALPHA: str = "<:wolframalpha:851473526992797756>" 16 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /settings/local.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | 5 | # Cogs 6 | # ----------------------------------------------------------------------------- 7 | # Math 8 | WOLFRAMALPHA_KEY = env.str("COGS_WOLFRAMALPHA_KEY") 9 | 10 | # Network 11 | IPINFO_KEY = env.str("COGS_IPINFO_KEY") 12 | GEOAPIFY_KEY = env.str("COGS_GEOAPIFY_KEY") 13 | IPGEOLOCATION_KEY = env.str("COGS_IPGEOLOCATION_KEY") 14 | OPENCAGEDATA_KEY = env.str("COGS_OPENCAGEDATA_KEY") 15 | PEERINGDB_KEY = env.str("COGS_PEERINGDB_KEY") 16 | -------------------------------------------------------------------------------- /docker/production/postgres/maintenance/backups: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ### View backups. 5 | ### 6 | ### Usage: 7 | ### $ docker-docker -f .yml (exec |run --rm) postgres backups 8 | 9 | 10 | set -o errexit 11 | set -o pipefail 12 | set -o nounset 13 | 14 | 15 | working_dir="$(dirname ${0})" 16 | source "${working_dir}/_sourced/constants.sh" 17 | source "${working_dir}/_sourced/messages.sh" 18 | 19 | 20 | message_welcome "These are the backups you have got:" 21 | 22 | ls -lht "${BACKUP_DIR_PATH}" 23 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/abc.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.functions.providers.abc 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Abstract Class for providers. 6 | """ 7 | import typing 8 | from abc import ABC 9 | 10 | 11 | class Provider(ABC): 12 | """Abstract Class for providers.""" 13 | 14 | def __init__(self, apikey: str | None = None): 15 | self.apikey = apikey 16 | 17 | async def fetch(self, ip: str) -> tuple[str, dict[str, typing.Any] | str]: 18 | ... 19 | -------------------------------------------------------------------------------- /docker/local/bot/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | if [ -z "${POSTGRES_USER}" ]; then 9 | base_postgres_image_default_user='postgres' 10 | export POSTGRES_USER="${base_postgres_image_default_user}" 11 | fi 12 | export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" 13 | export SETTINGS_MODULE="settings.local" 14 | export PYTHON_ENV="development" 15 | 16 | poetry run test-database 17 | 18 | >&2 echo 'PostgreSQL is available' 19 | 20 | exec "$@" 21 | -------------------------------------------------------------------------------- /docker/production/bot/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | if [ -z "${POSTGRES_USER}" ]; then 9 | base_postgres_image_default_user='postgres' 10 | export POSTGRES_USER="${base_postgres_image_default_user}" 11 | fi 12 | export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" 13 | export SETTINGS_MODULE="settings.production" 14 | export PYTHON_ENV="production" 15 | 16 | poetry run test-database 17 | 18 | >&2 echo 'PostgreSQL is available' 19 | 20 | exec "$@" 21 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postgresql 6 | true 7 | org.postgresql.Driver 8 | jdbc:postgresql://localhost/tuxbot_dev 9 | $ProjectFileDir$ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/HTTPs/__init__.py: -------------------------------------------------------------------------------- 1 | from . import FiveXX, FourXX, OneXX, ThreeXX, TwoXX 2 | from .FiveXX import * # noqa: F401, F403 3 | from .FourXX import * # noqa: F401, F403 4 | from .HttpCode import HttpCode 5 | from .OneXX import * # noqa: F401, F403 6 | from .ThreeXX import * # noqa: F401, F403 7 | from .TwoXX import * # noqa: F401, F403 8 | 9 | 10 | __all__ = ( 11 | "HttpCode", 12 | *OneXX.__all__, # noqa: F405 13 | *TwoXX.__all__, # noqa: F405 14 | *ThreeXX.__all__, # noqa: F405 15 | *FourXX.__all__, # noqa: F405 16 | *FiveXX.__all__, # noqa: F405 17 | ) 18 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/buttons/__init__.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from .DeleteButton import DeleteButton 4 | from .GIFButton import GIFButton 5 | from .JPGButton import JPGButton 6 | from .PNGButton import PNGButton 7 | from .WEBPButton import WEBPButton 8 | 9 | 10 | __all__ = [ 11 | "ButtonType", 12 | "GIFButton", 13 | "JPGButton", 14 | "PNGButton", 15 | "WEBPButton", 16 | "DeleteButton", 17 | ] 18 | 19 | ButtonType = typing.Union[ 20 | type[GIFButton], 21 | type[JPGButton], 22 | type[PNGButton], 23 | type[WEBPButton], 24 | type[DeleteButton], 25 | ] 26 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/http.py: -------------------------------------------------------------------------------- 1 | from .HTTPs import * # noqa: F403 2 | 3 | 4 | def http_if_exists(code: int) -> type[HttpCode] | None: # noqa: F405 5 | """Check if HTTP class for this code exists, 6 | if it does, return class instance. 7 | 8 | Parameters 9 | ---------- 10 | code: int 11 | HTTP code 12 | 13 | Returns 14 | ------- 15 | Optional[Type[HttpCode]] 16 | """ 17 | 18 | if (http := globals().get(f"Http{code}")) and isinstance( 19 | http(), HttpCode # noqa: F405 20 | ): 21 | return http # type: ignore 22 | 23 | return None 24 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/pages/GlobalEmbed.py: -------------------------------------------------------------------------------- 1 | """ 2 | Global embed page 3 | """ 4 | 5 | import discord 6 | 7 | from .Embed import Embed 8 | 9 | 10 | class GlobalEmbed(Embed): 11 | """Global embed page""" 12 | 13 | def rebuild(self) -> discord.Embed: 14 | """(Re)build embed""" 15 | 16 | e = discord.Embed( 17 | description=f"Auto quote toggled to `{self.model.activated}`", 18 | colour=self.controller.ctx.bot.utils.colors.ONLINE 19 | if self.model.activated 20 | else self.controller.ctx.bot.utils.colors.DND, 21 | ) 22 | 23 | return e 24 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Peeringdb/converters/ASConverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.converters.ASConverter 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Converter to ip or domain. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | 12 | 13 | ConvertType = str 14 | 15 | 16 | class ASConverter(commands.Converter[ConvertType]): 17 | """Clean user input by removing proto.""" 18 | 19 | async def convert( # type: ignore[override] 20 | self, ctx: commands.Context[TuxbotABC], argument: str 21 | ) -> ConvertType: 22 | return argument.lower().lstrip("as") 23 | -------------------------------------------------------------------------------- /.envs.example/.local/.cogs: -------------------------------------------------------------------------------- 1 | # Math 2 | # ------------------------------------------------------------------------------ 3 | # https://products.wolframalpha.com/api/ 4 | COGS_WOLFRAMALPHA_KEY= 5 | 6 | 7 | # Network 8 | # ------------------------------------------------------------------------------ 9 | # https://ipinfo.io/developers 10 | COGS_IPINFO_KEY= 11 | 12 | # https://www.geoapify.com/get-started-with-maps-api 13 | COGS_GEOAPIFY_KEY= 14 | 15 | # https://ipgeolocation.io/documentation.html 16 | COGS_IPGEOLOCATION_KEY= 17 | 18 | # https://opencagedata.com/api 19 | COGS_OPENCAGEDATA_KEY= 20 | 21 | # https://www.peeringdb.com/profile 22 | COGS_PEERINGDB_KEY= 23 | -------------------------------------------------------------------------------- /.envs.example/.production/.cogs: -------------------------------------------------------------------------------- 1 | # Math 2 | # ------------------------------------------------------------------------------ 3 | # https://products.wolframalpha.com/api/ 4 | COGS_WOLFRAMALPHA_KEY= 5 | 6 | 7 | # Network 8 | # ------------------------------------------------------------------------------ 9 | # https://ipinfo.io/developers 10 | COGS_IPINFO_KEY= 11 | 12 | # https://www.geoapify.com/get-started-with-maps-api 13 | COGS_GEOAPIFY_KEY= 14 | 15 | # https://ipgeolocation.io/documentation.html 16 | COGS_IPGEOLOCATION_KEY= 17 | 18 | # https://opencagedata.com/api 19 | COGS_OPENCAGEDATA_KEY= 20 | 21 | # https://www.peeringdb.com/profile 22 | COGS_PEERINGDB_KEY= 23 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/HTTPs/OneXX.py: -------------------------------------------------------------------------------- 1 | from .HttpCode import HttpCode 2 | 3 | 4 | __all__ = ("Http100", "Http101", "Http102", "Http103") 5 | 6 | 7 | class Http100(HttpCode): 8 | value = 100 9 | name = "Continue" 10 | mdn = True 11 | cat = True 12 | 13 | 14 | class Http101(HttpCode): 15 | value = 101 16 | name = "Switching Protocol" 17 | mdn = True 18 | cat = True 19 | 20 | 21 | class Http102(HttpCode): 22 | value = 102 23 | name = "Processing (WebDAV)" 24 | mdn = False 25 | cat = True 26 | 27 | 28 | class Http103(HttpCode): 29 | value = 103 30 | name = "Early Hints" 31 | mdn = True 32 | cat = False 33 | -------------------------------------------------------------------------------- /docker/production/postgres/maintenance/_sourced/messages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | message_newline() { 5 | echo 6 | } 7 | 8 | message_debug() 9 | { 10 | echo -e "DEBUG: ${@}" 11 | } 12 | 13 | message_welcome() 14 | { 15 | echo -e "\e[1m${@}\e[0m" 16 | } 17 | 18 | message_warning() 19 | { 20 | echo -e "\e[33mWARNING\e[0m: ${@}" 21 | } 22 | 23 | message_error() 24 | { 25 | echo -e "\e[31mERROR\e[0m: ${@}" 26 | } 27 | 28 | message_info() 29 | { 30 | echo -e "\e[37mINFO\e[0m: ${@}" 31 | } 32 | 33 | message_suggestion() 34 | { 35 | echo -e "\e[33mSUGGESTION\e[0m: ${@}" 36 | } 37 | 38 | message_success() 39 | { 40 | echo -e "\e[32mSUCCESS\e[0m: ${@}" 41 | } 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/Message/workers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners.Message.workers 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | import discord 7 | 8 | from tuxbot.core.Tuxbot import Tuxbot 9 | 10 | from .AutoQuote import AutoQuote 11 | 12 | 13 | class Worker: 14 | """Autoworker""" 15 | 16 | def __init__(self, bot: Tuxbot) -> None: 17 | self.bot = bot 18 | 19 | self.__workers = [ 20 | AutoQuote(self.bot), 21 | ] 22 | 23 | async def runs(self, message: discord.Message) -> None: 24 | """Run all automatics workers""" 25 | 26 | for worker in self.__workers: 27 | await worker.process(message) 28 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.functions.providers 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of providers for iplocalise 6 | """ 7 | from .HostnameProvider import HostnameProvider 8 | from .IPGeolocationProvider import IPGeolocationProvider 9 | from .IPInfoProvider import IPInfoProvider 10 | from .IPWhoisProvider import IPWhoisProvider 11 | from .MapProvider import MapProvider 12 | from .OpenCageDataProvider import OpenCageDataProvider 13 | 14 | 15 | __all__ = ( 16 | "HostnameProvider", 17 | "IPGeolocationProvider", 18 | "IPInfoProvider", 19 | "IPWhoisProvider", 20 | "MapProvider", 21 | "OpenCageDataProvider", 22 | ) 23 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/pages/Embed.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base embed page 3 | """ 4 | import typing 5 | 6 | import discord 7 | 8 | 9 | if typing.TYPE_CHECKING: 10 | from ..ViewController import ViewController 11 | 12 | 13 | class Embed: 14 | """Base embed page""" 15 | 16 | def __init__(self, controller: "ViewController"): 17 | self.controller = controller 18 | self.model = self.controller.model 19 | 20 | self.ctx = self.controller.ctx 21 | 22 | # ========================================================================= 23 | # ========================================================================= 24 | 25 | def rebuild(self) -> discord.Embed: 26 | """(Re)build embed""" 27 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/pages/GlobalEmbed.py: -------------------------------------------------------------------------------- 1 | """ 2 | Global embed page 3 | """ 4 | 5 | import discord 6 | 7 | from .Embed import Embed 8 | 9 | 10 | class GlobalEmbed(Embed): 11 | """Global embed page""" 12 | 13 | def rebuild(self) -> discord.Embed: 14 | """(Re)build embed""" 15 | 16 | e = discord.Embed( 17 | description=( 18 | f"Auto pin toggled to `{self.model.activated}` " 19 | f"and threshold `{self.model.threshold}`" 20 | ), 21 | colour=self.controller.ctx.bot.utils.colors.ONLINE 22 | if self.model.activated 23 | else self.controller.ctx.bot.utils.colors.DND, 24 | ) 25 | 26 | return e 27 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/pages/Embed.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base embed page 3 | """ 4 | import typing 5 | 6 | import discord 7 | 8 | 9 | if typing.TYPE_CHECKING: 10 | from ..ViewController import ViewController 11 | 12 | 13 | class Embed: 14 | """Base embed page""" 15 | 16 | def __init__(self, controller: "ViewController"): 17 | self.controller = controller 18 | self.model = self.controller.model 19 | 20 | self.ctx = self.controller.ctx 21 | 22 | # ========================================================================= 23 | # ========================================================================= 24 | 25 | def rebuild(self) -> discord.Embed: 26 | """(Re)build embed""" 27 | -------------------------------------------------------------------------------- /.idea/webServers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/RawReactionAdd/workers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners.RawReactionAdd.workers 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | import discord 7 | 8 | from tuxbot.core.Tuxbot import Tuxbot 9 | 10 | from .AutoPin import AutoPin 11 | 12 | 13 | class Worker: 14 | """Autoworker""" 15 | 16 | def __init__(self, bot: Tuxbot) -> None: 17 | self.bot = bot 18 | 19 | self.__workers = [ 20 | AutoPin(self.bot), 21 | ] 22 | 23 | async def runs(self, payload: discord.RawReactionActionEvent) -> None: 24 | """Run all automatics workers""" 25 | 26 | for worker in self.__workers: 27 | await worker.process(payload) 28 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Launch bot 14 | 3. Type command '...' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. Debian] 25 | - Python Version [e.g. 3.7.4] 26 | 27 | **Additional context** 28 | <-- Add any other context about the problem here. --> 29 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/BGPButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class BGPButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController): 19 | self.controller = controller 20 | 21 | super().__init__( 22 | label="BGP toolkit", 23 | disabled=True, 24 | style=discord.ButtonStyle.link, 25 | row=row, 26 | url="https://bgp.he.net/AS", 27 | ) 28 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/IPInfoButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class IPInfoButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController): 19 | self.controller = controller 20 | 21 | super().__init__( 22 | label="ipinfo.io", 23 | disabled=True, 24 | style=discord.ButtonStyle.link, 25 | row=row, 26 | url="https://ipinfo.io/", 27 | ) 28 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/IPGeolocationProvider.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing 3 | 4 | import aiohttp 5 | 6 | from .abc import Provider 7 | 8 | 9 | class IPGeolocationProvider(Provider): 10 | async def fetch(self, ip: str) -> tuple[str, dict[str, typing.Any]]: 11 | try: 12 | async with aiohttp.ClientSession() as cs, cs.get( 13 | f"https://api.ipgeolocation.io/ipgeo" 14 | f"?apiKey={self.apikey}" 15 | f"&ip={ip}", 16 | timeout=aiohttp.ClientTimeout(total=2), 17 | ) as s: 18 | return "ipgeo", await s.json() 19 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 20 | pass 21 | 22 | return "ipgeo", {} 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Config for Dependabot updates. See Documentation here: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | # Update GitHub actions in workflows 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | # Check for updates to GitHub Actions every weekday 10 | schedule: 11 | interval: "daily" 12 | 13 | # Enable version updates for Python/Pip - Production 14 | - package-ecosystem: "pip" 15 | # Look for a `requirements.txt` in the `root` directory 16 | # also 'setup.cfg', 'runtime.txt' and 'requirements/*.txt' 17 | directory: "/" 18 | # Check for updates to GitHub Actions every weekday 19 | schedule: 20 | interval: "daily" 21 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include .env.local 2 | export 3 | 4 | ######################################################################################################################## 5 | # Style 6 | ######################################################################################################################## 7 | 8 | .PHONY: pre_commit 9 | pre_commit: 10 | pre-commit run --all-files 11 | 12 | 13 | ######################################################################################################################## 14 | # Rewrite 15 | ######################################################################################################################## 16 | 17 | .PHONY: build 18 | build: 19 | poetry build 20 | 21 | .PHONY: dev 22 | dev: pre_commit build run 23 | 24 | .PHONY: run 25 | run: 26 | poetry run start 27 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/buttons/JPGButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class JPGButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController): 19 | self.controller = controller 20 | asset: discord.Asset = self.controller.data.display_avatar.with_format( 21 | "jpg" 22 | ) 23 | 24 | super().__init__( 25 | label="JPG", 26 | style=discord.ButtonStyle.link, 27 | row=row, 28 | url=asset.url, 29 | ) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/buttons/PNGButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class PNGButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController): 19 | self.controller = controller 20 | asset: discord.Asset = self.controller.data.display_avatar.with_format( 21 | "png" 22 | ) 23 | 24 | super().__init__( 25 | label="PNG", 26 | style=discord.ButtonStyle.link, 27 | row=row, 28 | url=asset.url, 29 | ) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/buttons/WEBPButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class WEBPButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController): 19 | self.controller = controller 20 | asset: discord.Asset = self.controller.data.display_avatar.with_format( 21 | "webp" 22 | ) 23 | 24 | super().__init__( 25 | label="WEBP", 26 | style=discord.ButtonStyle.link, 27 | row=row, 28 | url=asset.url, 29 | ) 30 | -------------------------------------------------------------------------------- /tuxbot/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tuxbot 3 | ~~~~~~~ 4 | 5 | bot made initially for https://gnous.eu, then proposed to the entire 6 | free software community 7 | """ 8 | import os 9 | from collections import namedtuple 10 | 11 | 12 | build = os.popen("/usr/bin/git rev-parse --short HEAD").read().strip() 13 | info = os.popen('/usr/bin/git log -n 3 -s --format="%s"').read().strip() 14 | 15 | VersionInfo = namedtuple( 16 | "VersionInfo", "major minor micro releaselevel build, info" 17 | ) 18 | version_info = VersionInfo( 19 | major=4, minor=2, micro=0, releaselevel="stable", build=build, info=info 20 | ) 21 | 22 | __version__ = "v{}.{}.{}-{}+{}".format( 23 | version_info.major, 24 | version_info.minor, 25 | version_info.micro, 26 | version_info.releaselevel, 27 | version_info.build, 28 | ).replace("\n", "") 29 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/converters/IPConverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.converters.IPConverter 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Converter to ip or domain. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | 12 | 13 | ConvertType = str 14 | 15 | 16 | class IPConverter(commands.Converter[ConvertType]): 17 | """Clean user input by removing proto.""" 18 | 19 | async def convert( # type: ignore[override] 20 | self, ctx: commands.Context[TuxbotABC], argument: str 21 | ) -> ConvertType: 22 | argument = argument.replace("http://", "").replace("https://", "") 23 | argument = argument.split("/")[0] 24 | 25 | argument = argument.lstrip("`").rstrip("`") 26 | 27 | return argument.lower() 28 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/MapProvider.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from .abc import Provider 4 | 5 | 6 | class MapProvider(Provider): 7 | # pylint: disable=arguments-renamed 8 | async def fetch(self, latlon: str) -> tuple[str, dict[str, typing.Any]]: 9 | url = ( 10 | "https://maps.geoapify.com/v1/staticmap" 11 | "?style=osm-carto" 12 | "&width=333" 13 | "&height=250" 14 | "¢er=lonlat:{lonlat}" 15 | "&zoom=10" 16 | "&marker=lonlat:{lonlat};color:%23ff0000;size:small" 17 | "&apiKey={apikey}" 18 | ) 19 | 20 | lonlat = ",".join(latlon.split(",")[::-1]) 21 | 22 | output = url.format(lonlat=lonlat, apikey=self.apikey) 23 | 24 | return "map", {"bytes": None, "url": output} 25 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/HostnameProvider.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import socket 3 | 4 | from .abc import Provider 5 | 6 | 7 | class HostnameProvider(Provider): 8 | async def fetch(self, ip: str) -> tuple[str, str]: 9 | def _get_hostname(_ip: str) -> str: 10 | try: 11 | return socket.gethostbyaddr(_ip)[0] 12 | except (socket.gaierror, socket.herror, UnicodeError): 13 | return "N/A" 14 | 15 | try: 16 | return "hostname", await asyncio.wait_for( 17 | asyncio.get_running_loop().run_in_executor( 18 | None, _get_hostname, str(ip) 19 | ), 20 | timeout=2, 21 | ) 22 | except asyncio.exceptions.TimeoutError: 23 | return "hostname", "N/A" 24 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/OpenCageDataProvider.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing 3 | 4 | import aiohttp 5 | 6 | from .abc import Provider 7 | 8 | 9 | class OpenCageDataProvider(Provider): 10 | # pylint: disable=arguments-renamed 11 | async def fetch(self, latlon: str) -> tuple[str, dict[str, typing.Any]]: 12 | try: 13 | async with aiohttp.ClientSession() as cs, cs.get( 14 | f"https://api.opencagedata.com/geocode/v1/json" 15 | f"?q={latlon}" 16 | f"&key={self.apikey}", 17 | timeout=aiohttp.ClientTimeout(total=2), 18 | ) as s: 19 | return "opencage", await s.json() 20 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 21 | pass 22 | 23 | return "opencage", {} 24 | -------------------------------------------------------------------------------- /tuxbot/core/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Set of utils functions and classes 3 | """ 4 | 5 | import typing 6 | 7 | from .Colors import Colors 8 | from .Emotes import Emotes 9 | from .Generators import gen_key as _gen_key 10 | from .Generators import shorten as _shorten 11 | 12 | 13 | class Utils: 14 | """Set of utils functions and classes""" 15 | 16 | colors: type[Colors] = Colors 17 | emotes: type[Emotes] = Emotes 18 | 19 | # ========================================================================= 20 | 21 | @staticmethod 22 | async def shorten(text: str, length: int) -> dict[str, str]: 23 | return await _shorten(text=text, length=length) 24 | 25 | @staticmethod 26 | def gen_key(*args: typing.Any, **kwargs: typing.Any) -> str: 27 | return _gen_key(*args, **kwargs) 28 | 29 | 30 | utils = Utils() 31 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/buttons/DeleteButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class DeleteButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.danger, 23 | row=row, 24 | emoji="\N{WASTEBASKET}", 25 | label="delete", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.delete() 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/buttons/DeleteButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class DeleteButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.danger, 23 | row=row, 24 | emoji="\N{WASTEBASKET}", 25 | label="delete", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.delete() 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/DeleteButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class DeleteButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.danger, 23 | row=row, 24 | emoji="\N{WASTEBASKET}", 25 | label="delete", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.delete() 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/buttons/ToggleButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class ToggleButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.secondary, 23 | row=row, 24 | disabled=True, 25 | label="toggle", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.change_state(interaction) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/ui/buttons/ToggleButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class ToggleButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.secondary, 23 | row=row, 24 | disabled=True, 25 | label="toggle", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.change_state(interaction) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/buttons/GIFButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class GIFButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController): 19 | self.controller = controller 20 | asset: discord.Asset = self.controller.data.display_avatar 21 | 22 | is_animated = asset.is_animated() 23 | 24 | super().__init__( 25 | label="GIF", 26 | style=discord.ButtonStyle.link, 27 | row=row, 28 | disabled=not is_animated, 29 | url=asset.url, 30 | ) 31 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/GeoButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class GeoButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.secondary, 23 | emoji="\N{WORLD MAP}", 24 | row=row, 25 | label="geo", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.change_page(1, interaction) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/buttons/ThresholdButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class ThresholdButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.secondary, 23 | row=row, 24 | disabled=False, 25 | label="threshold", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.set_threshold(interaction) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/converters/InetConverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.converters.InetConverter 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Converter to inet. 6 | """ 7 | import typing 8 | 9 | from discord.ext import commands 10 | 11 | from tuxbot.abc.TuxbotABC import TuxbotABC 12 | 13 | 14 | ConvertType = typing.Optional[int] 15 | 16 | 17 | class InetConverter(commands.Converter[ConvertType]): 18 | """Clean and return inet.""" 19 | 20 | async def convert( # type: ignore[override] 21 | self, ctx: commands.Context[TuxbotABC], argument: str | None 22 | ) -> ConvertType: 23 | res = None 24 | 25 | if not argument: 26 | return res 27 | 28 | if "6" in argument: 29 | res = 6 30 | 31 | if "4" in argument: 32 | res = 4 33 | 34 | return res 35 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/commands/Restart/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin.commands.Restart.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Command to restart Tuxbot 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | 14 | class RestartCommand(commands.Cog): 15 | """Restart tuxbot""" 16 | 17 | def __init__(self, bot: Tuxbot) -> None: 18 | self.bot = bot 19 | 20 | # ========================================================================= 21 | # ========================================================================= 22 | 23 | @commands.command("restart", aliases=["reboot"]) 24 | async def _restart(self, ctx: commands.Context[TuxbotABC]) -> None: 25 | await ctx.send("*restarting...*") 26 | await self.bot.shutdown() 27 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/GlobalButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class GlobalButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.secondary, 23 | emoji="\N{GLOBE WITH MERIDIANS}", 24 | row=row, 25 | label="global", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | await self.controller.change_page(0, interaction) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/Message/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners.Message.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever message is sent 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.Tuxbot import Tuxbot 11 | 12 | from .workers import Worker 13 | 14 | 15 | class Message(commands.Cog): 16 | """Listener whenever message is sent""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | self.worker = Worker(self.bot) 21 | 22 | # ========================================================================= 23 | # ========================================================================= 24 | 25 | @commands.Cog.listener(name="on_message") 26 | async def _on_message(self, message: discord.Message) -> None: 27 | await self.worker.runs(message) 28 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/__init__.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from .ASNButton import ASNButton 4 | from .BGPButton import BGPButton 5 | from .DeleteButton import DeleteButton 6 | from .GeoButton import GeoButton 7 | from .GlobalButton import GlobalButton 8 | from .IPInfoButton import IPInfoButton 9 | from .RawButton import RawButton 10 | from .WhoisButton import WhoisButton 11 | 12 | 13 | __all__ = [ 14 | "ButtonType", 15 | "GlobalButton", 16 | "GeoButton", 17 | "RawButton", 18 | "WhoisButton", 19 | "ASNButton", 20 | "IPInfoButton", 21 | "BGPButton", 22 | "DeleteButton", 23 | ] 24 | 25 | 26 | ButtonType = typing.Union[ 27 | type[GlobalButton], 28 | type[GeoButton], 29 | type[RawButton], 30 | type[WhoisButton], 31 | type[ASNButton], 32 | type[IPInfoButton], 33 | type[BGPButton], 34 | type[DeleteButton], 35 | ] 36 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/converters/HttpCodeConverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev.commands.HTTP.converters.HttpCodeConverter 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Converter to ensure user given data is HTTP code. 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | 12 | from ..exceptions import UnknownHttpCode 13 | from ..http import HttpCode, http_if_exists 14 | 15 | 16 | ConvertType = HttpCode 17 | 18 | 19 | class HttpCodeConverter(commands.Converter[ConvertType]): 20 | """Ensure passed data is HTTP code.""" 21 | 22 | async def convert( # type: ignore[override] 23 | self, ctx: commands.Context[TuxbotABC], argument: str 24 | ) -> ConvertType: 25 | if argument.isdigit() and (http := http_if_exists(int(argument))): 26 | return http() 27 | 28 | raise UnknownHttpCode("Unknown HTTP code") 29 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/Tag/ui/paginator.py: -------------------------------------------------------------------------------- 1 | import discord 2 | 3 | from tuxbot.core.utils.Paginator.SimplePages import SimplePages 4 | 5 | from ..models.Tags import TagsModel 6 | 7 | 8 | class TagPage: 9 | """One page of tags""" 10 | 11 | def __init__(self, i: int, entry: TagsModel) -> None: 12 | self.id: int = i 13 | self.name: str = entry.name 14 | self.uses: int = entry.uses 15 | 16 | def __str__(self) -> str: 17 | return f"{self.name} ({self.uses})" 18 | 19 | 20 | class TagPages(SimplePages): 21 | """All tags pages""" 22 | 23 | def __init__( 24 | self, 25 | entries: list[TagsModel], 26 | ctx: discord.Interaction, 27 | per_page: int = 15, 28 | ) -> None: 29 | super().__init__( 30 | [TagPage(i, entry) for i, entry in enumerate(entries)], 31 | ctx=ctx, 32 | per_page=per_page, 33 | ) 34 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [BASIC] 2 | good-names= 3 | e, # (exception) as e 4 | f, # (file) as f 5 | k, # for k, v in 6 | v, # for k, v in 7 | dt, # datetime 8 | 9 | [MASTER] 10 | disable= 11 | C0103, # invalid-name 12 | C0114, # missing-module-docstring 13 | C0115, # missing-class-docstring 14 | C0116, # missing-function-docstring 15 | C0209, # consider-using-f-string 16 | C0415, # import-outside-toplevel 17 | W0511, # fixme 18 | W0703, # broad-except 19 | W0707, # raise-missing-from 20 | R0801, # duplicate-code 21 | R0901, # too-many-ancestors 22 | R0902, # too-many-instance-attributes 23 | R0903, # too-few-public-methods 24 | R0913, # too-many-arguments 25 | E1136, # unsubscriptable-object (false positive with python 3.9) 26 | 27 | [FORMAT] 28 | max-line-length=79 29 | 30 | [DESIGN] 31 | max-parents=13 32 | 33 | [TYPECHECK] 34 | generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete 35 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/models/AutoQuote.py: -------------------------------------------------------------------------------- 1 | """ 2 | AutoQuote model 3 | """ 4 | from tortoise import fields 5 | from tortoise.models import Model 6 | 7 | 8 | class AutoQuoteModel(Model): 9 | """ 10 | AutoQuote Model used for auto_quote command 11 | """ 12 | 13 | id = fields.BigIntField(pk=True) 14 | guild_id = fields.BigIntField(description="Guild ID") 15 | 16 | activated = fields.BooleanField(default=False) 17 | 18 | # ========================================================================= 19 | 20 | class Meta: 21 | """Meta values""" 22 | 23 | table = "auto_quote" 24 | 25 | # ========================================================================= 26 | 27 | def __str__(self) -> str: 28 | return ( 29 | f"" 32 | ) 33 | 34 | __repr__ = __str__ 35 | -------------------------------------------------------------------------------- /scripts/merge_dotenvs_to_dotenv.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | ROOT_DIR_PATH = Path(__file__).parent.parent.resolve() 5 | ENVS_NAME = (".local", ".production") 6 | ENVS_FILES = (".bot", ".cogs", ".postgres") 7 | 8 | 9 | def merge(input_file_path: Path, merged_file_paths: Path) -> None: 10 | with open(merged_file_paths, "w") as output_file: 11 | for file_name in ENVS_FILES: 12 | 13 | with open(str(input_file_path / file_name)) as merged_file: 14 | for line in merged_file.readlines(): 15 | if line.startswith("#") or not line.strip(): 16 | continue 17 | 18 | output_file.write(line) 19 | 20 | 21 | def main() -> None: 22 | for environment in ENVS_NAME: 23 | merge( 24 | ROOT_DIR_PATH / ".envs" / environment, 25 | ROOT_DIR_PATH / f".env{environment}", 26 | ) 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /tuxbot/abc/ModuleABC.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tuxbot abstract class module: ModuleABC 3 | 4 | Contains all Module properties 5 | """ 6 | import typing 7 | 8 | from discord.ext import commands 9 | from tortoise.models import ModelMeta 10 | 11 | 12 | if typing.TYPE_CHECKING: 13 | from tuxbot.core.Tuxbot import Tuxbot 14 | 15 | 16 | class ModuleABC(commands.Cog): 17 | """Module Abstract Class""" 18 | 19 | bot: "Tuxbot" 20 | models: ModelMeta 21 | 22 | def crash_report(self) -> str: 23 | """Generate crash report""" 24 | report = f"{'='*10}{self.__class__.__name__}{'='*10}" 25 | 26 | report += "\nhas models:" 27 | # pylint: disable=no-member 28 | if hasattr(self, "models") and (models := self.models): 29 | report += str(models) 30 | else: 31 | report += "No" 32 | 33 | report += "\nhas commands:" 34 | report += str([c.name for c in self.get_commands()]) 35 | 36 | return report 37 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/RawReactionAdd/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners.RawReactionAdd.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever reaction is added 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.Tuxbot import Tuxbot 11 | 12 | from .workers import Worker 13 | 14 | 15 | class RawReactionAdd(commands.Cog): 16 | """Listener whenever reaction is added""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | self.worker = Worker(self.bot) 21 | 22 | # ========================================================================= 23 | # ========================================================================= 24 | 25 | @commands.Cog.listener(name="on_raw_reaction_add") 26 | async def _on_raw_reaction_add( 27 | self, payload: discord.RawReactionActionEvent 28 | ) -> None: 29 | await self.worker.runs(payload) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Coin/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Coin.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Flip a coin 6 | """ 7 | import random 8 | 9 | import discord 10 | from discord.ext import commands 11 | 12 | from tuxbot.abc.TuxbotABC import TuxbotABC 13 | from tuxbot.core.Tuxbot import Tuxbot 14 | 15 | 16 | class CoinCommand(commands.Cog): 17 | """Flip a coin""" 18 | 19 | def __init__(self, bot: Tuxbot) -> None: 20 | self.bot = bot 21 | 22 | # ========================================================================= 23 | # ========================================================================= 24 | 25 | @commands.command(name="coin") 26 | async def _coin(self, ctx: commands.Context[TuxbotABC]) -> None: 27 | e = discord.Embed( 28 | title=random.choice(["Tail", "Head"]), 29 | color=self.bot.utils.colors.EMBED_BORDER, 30 | ) 31 | 32 | await ctx.send(embed=e) 33 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/Message/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.Message.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever message is sent 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.Tuxbot import Tuxbot 11 | 12 | 13 | class Message(commands.Cog): 14 | """Listener whenever message is sent""" 15 | 16 | def __init__(self, bot: Tuxbot) -> None: 17 | self.bot = bot 18 | 19 | # ========================================================================= 20 | # ========================================================================= 21 | 22 | @commands.Cog.listener(name="on_message") 23 | async def _on_message(self, message: discord.Message) -> None: 24 | if message.guild: 25 | tags = [f"guild:{message.guild.id}"] 26 | else: 27 | tags = [f"private:{message.channel.id}"] 28 | 29 | self.bot.statsd.increment("messages", value=1, tags=tags) 30 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/WhoisButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class WhoisButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController) -> None: 19 | self.controller = controller 20 | prefix = self.controller.ctx.prefix or ( 21 | f"<@{self.controller.ctx.bot.user.id}>" 22 | if self.controller.ctx.bot.user 23 | else "" 24 | ) 25 | 26 | super().__init__( 27 | style=discord.ButtonStyle.primary, 28 | label=prefix + "whois", 29 | row=row, 30 | disabled=True, 31 | ) 32 | 33 | async def callback(self, interaction: discord.Interaction) -> None: 34 | ... 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/pages/Embed.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base embed page 3 | """ 4 | import typing 5 | 6 | import discord 7 | 8 | 9 | if typing.TYPE_CHECKING: 10 | from ..ViewController import ViewController 11 | 12 | 13 | class Embed: 14 | """Base embed page""" 15 | 16 | def __init__(self, controller: "ViewController") -> None: 17 | self.controller = controller 18 | self.data = self.controller.data 19 | 20 | self.ctx = self.controller.ctx 21 | 22 | # ========================================================================= 23 | # ========================================================================= 24 | 25 | def update(self, controller: "ViewController") -> None: 26 | """Update loaded data""" 27 | self.controller = controller 28 | self.data = self.controller.data 29 | 30 | # ========================================================================= 31 | 32 | def rebuild(self) -> discord.Embed: 33 | """(Re)build embed""" 34 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/buttons/DeleteButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | 7 | 8 | if typing.TYPE_CHECKING: 9 | from ..ViewController import ViewController 10 | 11 | 12 | class DeleteButton(discord.ui.Button["ViewController"]): 13 | disabled: bool 14 | label: str 15 | emoji: discord.PartialEmoji | None 16 | row: int 17 | 18 | def __init__(self, row: int, controller: ViewController): 19 | self.controller = controller 20 | 21 | super().__init__( 22 | style=discord.ButtonStyle.danger, 23 | row=row, 24 | emoji="\N{WASTEBASKET}", 25 | label="delete", 26 | ) 27 | 28 | async def callback(self, interaction: discord.Interaction) -> None: 29 | if ( 30 | interaction.user == self.controller.ctx.author 31 | ) and self.controller.sent_message: 32 | self.controller.stop() 33 | await self.controller.sent_message.delete() 34 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/models/AutoPin.py: -------------------------------------------------------------------------------- 1 | """ 2 | AutoPin model 3 | """ 4 | from tortoise import fields 5 | from tortoise.models import Model 6 | 7 | 8 | class AutoPinModel(Model): 9 | """ 10 | AutoPin Model used for auto_pin command 11 | """ 12 | 13 | id = fields.BigIntField(pk=True) 14 | guild_id = fields.BigIntField(description="Guild ID") 15 | 16 | activated = fields.BooleanField(default=False) 17 | threshold = fields.IntField(default=999_999_999) 18 | 19 | # ========================================================================= 20 | 21 | class Meta: 22 | """Meta values""" 23 | 24 | table = "auto_pin" 25 | 26 | # ========================================================================= 27 | 28 | def __str__(self) -> str: 29 | return ( 30 | f"" 34 | ) 35 | 36 | __repr__ = __str__ 37 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/SocketRawReceive/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.SocketRawReceive.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever websocket message is sent/received 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.config import config 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | 14 | class SocketRawReceive(commands.Cog): 15 | """Listener whenever websocket message is sent/received""" 16 | 17 | def __init__(self, bot: Tuxbot) -> None: 18 | self.bot = bot 19 | 20 | # ========================================================================= 21 | # ========================================================================= 22 | 23 | @commands.Cog.listener(name="on_socket_event_type") 24 | async def _on_socket_event_type(self, msg: str) -> None: 25 | if msg not in config.CLIENT["disabled_events"]: 26 | self.bot.statsd.increment( 27 | "socket_event_type", value=1, tags=[f"event_type:{msg}"] 28 | ) 29 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/converters/MemberOrUserConverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Gives discord member or user 6 | """ 7 | import typing 8 | 9 | import discord 10 | from discord.ext import commands 11 | 12 | from tuxbot.abc.TuxbotABC import TuxbotABC 13 | 14 | 15 | ConvertType = typing.Union[discord.Member, discord.User, None] 16 | 17 | 18 | class MemberOrUserConverter(commands.Converter[ConvertType]): 19 | """Gives either discord member or user format.""" 20 | 21 | async def convert( # type: ignore[override] 22 | self, ctx: commands.Context[TuxbotABC], argument: str 23 | ) -> ConvertType: 24 | if argument: 25 | try: 26 | return await commands.MemberConverter().convert(ctx, argument) 27 | except commands.MemberNotFound: 28 | pass 29 | 30 | try: 31 | return await commands.UserConverter().convert(ctx, argument) 32 | except commands.UserNotFound: 33 | pass 34 | 35 | return None 36 | -------------------------------------------------------------------------------- /scripts/test_database.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | import psycopg2 6 | 7 | 8 | suggest_unrecoverable_after = 10 9 | start = time.time() 10 | 11 | 12 | def test() -> None: 13 | while True: 14 | try: 15 | psycopg2.connect( 16 | dbname=os.getenv("POSTGRES_DB"), 17 | user=os.getenv("POSTGRES_USER"), 18 | password=os.getenv("POSTGRES_PASSWORD"), 19 | host=os.getenv("POSTGRES_HOST"), 20 | port=os.getenv("POSTGRES_PORT"), 21 | ) 22 | break 23 | 24 | except psycopg2.OperationalError as error: 25 | sys.stderr.write("Waiting for PostgreSQL to become available...\n") 26 | if time.time() - start > suggest_unrecoverable_after: 27 | sys.stderr.write( 28 | " This is taking longer than expected. " 29 | "The following exception may be indicative of an " 30 | "unrecoverable error: '{}'\n".format(error) 31 | ) 32 | time.sleep(1) 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | *$py.class 4 | 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/**/usage.statistics.xml 12 | .idea/**/dictionaries 13 | .idea/**/shelf 14 | 15 | # Generated files 16 | .idea/**/contentModel.xml 17 | 18 | # Sensitive or high-churn files 19 | .idea/**/dataSources/ 20 | .idea/**/dataSources.ids 21 | .idea/**/dataSources.local.xml 22 | .idea/**/sqlDataSources.xml 23 | .idea/**/dynamic.xml 24 | .idea/**/uiDesigner.xml 25 | .idea/**/dbnavigator.xml 26 | 27 | 28 | .idea/sonarlint/ 29 | 30 | *.log 31 | 32 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 33 | __pypackages__/ 34 | 35 | .venv 36 | dist 37 | build 38 | *.egg 39 | *.egg-info 40 | 41 | 42 | .ipython/ 43 | .env 44 | .envs/* 45 | 46 | 47 | dump.rdb 48 | profiling.prof 49 | 50 | cogs.back/ 51 | data/ 52 | logs/ 53 | !logs/.gitignore 54 | .env* 55 | -------------------------------------------------------------------------------- /local.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | volumes: 4 | tuxbot_local_postgres_data: {} 5 | tuxbot_local_postgres_data_backups: {} 6 | 7 | services: 8 | bot: 9 | build: 10 | context: . 11 | dockerfile: docker/local/bot/Dockerfile 12 | image: tuxbot_local_bot 13 | container_name: tuxbot_local_bot 14 | platform: linux/x86_64 15 | depends_on: 16 | - postgres 17 | - redis 18 | volumes: 19 | - .:/app:z 20 | env_file: 21 | - ./.envs/.local/.bot 22 | - ./.envs/.local/.cogs 23 | - ./.envs/.local/.postgres 24 | user: tuxbot 25 | command: /start 26 | 27 | postgres: 28 | build: 29 | context: . 30 | dockerfile: docker/production/postgres/Dockerfile 31 | image: tuxbot_production_postgres 32 | container_name: tuxbot_local_postgres 33 | volumes: 34 | - tuxbot_local_postgres_data:/var/lib/postgresql/data:Z 35 | - tuxbot_local_postgres_data_backups:/backups:z 36 | env_file: 37 | - ./.envs/.local/.postgres 38 | 39 | redis: 40 | image: redis:7 41 | container_name: tuxbot_local_redis 42 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/Ready/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.Ready.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener when Tuxbot is ready 6 | """ 7 | from discord.ext import commands 8 | 9 | from tuxbot.core.Tuxbot import Tuxbot 10 | 11 | 12 | class Ready(commands.Cog): 13 | """Listener when Tuxbot is ready""" 14 | 15 | def __init__(self, bot: Tuxbot) -> None: 16 | self.bot = bot 17 | 18 | # ========================================================================= 19 | # ========================================================================= 20 | 21 | @commands.Cog.listener(name="on_ready") 22 | async def _on_ready(self) -> None: 23 | self.bot.statsd.gauge( 24 | "guilds", 25 | value=len(self.bot.guilds), 26 | ) 27 | self.bot.statsd.gauge( 28 | "members", 29 | value=sum(guild.member_count or 0 for guild in self.bot.guilds), 30 | ) 31 | self.bot.statsd.gauge( 32 | "unique_members", 33 | value=len(self.bot.users), 34 | ) 35 | -------------------------------------------------------------------------------- /tuxbot/core/utils/Debug/TimeSpent.py: -------------------------------------------------------------------------------- 1 | """ 2 | Log time spent between each update 3 | """ 4 | import time 5 | 6 | 7 | class TimeSpent: 8 | """Log time spent between each update""" 9 | 10 | def __init__(self) -> None: 11 | self.order: list[str] = [] 12 | self.times: dict[str, list[float]] = {} 13 | 14 | def start(self, name: str) -> None: 15 | """Start timer""" 16 | self.order.append(name) 17 | self.times[name] = [time.perf_counter()] 18 | 19 | def stop(self, name: str) -> None: 20 | """Stop timer""" 21 | self.times[name].append(time.perf_counter()) 22 | 23 | def display(self) -> str: 24 | """Return breakpoints""" 25 | output = "" 26 | 27 | for point in self.order: 28 | delta = "..." 29 | 30 | if len(self.times[point]) == 2: 31 | delta = str( 32 | (self.times[point][1] - self.times[point][0]) * 1000 33 | ) 34 | delta = f"{delta:.2f}ms" 35 | 36 | output += f"\n{point}: {delta}" 37 | 38 | return output 39 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/RawButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import io 4 | import json 5 | import typing 6 | 7 | import discord 8 | 9 | 10 | if typing.TYPE_CHECKING: 11 | from ..ViewController import ViewController 12 | 13 | 14 | class RawButton(discord.ui.Button["ViewController"]): 15 | disabled: bool 16 | label: str 17 | emoji: discord.PartialEmoji | None 18 | row: int 19 | 20 | def __init__(self, row: int, controller: ViewController) -> None: 21 | self.controller = controller 22 | 23 | super().__init__( 24 | style=discord.ButtonStyle.secondary, 25 | emoji="\N{BOOKMARK TABS}", 26 | row=row, 27 | label="raw", 28 | ) 29 | 30 | async def callback(self, interaction: discord.Interaction) -> None: 31 | await interaction.response.send_message( 32 | file=discord.File( 33 | filename="output.json", 34 | fp=io.BytesIO(json.dumps(self.controller.data).encode()), 35 | ), 36 | ephemeral=True, 37 | ) 38 | -------------------------------------------------------------------------------- /.idea/tuxbot_bot.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /tuxbot/core/models/Tuxbot.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tuxbot model 3 | """ 4 | from tortoise import fields 5 | from tortoise.models import Model 6 | 7 | from tuxbot.core.models.fields.BigIntArrayField import BigIntArrayField 8 | 9 | 10 | class TuxbotModel(Model): 11 | """ 12 | Tuxbot Model used to store tuxbot configs 13 | """ 14 | 15 | id = fields.BigIntField(pk=True, description="Client ID") 16 | prefix = fields.TextField(default=".", description="Tuxbot prefix") 17 | 18 | ignored_users = BigIntArrayField( 19 | description="Tuxbot ignored users", default=None, null=True 20 | ) 21 | ignored_channels = BigIntArrayField( 22 | description="Tuxbot ignored channels", default=None, null=True 23 | ) 24 | ignored_guilds = BigIntArrayField( 25 | description="Tuxbot ignored guilds", default=None, null=True 26 | ) 27 | 28 | # ========================================================================= 29 | 30 | class Meta: 31 | """Meta values""" 32 | 33 | table = "tuxbot" 34 | 35 | # ========================================================================= 36 | -------------------------------------------------------------------------------- /production.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | volumes: 4 | tuxbot_production_postgres_data: {} 5 | tuxbot_production_postgres_data_backups: {} 6 | 7 | services: 8 | bot: 9 | build: 10 | context: . 11 | dockerfile: docker/production/bot/Dockerfile 12 | image: tuxbot_production_bot 13 | container_name: tuxbot_production_bot 14 | platform: linux/x86_64 15 | depends_on: 16 | - postgres 17 | - redis 18 | env_file: 19 | - ./.envs/.production/.bot 20 | - ./.envs/.production/.cogs 21 | - ./.envs/.production/.postgres 22 | user: tuxbot 23 | command: /start 24 | 25 | postgres: 26 | build: 27 | context: . 28 | dockerfile: docker/production/postgres/Dockerfile 29 | image: tuxbot_production_postgres 30 | container_name: tuxbot_production_postgres 31 | volumes: 32 | - tuxbot_production_postgres_data:/var/lib/postgresql/data:Z 33 | - tuxbot_production_postgres_data_backups:/backups:z 34 | env_file: 35 | - ./.envs/.production/.postgres 36 | 37 | redis: 38 | image: redis:7 39 | container_name: tuxbot_production_redis 40 | -------------------------------------------------------------------------------- /tuxbot/core/models/fields/BigIntArrayField.py: -------------------------------------------------------------------------------- 1 | """ 2 | Big Int Array field specifically for PostgreSQL. 3 | 4 | This field can store list of bigint values. 5 | """ 6 | import json 7 | import typing 8 | 9 | from tortoise.fields import Field 10 | from tortoise.models import Model 11 | 12 | 13 | class BigIntArrayField(Field[list[int]]): 14 | """ 15 | Big Int Array field specifically for PostgreSQL. 16 | 17 | This field can store list of bigint values. 18 | """ 19 | 20 | SQL_TYPE = "bigint[]" 21 | 22 | def __init__(self, **kwargs: typing.Any) -> None: 23 | super().__init__(**kwargs) 24 | 25 | def to_db_value( 26 | self, value: list[int], instance: type[Model] | Model 27 | ) -> list[int] | None: 28 | """Convert value before send to db""" 29 | 30 | return value 31 | 32 | def to_python_value(self, value: typing.Any) -> list[int] | None: 33 | """Convert db value to python""" 34 | 35 | if isinstance(value, str): 36 | array = json.loads(value.replace("'", '"')) 37 | return [int(x) for x in array] 38 | 39 | return None 40 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/IPInfoProvider.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing 3 | 4 | import ipinfo 5 | from ipinfo.exceptions import RequestQuotaExceededError 6 | 7 | from .abc import Provider 8 | 9 | 10 | class IPInfoProvider(Provider): 11 | async def fetch(self, ip: str) -> tuple[str, dict[str, typing.Any]]: 12 | def _get_ipinfo_result(_ip: str) -> dict[str, typing.Any]: 13 | """ 14 | Q. Why no getHandlerAsync ? 15 | A. Use of this return "Unclosed client session" and 16 | "Unclosed connector" 17 | """ 18 | try: 19 | handler = ipinfo.getHandler(self.apikey) 20 | return (handler.getDetails(ip)).all # type: ignore 21 | except RequestQuotaExceededError: 22 | return {} 23 | 24 | try: 25 | return "ipinfo", await asyncio.wait_for( 26 | asyncio.get_running_loop().run_in_executor( 27 | None, _get_ipinfo_result, str(ip) 28 | ), 29 | timeout=2, 30 | ) 31 | except asyncio.exceptions.TimeoutError: 32 | return "ipinfo", {} 33 | -------------------------------------------------------------------------------- /tuxbot/cogs/Help/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Help 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | Tuxbot help command. 6 | """ 7 | 8 | from collections import namedtuple 9 | 10 | from tuxbot.abc.ModuleABC import ModuleABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | from .commands.Help.command import HelpCommand 14 | 15 | 16 | VersionInfo = namedtuple("VersionInfo", "major minor micro release_level") 17 | version_info = VersionInfo(major=2, minor=1, micro=0, release_level="stable") 18 | 19 | __version__ = "v{}.{}.{}-{}".format( 20 | version_info.major, 21 | version_info.minor, 22 | version_info.micro, 23 | version_info.release_level, 24 | ).replace("\n", "") 25 | 26 | 27 | class Help(ModuleABC): 28 | """Tuxbot help command.""" 29 | 30 | def __init__(self, bot: Tuxbot) -> None: 31 | self.old_help_command = bot.help_command 32 | bot.help_command = HelpCommand() 33 | bot.help_command.cog = self 34 | 35 | # ========================================================================= 36 | 37 | async def cog_unload(self) -> None: 38 | """Rebind native dpy help command before unload""" 39 | self.bot.help_command = self.old_help_command 40 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Flake8 2 | 3 | on: [push] 4 | 5 | jobs: 6 | linter: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout Code Repository 10 | uses: actions/checkout@v3 11 | 12 | - name: Set up Python 13 | uses: actions/setup-python@v4 14 | with: 15 | python-version: "3.10" 16 | 17 | - name: Install Poetry 18 | uses: snok/install-poetry@v1 19 | with: 20 | virtualenvs-create: true 21 | virtualenvs-in-project: true 22 | installer-parallel: true 23 | 24 | - name: Load cached venv 25 | id: cached-poetry-dependencies 26 | uses: actions/cache@v3 27 | with: 28 | path: .venv 29 | key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} 30 | 31 | - name: Install dependencies 32 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' 33 | run: poetry install --no-interaction --no-root 34 | 35 | - name: Install library 36 | run: poetry install --no-interaction 37 | 38 | - name: Run pre-commit 39 | uses: pre-commit/action@v3.0.0 40 | -------------------------------------------------------------------------------- /docker/production/postgres/maintenance/backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ### Create a database backup. 5 | ### 6 | ### Usage: 7 | ### $ docker-docker -f .yml (exec |run --rm) postgres backup 8 | 9 | 10 | set -o errexit 11 | set -o pipefail 12 | set -o nounset 13 | 14 | 15 | working_dir="$(dirname ${0})" 16 | source "${working_dir}/_sourced/constants.sh" 17 | source "${working_dir}/_sourced/messages.sh" 18 | 19 | 20 | message_welcome "Backing up the '${POSTGRES_DB}' database..." 21 | 22 | 23 | if [[ "${POSTGRES_USER}" == "postgres" ]]; then 24 | message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again." 25 | exit 1 26 | fi 27 | 28 | export PGHOST="${POSTGRES_HOST}" 29 | export PGPORT="${POSTGRES_PORT}" 30 | export PGUSER="${POSTGRES_USER}" 31 | export PGPASSWORD="${POSTGRES_PASSWORD}" 32 | export PGDATABASE="${POSTGRES_DB}" 33 | 34 | backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz" 35 | pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}" 36 | 37 | 38 | message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'." 39 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/commands/Stats/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.commands.Stats.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Send Datadog dashboard link 6 | """ 7 | 8 | import discord 9 | from discord.ext import commands 10 | 11 | from tuxbot.abc.TuxbotABC import TuxbotABC 12 | from tuxbot.core.Tuxbot import Tuxbot 13 | 14 | 15 | class StatsCommand(commands.Cog): 16 | """Send Datadog dashboard link""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | 21 | self.datadog_url = ( 22 | "https://p.datadoghq.com/sb/" 23 | "9xjljtz2ur1xzb71-f2770ff41307443a259597fa4a881b0b" 24 | ) 25 | 26 | # ========================================================================= 27 | # ========================================================================= 28 | 29 | @commands.command(name="stats", aliases=["statistics"]) 30 | async def _stats(self, ctx: commands.Context[TuxbotABC]) -> None: 31 | e = discord.Embed(title="Tuxbot statistics") 32 | 33 | e.add_field( 34 | name="__Datadog:__", value=f"[dashboard]({self.datadog_url})" 35 | ) 36 | 37 | await ctx.send(embed=e) 38 | -------------------------------------------------------------------------------- /settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | 5 | # Datadog 6 | # ----------------------------------------------------------------------------- 7 | STATSD_HOST = env.str("STATSD_HOST", "127.0.0.1") 8 | STATSD_PORT = env.int("STATSD_PORT", 8125) 9 | STATSD_NAMESPACE = env.str("STATSD_NAMESPACE", "tuxbot_metric") 10 | 11 | if env.bool("DD_ACTIVE", False): 12 | from datadog import initialize 13 | from ddtrace import patch 14 | 15 | initialize( 16 | statsd_host=STATSD_HOST, 17 | statsd_port=STATSD_PORT, 18 | statsd_namespace=STATSD_NAMESPACE, 19 | ) 20 | 21 | patch( 22 | aioredis=True, 23 | asyncio=True, 24 | requests=True, 25 | asyncpg=True, 26 | logging=True, 27 | ) 28 | 29 | 30 | # Cogs 31 | # ----------------------------------------------------------------------------- 32 | # Math 33 | WOLFRAMALPHA_KEY = env.str("COGS_WOLFRAMALPHA_KEY") 34 | 35 | # Network 36 | IPINFO_KEY = env.str("COGS_IPINFO_KEY") 37 | GEOAPIFY_KEY = env.str("COGS_GEOAPIFY_KEY") 38 | IPGEOLOCATION_KEY = env.str("COGS_IPGEOLOCATION_KEY") 39 | OPENCAGEDATA_KEY = env.str("COGS_OPENCAGEDATA_KEY") 40 | PEERINGDB_KEY = env.str("COGS_PEERINGDB_KEY") 41 | -------------------------------------------------------------------------------- /.envs.example/.local/.bot: -------------------------------------------------------------------------------- 1 | # Tuxbot 2 | # ------------------------------------------------------------------------------ 3 | TUXBOT_LOG_LEVEL= 4 | TUXBOT_CLUSTER_COUNT= 5 | TUXBOT_SHARDING_STRATEGY= 6 | TUXBOT_FIRST_SHARD_ID= 7 | TUXBOT_LAST_SHARD_ID= 8 | TUXBOT_SHARD_COUNT= 9 | TUXBOT_CLUSTER_ID= 10 | TUXBOT_SHARD_ID= 11 | 12 | 13 | # Client 14 | # ----------------------------------------------------------------------------- 15 | TUXBOT_CLIENT_ID= 16 | TUXBOT_CLIENT_TOKEN= 17 | TUXBOT_CLIENT_GAME= 18 | TUXBOT_CLIENT_DISABLE_EVERYONE= 19 | TUXBOT_CLIENT_DISABLE_HELP= 20 | TUXBOT_CLIENT_DISABLED_EVENTS= 21 | TUXBOT_CLIENT_MAX_CACHED_MESSAGES= 22 | TUXBOT_CLIENT_OWNERS_ID= 23 | TUXBOT_CLIENT_PREFIXES= 24 | 25 | 26 | # Redis 27 | # ----------------------------------------------------------------------------- 28 | REDIS_URL= 29 | 30 | 31 | # Logging 32 | # ----------------------------------------------------------------------------- 33 | TUXBOT_WEBHOOK_SHARD= 34 | TUXBOT_WEBHOOK_ERROR= 35 | TUXBOT_WEBHOOK_CLUSTER= 36 | SENTRY_DSN= 37 | SENTRY_LOG_LEVEL= 38 | 39 | DD_ACTIVE= 40 | STATSD_HOST= 41 | STATSD_PORT= 42 | STATSD_NAMESPACE= 43 | 44 | 45 | # Cogs 46 | # ----------------------------------------------------------------------------- 47 | TUXBOT_LOADED_COGS= 48 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/IPWhoisProvider.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing 3 | 4 | import ipwhois 5 | from ipwhois import IPWhois 6 | 7 | from ...exceptions import RFCReserved 8 | from .abc import Provider 9 | 10 | 11 | class IPWhoisProvider(Provider): 12 | async def fetch(self, ip: str) -> tuple[str, dict[str, typing.Any]]: 13 | def _get_ipwhois_result(_ip: str) -> dict[str, typing.Any]: 14 | try: 15 | obj = IPWhois(ip) 16 | return obj.lookup_whois() # type: ignore 17 | 18 | except ipwhois.exceptions.ASNRegistryError: 19 | return {} 20 | 21 | except ipwhois.exceptions.IPDefinedError as e: 22 | raise RFCReserved( 23 | "This IP address defined as Private-Use Networks via " 24 | "RFC 1918." 25 | ) from e 26 | 27 | try: 28 | return "ipwhois", await asyncio.wait_for( 29 | asyncio.get_running_loop().run_in_executor( 30 | None, _get_ipwhois_result, str(ip) 31 | ), 32 | timeout=2, 33 | ) 34 | except asyncio.exceptions.TimeoutError: 35 | return "ipwhois", {} 36 | -------------------------------------------------------------------------------- /.envs.example/.production/.bot: -------------------------------------------------------------------------------- 1 | # Tuxbot 2 | # ------------------------------------------------------------------------------ 3 | TUXBOT_LOG_LEVEL= 4 | TUXBOT_CLUSTER_COUNT= 5 | TUXBOT_SHARDING_STRATEGY= 6 | TUXBOT_FIRST_SHARD_ID= 7 | TUXBOT_LAST_SHARD_ID= 8 | TUXBOT_SHARD_COUNT= 9 | TUXBOT_CLUSTER_ID= 10 | TUXBOT_SHARD_ID= 11 | 12 | 13 | # Client 14 | # ----------------------------------------------------------------------------- 15 | TUXBOT_CLIENT_ID= 16 | TUXBOT_CLIENT_TOKEN= 17 | TUXBOT_CLIENT_GAME= 18 | TUXBOT_CLIENT_DISABLE_EVERYONE= 19 | TUXBOT_CLIENT_DISABLE_HELP= 20 | TUXBOT_CLIENT_DISABLED_EVENTS= 21 | TUXBOT_CLIENT_MAX_CACHED_MESSAGES= 22 | TUXBOT_CLIENT_OWNERS_ID= 23 | TUXBOT_CLIENT_PREFIXES= 24 | 25 | 26 | # Redis 27 | # ----------------------------------------------------------------------------- 28 | REDIS_URL= 29 | 30 | 31 | # Logging 32 | # ----------------------------------------------------------------------------- 33 | TUXBOT_WEBHOOK_SHARD= 34 | TUXBOT_WEBHOOK_ERROR= 35 | TUXBOT_WEBHOOK_CLUSTER= 36 | SENTRY_DSN= 37 | SENTRY_LOG_LEVEL= 38 | 39 | DD_ACTIVE= 40 | STATSD_HOST= 41 | STATSD_PORT= 42 | STATSD_NAMESPACE= 43 | 44 | 45 | # Cogs 46 | # ----------------------------------------------------------------------------- 47 | TUXBOT_LOADED_COGS= 48 | -------------------------------------------------------------------------------- /tuxbot/core/models/Guild.py: -------------------------------------------------------------------------------- 1 | """ 2 | Guild model 3 | """ 4 | from tortoise import fields 5 | from tortoise.models import Model 6 | 7 | from tuxbot.core.models.fields.BigIntArrayField import BigIntArrayField 8 | 9 | 10 | class GuildModel(Model): 11 | """ 12 | Guild Model used to store guild configs 13 | """ 14 | 15 | id = fields.BigIntField(pk=True, description="Guild ID") 16 | prefix = fields.TextField(default=".", description="Guild prefix") 17 | 18 | moderators = BigIntArrayField( 19 | description="Guild moderators", default=None, null=True 20 | ) 21 | moderator_roles = BigIntArrayField( 22 | description="Guild moderator roles", default=None, null=True 23 | ) 24 | 25 | deleted = fields.BooleanField( 26 | default=True, description="Either the bot is on this guild" 27 | ) 28 | 29 | # ========================================================================= 30 | 31 | class Meta: 32 | """Meta values""" 33 | 34 | table = "guild" 35 | 36 | # ========================================================================= 37 | 38 | def __str__(self) -> str: 39 | return str(self.id) 40 | 41 | def __repr__(self) -> str: 42 | return f"" 43 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/Tag/models/Tags.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tags model 3 | """ 4 | from tortoise import fields 5 | from tortoise.models import Model 6 | 7 | 8 | class TagsModel(Model): 9 | """ 10 | Tag Model used for tags command 11 | """ 12 | 13 | id = fields.BigIntField(pk=True) 14 | guild_id = fields.BigIntField(description="Guild ID") 15 | author_id = fields.BigIntField(description="Author ID") 16 | 17 | name = fields.TextField() 18 | content = fields.TextField() 19 | 20 | uses = fields.IntField(default=0) 21 | 22 | created_at = fields.DatetimeField(auto_now_add=True) 23 | 24 | # ========================================================================= 25 | 26 | class Meta: 27 | """Meta values""" 28 | 29 | table = "tags" 30 | 31 | # ========================================================================= 32 | 33 | def __str__(self) -> str: 34 | return ( 35 | f"" 42 | ) 43 | 44 | __repr__ = __str__ 45 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/commands/Poll/models/Choices.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from tortoise import fields 4 | from tortoise.models import Model 5 | 6 | 7 | if typing.TYPE_CHECKING: 8 | from .Polls import PollsModel 9 | 10 | 11 | class ChoicesModel(Model): 12 | id = fields.BigIntField(pk=True) 13 | 14 | poll: fields.ForeignKeyRelation["PollsModel"] = fields.ForeignKeyField( 15 | "models.PollsModel", on_delete=fields.CASCADE, related_name="choices" 16 | ) 17 | 18 | label = fields.CharField(description="Choice label", max_length=8) 19 | choice = fields.CharField(description="Choice value", max_length=256) 20 | checked = fields.IntField(default=0) 21 | 22 | created_at = fields.DatetimeField(auto_now_add=True) 23 | 24 | # ========================================================================= 25 | 26 | class Meta: 27 | """Meta values""" 28 | 29 | table = "polls_choices" 30 | 31 | # ========================================================================= 32 | 33 | def __str__(self) -> str: 34 | return ( 35 | f"" 40 | ) 41 | 42 | __repr__ = __str__ 43 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Math 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for maths. 6 | """ 7 | 8 | from collections import namedtuple 9 | 10 | from tuxbot.abc.ModuleABC import ModuleABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | from .commands.Factor.command import FactorCommand 14 | from .commands.Graph.command import GraphCommand 15 | from .commands.Wolf.command import WolfCommand 16 | 17 | 18 | STANDARD_COMMANDS = (FactorCommand, WolfCommand, GraphCommand) 19 | 20 | VersionInfo = namedtuple("VersionInfo", "major minor micro release_level") 21 | version_info = VersionInfo(major=2, minor=2, micro=0, release_level="stable") 22 | 23 | __version__ = "v{}.{}.{}-{}".format( 24 | version_info.major, 25 | version_info.minor, 26 | version_info.micro, 27 | version_info.release_level, 28 | ).replace("\n", "") 29 | 30 | 31 | class Commands: 32 | def __init__(self, bot: Tuxbot) -> None: 33 | for command in STANDARD_COMMANDS: 34 | bot.collection.add_module("Math", command(bot=bot)) 35 | 36 | 37 | class Math(ModuleABC, Commands): 38 | """Set of useful commands for maths.""" 39 | 40 | def __init__(self, bot: Tuxbot) -> None: 41 | self.bot = bot 42 | 43 | super().__init__(bot=self.bot) 44 | 45 | # ========================================================================= 46 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/AppCommandCompletion/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.AppCommandCompletion.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever an app command is completed 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.Tuxbot import Tuxbot 11 | 12 | 13 | class AppCommandCompletion(commands.Cog): 14 | """Listener whenever an app command is completed""" 15 | 16 | def __init__(self, bot: Tuxbot) -> None: 17 | self.bot = bot 18 | 19 | # ========================================================================= 20 | # ========================================================================= 21 | 22 | @commands.Cog.listener(name="on_app_command_completion") 23 | async def _on_app_command_completion( 24 | self, 25 | interaction: discord.Interaction, 26 | command: discord.app_commands.Command # type: ignore[type-arg] 27 | | discord.app_commands.ContextMenu, 28 | ) -> None: 29 | self.bot.logger.info( 30 | "[AppCommandCompletion] App Command '%s' completed.", 31 | command.qualified_name, 32 | ) 33 | 34 | self.bot.statsd.increment( 35 | "command_success", 36 | value=1, 37 | tags=[f"command:{command.qualified_name}"], 38 | ) 39 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/commands/Poll/models/Polls.py: -------------------------------------------------------------------------------- 1 | from tortoise import fields 2 | from tortoise.models import Model 3 | 4 | from .Choices import ChoicesModel 5 | 6 | 7 | class PollsModel(Model): 8 | id = fields.BigIntField(pk=True) 9 | 10 | channel_id = fields.BigIntField(description="Channel ID") 11 | message_id = fields.BigIntField(description="Message ID") 12 | author_id = fields.BigIntField(description="Author ID") 13 | 14 | message = fields.CharField(description="Poll message", max_length=256) 15 | 16 | created_at = fields.DatetimeField(auto_now_add=True) 17 | 18 | # ========================================================================= 19 | 20 | choices: fields.ReverseRelation[ChoicesModel] 21 | 22 | # ========================================================================= 23 | 24 | class Meta: 25 | """Meta values""" 26 | 27 | table = "polls_message" 28 | 29 | # ========================================================================= 30 | 31 | def __str__(self) -> str: 32 | return ( 33 | f"" 39 | ) 40 | 41 | __repr__ = __str__ 42 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Quote/converters/QuoteConverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.converters 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Gives either discord message link format, or text. 6 | """ 7 | import typing 8 | 9 | import discord 10 | from discord.ext import commands 11 | 12 | from tuxbot.abc.TuxbotABC import TuxbotABC 13 | 14 | 15 | class QuoteMessage(typing.NamedTuple): 16 | """Fake discord Message style""" 17 | 18 | content: str 19 | author: str 20 | 21 | 22 | ConvertType = typing.Union[QuoteMessage, discord.Message] 23 | 24 | 25 | class QuoteConverter(commands.Converter[ConvertType]): 26 | """Gives either discord message link format, or text.""" 27 | 28 | async def convert( # type: ignore[override] 29 | self, ctx: commands.Context[TuxbotABC], argument: str 30 | ) -> ConvertType: 31 | try: 32 | if ( 33 | message := await commands.MessageConverter().convert( 34 | ctx, argument 35 | ) 36 | ) and message.channel.permissions_for( 37 | ctx.author # type: ignore 38 | ).read_message_history: 39 | return message 40 | 41 | raise commands.BadArgument 42 | except commands.BadArgument: 43 | return QuoteMessage( 44 | content=argument, author=str(ctx.message.author) 45 | ) 46 | -------------------------------------------------------------------------------- /tuxbot/cogs/Tags/commands/Tag/ui/modals/TagEditionModal.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tags edition view 3 | """ 4 | import discord 5 | 6 | from ...models.Tags import TagsModel 7 | 8 | 9 | class TagEditionModal(discord.ui.Modal): 10 | """ 11 | Tag edition modal 12 | """ 13 | 14 | title = "Edit a tag" 15 | 16 | def __init__(self, tag: TagsModel): 17 | super().__init__() 18 | 19 | self.__tag = tag 20 | 21 | self.content: discord.ui.TextInput[ 22 | TagEditionModal 23 | ] = discord.ui.TextInput( 24 | label="Content", 25 | style=discord.TextStyle.long, 26 | placeholder="Tag content here...", 27 | max_length=1900, 28 | default=self.__tag.content, 29 | ) 30 | self.add_item(self.content) 31 | 32 | # ========================================================================= 33 | # ========================================================================= 34 | 35 | async def on_submit(self, interaction: discord.Interaction) -> None: 36 | """Save tag on submit""" 37 | 38 | self.__tag.content = discord.utils.escape_mentions( 39 | self.content.value or "" 40 | ) 41 | await self.__tag.save() 42 | 43 | await interaction.response.send_message( 44 | f"Tag '{self.__tag.name}' successfully edited!", ephemeral=True 45 | ) 46 | -------------------------------------------------------------------------------- /tuxbot/core/utils/Generators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Useful generators 3 | """ 4 | import asyncio 5 | import inspect 6 | import textwrap 7 | import typing 8 | 9 | import aiohttp 10 | 11 | 12 | def gen_key(*args: typing.Any, **kwargs: typing.Any) -> str: 13 | frame = inspect.stack()[1] 14 | file = "/tuxbot/" + frame.filename.split("/tuxbot/")[-1] 15 | 16 | base_key = f"{file}>{frame.function}" 17 | params = "" 18 | 19 | if args: 20 | params = ",".join([repr(arg) for arg in args]) 21 | 22 | if kwargs: 23 | params += ",".join([f"{k}={repr(v)}" for k, v in kwargs.items()]) 24 | 25 | return f"{base_key}({params})" 26 | 27 | 28 | async def shorten(text: str, length: int) -> dict[str, str]: 29 | output: dict[str, str] = { 30 | "text": textwrap.shorten(text, length), 31 | "link": "", 32 | } 33 | 34 | if output["text"] != text: 35 | try: 36 | async with aiohttp.ClientSession() as cs, cs.post( 37 | "https://paste.ramle.be/documents", 38 | data=text.encode(), 39 | timeout=aiohttp.ClientTimeout(total=0.300), 40 | ) as r: 41 | output[ 42 | "link" 43 | ] = f"https://paste.ramle.be/{(await r.json())['key']}" 44 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 45 | pass 46 | 47 | return output 48 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/HTTPs/ThreeXX.py: -------------------------------------------------------------------------------- 1 | from .HttpCode import HttpCode 2 | 3 | 4 | __all__ = ( 5 | "Http300", 6 | "Http301", 7 | "Http302", 8 | "Http303", 9 | "Http304", 10 | "Http305", 11 | "Http306", 12 | "Http307", 13 | "Http308", 14 | ) 15 | 16 | 17 | class Http300(HttpCode): 18 | value = 300 19 | name = "Multiple Choices" 20 | mdn = True 21 | cat = True 22 | 23 | 24 | class Http301(HttpCode): 25 | value = 301 26 | name = "Moved Permanently" 27 | mdn = True 28 | cat = True 29 | 30 | 31 | class Http302(HttpCode): 32 | value = 302 33 | name = "Found" 34 | mdn = True 35 | cat = True 36 | 37 | 38 | class Http303(HttpCode): 39 | value = 303 40 | name = "See Other" 41 | mdn = True 42 | cat = True 43 | 44 | 45 | class Http304(HttpCode): 46 | value = 304 47 | name = "Not Modified" 48 | mdn = True 49 | cat = True 50 | 51 | 52 | class Http305(HttpCode): 53 | value = 305 54 | name = "Use Proxy" 55 | mdn = False 56 | cat = True 57 | 58 | 59 | class Http306(HttpCode): 60 | value = 306 61 | name = "Unused" 62 | mdn = False 63 | cat = False 64 | 65 | 66 | class Http307(HttpCode): 67 | value = 307 68 | name = "Temporary Redirect" 69 | mdn = True 70 | cat = True 71 | 72 | 73 | class Http308(HttpCode): 74 | value = 308 75 | name = "Permanent Redirect" 76 | mdn = True 77 | cat = True 78 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/GuildRemove/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.GuildRemove.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever a guild is leaved 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.Tuxbot import Tuxbot 11 | 12 | 13 | class GuildRemove(commands.Cog): 14 | """Listener whenever a guild is leaved""" 15 | 16 | def __init__(self, bot: Tuxbot) -> None: 17 | self.bot = bot 18 | 19 | # ========================================================================= 20 | # ========================================================================= 21 | 22 | @commands.Cog.listener(name="on_guild_remove") 23 | async def _on_guild_remove( 24 | self, guild: discord.Guild 25 | ) -> None: # pylint: disable=unused-argument 26 | self.bot.statsd.gauge( 27 | "guilds", 28 | value=len(self.bot.guilds), 29 | ) 30 | 31 | self.bot.logger.info( 32 | "[GuildRemove] Tuxbot removed from the guild '%s'.", guild.name 33 | ) 34 | 35 | if guild_model := await self.bot.models["Guild"].get_or_none( 36 | id=guild.id 37 | ): 38 | guild_model.deleted = True 39 | await guild_model.save() 40 | else: 41 | guild_model = await self.bot.models["Guild"].create( 42 | id=guild.id, moderators=[], moderator_roles=[], deleted=True 43 | ) 44 | await guild_model.save() 45 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.commands.AutoPin.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Toggle auto pins 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | from .models.AutoPin import AutoPinModel 14 | from .ui.ViewController import ViewController 15 | 16 | 17 | class AutoPinCommand(commands.Cog): 18 | """Toggle auto pins""" 19 | 20 | def __init__(self, bot: Tuxbot) -> None: 21 | self.bot = bot 22 | 23 | @staticmethod 24 | async def __get_model(guild_id: int) -> AutoPinModel: 25 | model: AutoPinModel | None = await AutoPinModel.get_or_none( 26 | guild_id=guild_id 27 | ) 28 | 29 | if not isinstance(model, AutoPinModel): 30 | _m: AutoPinModel = await AutoPinModel.create(guild_id=guild_id) 31 | return _m 32 | 33 | return model 34 | 35 | # ========================================================================= 36 | # ========================================================================= 37 | 38 | @commands.group(name="auto_pin") 39 | @commands.guild_only() 40 | async def _auto_pin(self, ctx: commands.Context[TuxbotABC]) -> None: 41 | if not ctx.guild: 42 | return 43 | 44 | controller = ViewController( 45 | ctx=ctx, 46 | model=await self.__get_model(guild_id=ctx.guild.id), 47 | ) 48 | 49 | await controller.send() 50 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/commands/Update/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin.commands.Update.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Command to update Tuxbot 6 | """ 7 | 8 | from discord.ext import commands 9 | from jishaku.models import copy_context_with 10 | 11 | from tuxbot.abc.TuxbotABC import TuxbotABC 12 | from tuxbot.core.Tuxbot import Tuxbot 13 | 14 | 15 | class UpdateCommand(commands.Cog): 16 | """Update tuxbot""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | 21 | # ========================================================================= 22 | # ========================================================================= 23 | 24 | @commands.command("update") 25 | async def _update(self, ctx: commands.Context[TuxbotABC]) -> None: 26 | sh = "jsk sh" 27 | prefix = ctx.prefix or ( 28 | f"<@{ctx.bot.user.id}>" if ctx.bot.user else "" 29 | ) 30 | 31 | git = f"{sh} git pull" 32 | update = f"{sh} make update" 33 | 34 | git_command_ctx = await copy_context_with(ctx, content=prefix + git) 35 | update_command_ctx = await copy_context_with( 36 | ctx, content=prefix + update 37 | ) 38 | 39 | if git_command_ctx.command and update_command_ctx.command: 40 | await git_command_ctx.command.invoke(git_command_ctx) 41 | await update_command_ctx.command.invoke(update_command_ctx) 42 | 43 | if command := self.bot.get_command("restart"): 44 | await command(ctx) 45 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/ui/buttons/ASNButton.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing 4 | 5 | import discord 6 | from jishaku.models import copy_context_with 7 | 8 | 9 | if typing.TYPE_CHECKING: 10 | from ..ViewController import ViewController 11 | 12 | 13 | class ASNButton(discord.ui.Button["ViewController"]): 14 | disabled: bool 15 | label: str 16 | emoji: discord.PartialEmoji | None 17 | row: int 18 | 19 | def __init__(self, row: int, controller: ViewController) -> None: 20 | self.controller = controller 21 | prefix = self.controller.ctx.prefix or ( 22 | f"<@{self.controller.ctx.bot.user.id}>" 23 | if self.controller.ctx.bot.user 24 | else "" 25 | ) 26 | 27 | super().__init__( 28 | style=discord.ButtonStyle.primary, 29 | label=prefix + "peeringdb", 30 | row=row, 31 | ) 32 | 33 | async def callback(self, interaction: discord.Interaction) -> None: 34 | args = "peeringdb " + str(self.controller.get_data("ipwhois", "asn")) 35 | prefix = self.controller.ctx.prefix or ( 36 | f"<@{self.controller.ctx.bot.user.id}>" 37 | if self.controller.ctx.bot.user 38 | else "" 39 | ) 40 | 41 | command_ctx = await copy_context_with( 42 | self.controller.ctx, 43 | content=prefix + args, 44 | author=interaction.user, 45 | ) 46 | await self.controller.ctx.bot.process_commands(command_ctx.message) 47 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/GuildJoin/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.GuildJoin.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever a guild is joined 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.models.Guild import GuildModel 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | 14 | class GuildJoin(commands.Cog): 15 | """Listener whenever a guild is joined""" 16 | 17 | def __init__(self, bot: Tuxbot) -> None: 18 | self.bot = bot 19 | 20 | # ========================================================================= 21 | # ========================================================================= 22 | 23 | @commands.Cog.listener(name="on_guild_join") 24 | async def _on_guild_join( 25 | self, guild: discord.Guild 26 | ) -> None: # pylint: disable=unused-argument 27 | self.bot.statsd.gauge( 28 | "guilds", 29 | value=len(self.bot.guilds), 30 | ) 31 | 32 | self.bot.logger.info( 33 | "[GuildJoin] Tuxbot added to the guild '%s'.", guild.name 34 | ) 35 | 36 | if guild_model := await GuildModel.get_or_none(id=guild.id): 37 | guild_model.deleted = False 38 | await guild_model.save() 39 | else: 40 | guild_model = await GuildModel.create( 41 | id=guild.id, moderators=[], moderator_roles=[], deleted=False 42 | ) 43 | await guild_model.save() 44 | 45 | self.bot.cached_config[guild.id] = {} 46 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoQuote/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.commands.AutoQuote.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Toggle auto quotes 6 | """ 7 | 8 | from discord.ext import commands 9 | 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | from .models.AutoQuote import AutoQuoteModel 14 | from .ui.ViewController import ViewController 15 | 16 | 17 | class AutoQuoteCommand(commands.Cog): 18 | """Toggle auto quotes""" 19 | 20 | def __init__(self, bot: Tuxbot) -> None: 21 | self.bot = bot 22 | 23 | @staticmethod 24 | async def __get_model(guild_id: int) -> AutoQuoteModel: 25 | model: AutoQuoteModel | None = await AutoQuoteModel.get_or_none( 26 | guild_id=guild_id 27 | ) 28 | 29 | if not isinstance(model, AutoQuoteModel): 30 | _m: AutoQuoteModel = await AutoQuoteModel.create(guild_id=guild_id) 31 | return _m 32 | 33 | return model 34 | 35 | # ========================================================================= 36 | # ========================================================================= 37 | 38 | @commands.group(name="auto_quote") 39 | @commands.guild_only() 40 | async def _auto_quote(self, ctx: commands.Context[TuxbotABC]) -> None: 41 | if not ctx.guild: 42 | return 43 | 44 | controller = ViewController( 45 | ctx=ctx, 46 | model=await self.__get_model(guild_id=ctx.guild.id), 47 | ) 48 | 49 | await controller.send() 50 | -------------------------------------------------------------------------------- /tuxbot/cogs/Logs/listeners/CommandCompletion/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Logs.listeners.CommandCompletion.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever command is completed 6 | """ 7 | from datetime import datetime, timezone 8 | 9 | from discord.ext import commands 10 | 11 | from tuxbot.abc.TuxbotABC import TuxbotABC 12 | from tuxbot.core.Tuxbot import Tuxbot 13 | 14 | 15 | class CommandCompletion(commands.Cog): 16 | """Listener whenever command is completed""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | 21 | # ========================================================================= 22 | # ========================================================================= 23 | 24 | @commands.Cog.listener(name="on_command_completion") 25 | async def _on_command_completion( 26 | self, ctx: commands.Context[TuxbotABC] 27 | ) -> None: 28 | if not ctx.command: 29 | return 30 | 31 | command = ctx.command.name 32 | 33 | if parent_name := ctx.command.full_parent_name: 34 | command = f"{parent_name} {ctx.command.name}" 35 | 36 | delta = datetime.now(tz=timezone.utc) - ctx.message.created_at 37 | 38 | self.bot.logger.info( 39 | "[CommandCompletion] Command '%s' completed in %d ms.", 40 | command, 41 | delta.total_seconds() * 1000, 42 | ) 43 | 44 | self.bot.statsd.increment( 45 | "command_success", value=1, tags=[f"command:{command}"] 46 | ) 47 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Avatar.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Shows avatar of user 6 | """ 7 | 8 | import discord 9 | from discord.ext import commands 10 | 11 | from tuxbot.abc.TuxbotABC import TuxbotABC 12 | from tuxbot.core.Tuxbot import Tuxbot 13 | 14 | from ...converters.MemberOrUserConverter import MemberOrUserConverter 15 | from ..exceptions import UserNotFound 16 | from .ui.ViewController import ViewController 17 | 18 | 19 | class AvatarCommand(commands.Cog): 20 | """Shows user's avatar""" 21 | 22 | def __init__(self, bot: Tuxbot) -> None: 23 | self.bot = bot 24 | 25 | # ========================================================================= 26 | # ========================================================================= 27 | 28 | @commands.command(name="avatar") 29 | async def _avatar( 30 | self, 31 | ctx: commands.Context[TuxbotABC], 32 | *, 33 | argument: str | None = None, 34 | ) -> None: 35 | if not argument: 36 | user = ctx.author 37 | elif not (_u := await MemberOrUserConverter().convert(ctx, argument)): 38 | raise UserNotFound("Unable to find this user") 39 | else: 40 | user = _u 41 | 42 | e = discord.Embed( 43 | title=f"Avatar of {user}", 44 | color=self.bot.utils.colors.EMBED_BORDER, 45 | ) 46 | e.set_image(url=user.display_avatar.url) 47 | 48 | controller = ViewController(ctx=ctx, data=user) 49 | 50 | await controller.send() 51 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev.commands.HTTP.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Command to show doc about HTTP code 6 | """ 7 | 8 | import discord 9 | from discord.ext import commands 10 | 11 | from tuxbot.abc.TuxbotABC import TuxbotABC 12 | from tuxbot.core.Tuxbot import Tuxbot 13 | 14 | from .converters.HttpCodeConverter import HttpCodeConverter 15 | from .HTTPs import HttpCode 16 | 17 | 18 | class HTTPCommand(commands.Cog): 19 | """Shows HTTP code doc""" 20 | 21 | def __init__(self, bot: Tuxbot) -> None: 22 | self.bot = bot 23 | 24 | # ========================================================================= 25 | # ========================================================================= 26 | 27 | @commands.command(name="http") 28 | async def _http( 29 | self, ctx: commands.Context[TuxbotABC], http_code: HttpCodeConverter 30 | ) -> None: 31 | if isinstance(http_code, HttpCode): 32 | e = discord.Embed( 33 | title=f"{http_code.value} {http_code.name}", color=0x2F3136 34 | ) 35 | 36 | if http_code.cat: 37 | e.set_image(url=f"https://http.cat/{http_code.value}") 38 | 39 | if http_code.mdn: 40 | url = ( 41 | "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status" 42 | f"/{http_code.value}" 43 | ) 44 | e.add_field(name="MDN", value=f"> [{url}]({url})") 45 | e.set_image(url=f"https://http.cat/{http_code.value}") 46 | 47 | await ctx.send(embed=e) 48 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/HTTPs/TwoXX.py: -------------------------------------------------------------------------------- 1 | from .HttpCode import HttpCode 2 | 3 | 4 | __all__ = ( 5 | "Http200", 6 | "Http201", 7 | "Http202", 8 | "Http203", 9 | "Http204", 10 | "Http205", 11 | "Http206", 12 | "Http207", 13 | "Http208", 14 | "Http226", 15 | ) 16 | 17 | 18 | class Http200(HttpCode): 19 | value = 200 20 | name = "OK" 21 | mdn = True 22 | cat = True 23 | 24 | 25 | class Http201(HttpCode): 26 | value = 201 27 | name = "Created" 28 | mdn = True 29 | cat = True 30 | 31 | 32 | class Http202(HttpCode): 33 | value = 202 34 | name = "Accepted" 35 | mdn = True 36 | cat = True 37 | 38 | 39 | class Http203(HttpCode): 40 | value = 203 41 | name = "Non-Authoritative Information" 42 | mdn = True 43 | cat = False 44 | 45 | 46 | class Http204(HttpCode): 47 | value = 204 48 | name = "No Content" 49 | mdn = True 50 | cat = True 51 | 52 | 53 | class Http205(HttpCode): 54 | value = 205 55 | name = "Reset Content" 56 | mdn = True 57 | cat = False 58 | 59 | 60 | class Http206(HttpCode): 61 | value = 206 62 | name = "Partial Content" 63 | mdn = True 64 | cat = True 65 | 66 | 67 | class Http207(HttpCode): 68 | value = 207 69 | name = "Multi-Status (WebDAV)" 70 | mdn = False 71 | cat = True 72 | 73 | 74 | class Http208(HttpCode): 75 | value = 208 76 | name = "Already Reported (WebDAV)" 77 | mdn = False 78 | cat = False 79 | 80 | 81 | class Http226(HttpCode): 82 | value = 226 83 | name = "IM Used (HTTP Delta encoding)" 84 | mdn = False 85 | cat = False 86 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/HTTPs/HttpCode.py: -------------------------------------------------------------------------------- 1 | class HttpCode: 2 | _value: int 3 | _name: str 4 | _mdn: bool 5 | _cat: bool 6 | 7 | # ========================================================================= 8 | # ========================================================================= 9 | 10 | @property 11 | def value(self) -> int: 12 | return self._value 13 | 14 | @value.setter 15 | def value(self, val: int) -> None: 16 | if not isinstance(val, int): 17 | raise TypeError 18 | 19 | self._value = val 20 | 21 | # ========================================================================= 22 | 23 | @property 24 | def name(self) -> str: 25 | return self._name 26 | 27 | @name.setter 28 | def name(self, val: str) -> None: 29 | if not isinstance(val, str): 30 | raise TypeError 31 | 32 | self._name = val 33 | 34 | # ========================================================================= 35 | 36 | @property 37 | def mdn(self) -> bool: 38 | return self._mdn 39 | 40 | @mdn.setter 41 | def mdn(self, val: bool) -> None: 42 | if not isinstance(val, bool): 43 | raise TypeError 44 | 45 | self._mdn = val 46 | 47 | # ========================================================================= 48 | 49 | @property 50 | def cat(self) -> bool: 51 | return self._cat 52 | 53 | @cat.setter 54 | def cat(self, val: bool) -> None: 55 | if not isinstance(val, bool): 56 | raise TypeError 57 | 58 | self._cat = val 59 | 60 | # ========================================================================= 61 | -------------------------------------------------------------------------------- /tuxbot/cogs/Network/commands/Iplocalise/providers/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Network.functions.providers.base 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Global provider which uses all sub provider 6 | """ 7 | import asyncio 8 | import typing 9 | 10 | from tuxbot.core.config import config 11 | 12 | from . import ( 13 | HostnameProvider, 14 | IPGeolocationProvider, 15 | IPInfoProvider, 16 | IPWhoisProvider, 17 | MapProvider, 18 | OpenCageDataProvider, 19 | ) 20 | 21 | 22 | def get_base_providers(data: dict[str, typing.Any]) -> dict[str, typing.Any]: 23 | """Get result from base providers""" 24 | 25 | hostname = HostnameProvider() 26 | ipgeo = IPGeolocationProvider(config.IPGEOLOCATION_KEY) 27 | ipinfo = IPInfoProvider(config.IPINFO_KEY) 28 | ipwhois = IPWhoisProvider() 29 | 30 | result = { 31 | "hostname": asyncio.create_task(hostname.fetch(data["ip"])), 32 | "ipgeo": asyncio.create_task(ipgeo.fetch(data["ip"])), 33 | "ipinfo": asyncio.create_task(ipinfo.fetch(data["ip"])), 34 | "ipwhois": asyncio.create_task(ipwhois.fetch(data["ip"])), 35 | } 36 | 37 | return result 38 | 39 | 40 | def get_auxiliary_providers( 41 | data: dict[str, typing.Any] 42 | ) -> dict[str, typing.Any]: 43 | """Get result from auxiliary providers""" 44 | 45 | loc = data["ipinfo"].get("loc", "") 46 | 47 | map_location = MapProvider(config.GEOAPIFY_KEY) 48 | opencage = OpenCageDataProvider(config.OPENCAGEDATA_KEY) 49 | 50 | result = { 51 | "map": asyncio.create_task(map_location.fetch(loc)), 52 | "opencage": asyncio.create_task(opencage.fetch(loc.replace(",", "+"))), 53 | } 54 | 55 | return result 56 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/listeners/RawReactionAdd/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Polls.listeners.RawReactionAdd.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever a reaction is added 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.Tuxbot import Tuxbot 11 | 12 | from ...commands.Poll.command import PollCommand 13 | 14 | 15 | class RawReactionAdd(commands.Cog): 16 | """Listener whenever a reaction is added""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | 21 | # ========================================================================= 22 | # ========================================================================= 23 | 24 | @commands.Cog.listener(name="on_raw_reaction_add") 25 | async def _on_raw_reaction_add( 26 | self, pld: discord.RawReactionActionEvent 27 | ) -> None: 28 | if pld.member == self.bot.user: 29 | return 30 | 31 | if pld.emoji.name not in self.bot.utils.emotes.ALPHABET: 32 | return 33 | 34 | if poll := await PollCommand.get_poll(pld.message_id): 35 | choices = await poll.choices.all() 36 | 37 | if ( 38 | pld.emoji.name 39 | not in self.bot.utils.emotes.ALPHABET[: len(choices)] 40 | ): 41 | return 42 | 43 | if not ( 44 | _choice := [c for c in choices if c.label == pld.emoji.name] 45 | ): 46 | return 47 | 48 | choice = _choice[0] 49 | 50 | choice.checked += 1 51 | await choice.save() 52 | 53 | await PollCommand.update_poll(self.bot, poll) 54 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Credits/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Credits.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Shows information about tuxbot creators 6 | """ 7 | 8 | import discord 9 | from discord.ext import commands 10 | 11 | from tuxbot.abc.TuxbotABC import TuxbotABC 12 | from tuxbot.core.Tuxbot import Tuxbot 13 | 14 | 15 | class CreditsCommand(commands.Cog): 16 | """Shows tuxbot's creators""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | 21 | # ========================================================================= 22 | # ========================================================================= 23 | 24 | @commands.command( 25 | name="credits", aliases=["contributors", "authors", "credit"] 26 | ) 27 | async def _credits(self, ctx: commands.Context[TuxbotABC]) -> None: 28 | e = discord.Embed( 29 | title="Contributors", 30 | color=self.bot.utils.colors.EMBED_BORDER, 31 | ) 32 | 33 | e.add_field( 34 | name="**Romain#5117** ", 35 | value=( 36 | "> • [github](https://github.com/Rom1-J)\n" 37 | "> • [gitlab](https://gitlab.gnous.eu/Romain)\n" 38 | "> • romain@gnous.eu" 39 | ), 40 | inline=True, 41 | ) 42 | e.add_field( 43 | name="**Outout#4039** ", 44 | value=( 45 | "> • [gitea](https://git.gnous.eu/mael)\n" 46 | "> • [@outoutxyz](https://twitter.com/outouxyz)\n" 47 | "> • mael@gnous.eu" 48 | ), 49 | inline=True, 50 | ) 51 | 52 | await ctx.send(embed=e) 53 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/commands/Sync/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin.commands.Sync.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Command to sync Tuxbot 6 | """ 7 | import typing 8 | 9 | import discord 10 | from discord.ext import commands 11 | 12 | from tuxbot.abc.TuxbotABC import TuxbotABC 13 | from tuxbot.core.Tuxbot import Tuxbot 14 | 15 | 16 | class SyncCommand(commands.Cog): 17 | """Sync tuxbot""" 18 | 19 | def __init__(self, bot: Tuxbot) -> None: 20 | self.bot = bot 21 | 22 | # ========================================================================= 23 | # ========================================================================= 24 | 25 | @commands.command("sync") 26 | async def _sync( 27 | self, 28 | ctx: commands.Context[TuxbotABC], 29 | guilds: commands.Greedy[discord.Object], 30 | spec: typing.Literal["~"] | None = None, 31 | ) -> None: 32 | if not guilds and ctx.guild: 33 | if spec == "~": 34 | fmt = await ctx.bot.tree.sync(guild=ctx.guild) 35 | else: 36 | fmt = await ctx.bot.tree.sync() 37 | 38 | await ctx.send( 39 | f"Synced {len(fmt)} commands " 40 | + ("globally" if spec is not None else "to the current guild.") 41 | ) 42 | return 43 | 44 | if not guilds: 45 | return 46 | 47 | i = 0 48 | for guild in guilds: 49 | try: 50 | await ctx.bot.tree.sync(guild=guild) 51 | except discord.HTTPException: 52 | pass 53 | else: 54 | i += 1 55 | 56 | await ctx.send(f"Synced the tree to {i}/{len(guilds)} guilds.") 57 | -------------------------------------------------------------------------------- /tuxbot/cogs/Polls/listeners/RawReactionRemove/listener.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Polls.listeners.RawReactionRemove.listener 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Listener whenever a reaction is removed 6 | """ 7 | import discord 8 | from discord.ext import commands 9 | 10 | from tuxbot.core.Tuxbot import Tuxbot 11 | 12 | from ...commands.Poll.command import PollCommand 13 | 14 | 15 | class RawReactionRemove(commands.Cog): 16 | """Listener whenever a reaction is removed""" 17 | 18 | def __init__(self, bot: Tuxbot) -> None: 19 | self.bot = bot 20 | 21 | # ========================================================================= 22 | # ========================================================================= 23 | 24 | @commands.Cog.listener(name="on_raw_reaction_remove") 25 | async def _on_raw_reaction_remove( 26 | self, pld: discord.RawReactionActionEvent 27 | ) -> None: 28 | if pld.member == self.bot.user: 29 | return 30 | 31 | if pld.emoji.name not in self.bot.utils.emotes.ALPHABET: 32 | return 33 | 34 | if poll := await PollCommand.get_poll(pld.message_id): 35 | choices = await poll.choices.all() 36 | 37 | if ( 38 | pld.emoji.name 39 | not in self.bot.utils.emotes.ALPHABET[: len(choices)] 40 | ): 41 | return 42 | 43 | if not ( 44 | _choice := [c for c in choices if c.label == pld.emoji.name] 45 | ): 46 | return 47 | 48 | choice = _choice[0] 49 | 50 | choice.checked -= 1 51 | await choice.save() 52 | 53 | await PollCommand.update_poll(self.bot, poll) 54 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/commands/AutoPin/ui/modals/PinThresholdModal.py: -------------------------------------------------------------------------------- 1 | """ 2 | AutoPin threshold configuration view 3 | """ 4 | import typing 5 | 6 | import discord 7 | 8 | 9 | if typing.TYPE_CHECKING: 10 | from ..ViewController import ViewController 11 | 12 | 13 | class PinThresholdModal(discord.ui.Modal): 14 | """ 15 | Tag creation modal 16 | """ 17 | 18 | title = "Set the threshold" 19 | 20 | def __init__(self, view: "ViewController") -> None: 21 | super().__init__() 22 | 23 | self.view = view 24 | 25 | self.threshold: discord.ui.TextInput[ 26 | PinThresholdModal 27 | ] = discord.ui.TextInput( 28 | label="Threshold", 29 | placeholder="How many reaction to add for pin...", 30 | ) 31 | 32 | self.add_item(self.threshold) 33 | 34 | # ========================================================================= 35 | # ========================================================================= 36 | 37 | async def on_submit(self, interaction: discord.Interaction) -> None: 38 | """Save threshold on submit""" 39 | 40 | if not interaction.guild: 41 | return 42 | 43 | if not self.threshold.value.isdigit() or ( 44 | 2 > (threshold := int(self.threshold.value)) or threshold > 9999 45 | ): 46 | await interaction.response.send_message( 47 | "The threshold must be between 2 and 9999", 48 | ephemeral=True, 49 | ) 50 | return 51 | 52 | self.view.model.threshold = threshold 53 | await self.view.model.save() 54 | 55 | await self.view.cache() 56 | 57 | await self.view.edit() 58 | await interaction.response.defer() 59 | -------------------------------------------------------------------------------- /tuxbot/core/utils/Paginator/SimplePages.py: -------------------------------------------------------------------------------- 1 | """Simple paginator from embeds""" 2 | import typing 3 | 4 | import discord 5 | from discord.ext import commands, menus 6 | 7 | from tuxbot.abc.TuxbotABC import TuxbotABC 8 | 9 | from .Pages import Pages 10 | 11 | 12 | class SimplePages(Pages): 13 | """Simple paginator from embeds""" 14 | 15 | class SimplePageSource(menus.ListPageSource): 16 | """Source form SimplePages""" 17 | 18 | # pylint: disable=arguments-renamed 19 | async def format_page( 20 | self, menu: Pages, entries: typing.Any 21 | ) -> discord.Embed: 22 | """Format page before rendering""" 23 | 24 | pages = [] 25 | 26 | for index, entry in enumerate( 27 | entries, start=menu.current_page * self.per_page 28 | ): 29 | pages.append(f"{index + 1}. {entry}") 30 | 31 | maximum = self.get_max_pages() # type: ignore 32 | 33 | if maximum > 1: 34 | footer = ( 35 | f"Page {menu.current_page + 1}/{maximum} " 36 | f"({len(self.entries)} entries)" 37 | ) 38 | menu.embed.set_footer(text=footer) # type: ignore 39 | 40 | menu.embed.description = "\n".join(pages) # type: ignore 41 | return menu.embed # type: ignore 42 | 43 | def __init__( 44 | self, 45 | entries: typing.Any, 46 | *, 47 | ctx: commands.Context[TuxbotABC] | discord.Interaction, 48 | per_page: int = 12, 49 | ): 50 | super().__init__( 51 | self.SimplePageSource(entries, per_page=per_page), # type: ignore 52 | ctx=ctx, 53 | ) 54 | self.embed = discord.Embed(colour=discord.Colour.blurple()) 55 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |image0| |image1| |image2| |image3| 2 | 3 | 4 | Tuxbot 5 | ====== 6 | 7 | Tuxbot, made by `GnousEU `_ 8 | 9 | 10 | Project Purpose 11 | --------------- 12 | 13 | **Tuxbot** is a discord bot written in python and maintained since 2017. 14 | Its main missions are to propose useful commands for domains related to network, system administration, mathematics and others. 15 | 16 | -------------- 17 | 18 | Basic Makefile Commands 19 | ----------------------- 20 | 21 | 22 | Poetry 23 | ~~~~~~ 24 | 25 | Create Poetry venv for this project: 26 | 27 | .. code-block:: bash 28 | 29 | $ poetry env use 3.10 30 | $ poetry shell 31 | $ poetry install 32 | 33 | 34 | Type checks 35 | ~~~~~~~~~~~ 36 | 37 | Running type and lint checks with ``pre_commit``: 38 | 39 | .. code-block:: bash 40 | 41 | $ make pre_commit 42 | 43 | 44 | Running development instance 45 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 46 | 47 | To run a development instance: 48 | 49 | .. code-block:: bash 50 | 51 | $ make dev 52 | 53 | 54 | Sentry 55 | ~~~~~~ 56 | 57 | Sentry is an error logging aggregator service. You can sign up for a free account at ``_ or download and host it yourself. 58 | 59 | You must set the DSN url in production. 60 | 61 | -------------- 62 | 63 | Deployment 64 | ---------- 65 | 66 | The following details how to deploy this application. 67 | 68 | Refer to `INSTALL.rst <./INSTALL.rst>`_ 69 | 70 | 71 | .. |image0| image:: https://img.shields.io/badge/python-3.10-%23007ec6 72 | .. |image1| image:: https://img.shields.io/github/issues/Rom1-J/tuxbot-bot 73 | .. |image2| image:: https://img.shields.io/badge/code%20style-black-000000.svg 74 | .. |image3| image:: https://wakatime.com/badge/github/Rom1-J/tuxbot-bot.svg 75 | :target: https://wakatime.com/badge/github/Rom1-J/tuxbot-bot 76 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: "^docs/|/migrations/" 2 | default_stages: [commit] 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.3.0 7 | hooks: 8 | - id: trailing-whitespace 9 | - id: end-of-file-fixer 10 | - id: check-yaml 11 | 12 | - repo: https://github.com/asottile/pyupgrade 13 | rev: v2.34.0 14 | hooks: 15 | - id: pyupgrade 16 | args: [--py310-plus] 17 | 18 | - repo: https://github.com/psf/black 19 | rev: 22.3.0 20 | hooks: 21 | - id: black 22 | exclude: ^docker/scripts/ 23 | 24 | - repo: https://github.com/PyCQA/isort 25 | rev: 5.10.1 26 | hooks: 27 | - id: isort 28 | 29 | - repo: https://github.com/pre-commit/mirrors-mypy 30 | rev: 'tags/v0.971' 31 | hooks: 32 | - id: mypy 33 | args: ["--strict", "--namespace-packages", "--ignore-missing-imports", "--show-error-codes"] 34 | additional_dependencies: 35 | - types-PyYAML 36 | - types-aiofiles 37 | - types-beautifulsoup4 38 | - types-boto 39 | - types-protobuf 40 | - types-pytz 41 | - types-requests 42 | - types-setuptools 43 | - types-six 44 | - types-tabulate 45 | - git+https://github.com/Rapptz/discord.py 46 | - discord-ext-menus 47 | - tortoise-orm 48 | - sympy 49 | - ddtrace 50 | 51 | - repo: https://github.com/PyCQA/flake8 52 | rev: 4.0.1 53 | hooks: 54 | - id: flake8 55 | exclude: ^docker/scripts/ 56 | args: ["--config=setup.cfg"] 57 | additional_dependencies: [flake8-isort] 58 | 59 | # sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date 60 | ci: 61 | autoupdate_schedule: weekly 62 | skip: [] 63 | submodules: false 64 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Ping/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Ping.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Shows all pings about tuxbot 6 | """ 7 | import time 8 | 9 | import discord 10 | from discord.ext import commands 11 | from tortoise import Tortoise 12 | 13 | from tuxbot.abc.TuxbotABC import TuxbotABC 14 | from tuxbot.core.Tuxbot import Tuxbot 15 | 16 | 17 | class PingCommand(commands.Cog): 18 | """Shows tuxbot's ping""" 19 | 20 | def __init__(self, bot: Tuxbot) -> None: 21 | self.bot = bot 22 | 23 | # ========================================================================= 24 | # ========================================================================= 25 | 26 | @commands.command(name="ping") 27 | async def _ping(self, ctx: commands.Context[TuxbotABC]) -> None: 28 | start = time.perf_counter() 29 | await ctx.typing() 30 | end = time.perf_counter() 31 | typing = round((end - start) * 1000, 2) 32 | 33 | start = time.perf_counter() 34 | await self.bot.redis.ping() 35 | end = time.perf_counter() 36 | redis = round((end - start) * 1000, 2) 37 | 38 | connection = Tortoise.get_connection("default") 39 | start = time.perf_counter() 40 | await connection.execute_query("select 1;") 41 | end = time.perf_counter() 42 | postgres = round((end - start) * 1000, 2) 43 | 44 | latency = round(self.bot.latency * 1000, 2) 45 | 46 | e = discord.Embed(title="Ping", color=discord.Color.teal()) 47 | 48 | e.add_field(name="Websocket", value=f"{latency}ms") 49 | e.add_field(name="Typing", value=f"{typing}ms") 50 | e.add_field(name="Redis", value=f"{redis}ms") 51 | e.add_field(name="Postgres", value=f"{postgres}ms") 52 | 53 | await ctx.send(embed=e) 54 | -------------------------------------------------------------------------------- /tuxbot/cogs/Admin/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Admin 3 | ~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of owner only command. 6 | """ 7 | 8 | from collections import namedtuple 9 | 10 | from tuxbot.abc.ModuleABC import ModuleABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | from ...abc.TuxbotABC import TuxbotABC 14 | from .commands.Restart.command import RestartCommand 15 | from .commands.Sync.command import SyncCommand 16 | from .commands.Update.command import UpdateCommand 17 | 18 | 19 | # Note: for some reasons, this import must be done after tuxbot.* imports. 20 | # If it isn't, commands is bind on tuxbot.cogs.Admin.commands ¯\_(ツ)_/¯ 21 | # pylint: disable=wrong-import-order 22 | from discord.ext import commands # isort: skip 23 | 24 | 25 | STANDARD_COMMANDS = (RestartCommand, SyncCommand, UpdateCommand) 26 | 27 | VersionInfo = namedtuple("VersionInfo", "major minor micro release_level") 28 | version_info = VersionInfo(major=3, minor=1, micro=0, release_level="stable") 29 | 30 | __version__ = "v{}.{}.{}-{}".format( 31 | version_info.major, 32 | version_info.minor, 33 | version_info.micro, 34 | version_info.release_level, 35 | ).replace("\n", "") 36 | 37 | 38 | class Commands: 39 | def __init__(self, bot: Tuxbot) -> None: 40 | for command in STANDARD_COMMANDS: 41 | bot.collection.add_module("Admin", command(bot=bot)) 42 | 43 | 44 | class Admin(ModuleABC, Commands): 45 | """Set of owner only commands.""" 46 | 47 | def __init__(self, bot: Tuxbot) -> None: 48 | self.bot = bot 49 | 50 | super().__init__(bot=self.bot) 51 | 52 | # ========================================================================= 53 | 54 | async def cog_check( # type: ignore[override] 55 | self, ctx: commands.Context[TuxbotABC] 56 | ) -> bool: 57 | return bool(await self.bot.is_owner(ctx.author)) 58 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Avatar/ui/ViewController.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | import discord 4 | from discord.ext import commands 5 | 6 | from tuxbot.abc.TuxbotABC import TuxbotABC 7 | 8 | from .panels import ViewPanel 9 | 10 | 11 | if typing.TYPE_CHECKING: 12 | Author = typing.Union[discord.User, discord.Member] 13 | 14 | 15 | class ViewController(discord.ui.View): 16 | sent_message = None 17 | 18 | def __init__( 19 | self, 20 | ctx: commands.Context[TuxbotABC], 21 | data: "Author", 22 | ): 23 | super().__init__(timeout=60) 24 | 25 | self.data: Author = data 26 | 27 | self.ctx = ctx 28 | 29 | panel = ViewPanel.buttons 30 | 31 | for x, row in enumerate(panel): 32 | for button in row: 33 | self.add_item(button(row=x, controller=self)) 34 | 35 | # ========================================================================= 36 | # ========================================================================= 37 | 38 | async def on_timeout(self) -> None: 39 | """Remove buttons after timeout""" 40 | 41 | self.clear_items() 42 | 43 | await self.send() 44 | 45 | # ========================================================================= 46 | 47 | async def send(self) -> None: 48 | """Send selected embed""" 49 | 50 | embed = discord.Embed( 51 | title=f"Avatar of {self.data}", 52 | color=self.ctx.bot.utils.colors.EMBED_BORDER, 53 | ) 54 | embed.set_image(url=self.data.display_avatar.url) 55 | 56 | if self.sent_message is None: 57 | self.sent_message = await self.ctx.send(embed=embed, view=self) 58 | else: 59 | self.sent_message = await self.sent_message.edit( 60 | content="", embed=embed, view=self 61 | ) 62 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Dev 3 | ~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for developers. 6 | """ 7 | from collections import namedtuple 8 | 9 | from tuxbot.abc.ModuleABC import ModuleABC 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | from .commands.exceptions import DevException 14 | from .commands.HTTP.command import HTTPCommand 15 | 16 | 17 | # Note: for some reasons, this import must be done after tuxbot.* imports. 18 | # If it isn't, commands is bind on tuxbot.cogs.Dev.commands ¯\_(ツ)_/¯ 19 | # pylint: disable=wrong-import-order 20 | from discord.ext import commands # isort: skip 21 | 22 | STANDARD_COMMANDS = (HTTPCommand,) 23 | 24 | VersionInfo = namedtuple("VersionInfo", "major minor micro release_level") 25 | version_info = VersionInfo(major=2, minor=1, micro=0, release_level="stable") 26 | 27 | __version__ = "v{}.{}.{}-{}".format( 28 | version_info.major, 29 | version_info.minor, 30 | version_info.micro, 31 | version_info.release_level, 32 | ).replace("\n", "") 33 | 34 | 35 | class Commands: 36 | def __init__(self, bot: Tuxbot) -> None: 37 | for command in STANDARD_COMMANDS: 38 | bot.collection.add_module("Dev", command(bot=bot)) 39 | 40 | 41 | class Dev(ModuleABC, Commands): 42 | """Set of useful commands for developers.""" 43 | 44 | def __init__(self, bot: Tuxbot) -> None: 45 | self.bot = bot 46 | 47 | super().__init__(bot=self.bot) 48 | 49 | # ========================================================================= 50 | 51 | @commands.Cog.listener() 52 | async def on_command_error( 53 | self, ctx: commands.Context[TuxbotABC], error: Exception 54 | ) -> None: 55 | """Send errors raised by commands""" 56 | 57 | if isinstance(error, DevException): 58 | await ctx.send(str(error)) 59 | -------------------------------------------------------------------------------- /docker/production/postgres/maintenance/restore: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ### Restore database from a backup. 5 | ### 6 | ### Parameters: 7 | ### <1> filename of an existing backup. 8 | ### 9 | ### Usage: 10 | ### $ docker-docker -f .yml (exec |run --rm) postgres restore <1> 11 | 12 | 13 | set -o errexit 14 | set -o pipefail 15 | set -o nounset 16 | 17 | 18 | working_dir="$(dirname ${0})" 19 | source "${working_dir}/_sourced/constants.sh" 20 | source "${working_dir}/_sourced/messages.sh" 21 | 22 | 23 | if [[ -z ${1+x} ]]; then 24 | message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again." 25 | exit 1 26 | fi 27 | backup_filename="${BACKUP_DIR_PATH}/${1}" 28 | if [[ ! -f "${backup_filename}" ]]; then 29 | message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again." 30 | exit 1 31 | fi 32 | 33 | message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..." 34 | 35 | if [[ "${POSTGRES_USER}" == "postgres" ]]; then 36 | message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again." 37 | exit 1 38 | fi 39 | 40 | export PGHOST="${POSTGRES_HOST}" 41 | export PGPORT="${POSTGRES_PORT}" 42 | export PGUSER="${POSTGRES_USER}" 43 | export PGPASSWORD="${POSTGRES_PASSWORD}" 44 | export PGDATABASE="${POSTGRES_DB}" 45 | 46 | message_info "Dropping the database..." 47 | dropdb "${PGDATABASE}" 48 | 49 | message_info "Creating a new database..." 50 | createdb --owner="${POSTGRES_USER}" 51 | 52 | message_info "Applying the backup to the new database..." 53 | gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}" 54 | 55 | message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup." 56 | -------------------------------------------------------------------------------- /tuxbot/cogs/Linux/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Linux 3 | ~~~~~~~~~~~~~~~~~~ 4 | 5 | Set of useful commands for GNU/Linux users. 6 | """ 7 | from collections import namedtuple 8 | 9 | from tuxbot.abc.ModuleABC import ModuleABC 10 | from tuxbot.abc.TuxbotABC import TuxbotABC 11 | from tuxbot.core.Tuxbot import Tuxbot 12 | 13 | from .commands.CNF.command import CNFCommand 14 | from .commands.exceptions import LinuxException 15 | 16 | 17 | # Note: for some reasons, this import must be done after tuxbot.* imports. 18 | # If it isn't, commands is bind on tuxbot.cogs.Linux.commands ¯\_(ツ)_/¯ 19 | # pylint: disable=wrong-import-order 20 | from discord.ext import commands # isort: skip 21 | 22 | STANDARD_COMMANDS = (CNFCommand,) 23 | 24 | VersionInfo = namedtuple("VersionInfo", "major minor micro release_level") 25 | version_info = VersionInfo(major=2, minor=1, micro=0, release_level="stable") 26 | 27 | __version__ = "v{}.{}.{}-{}".format( 28 | version_info.major, 29 | version_info.minor, 30 | version_info.micro, 31 | version_info.release_level, 32 | ).replace("\n", "") 33 | 34 | 35 | class Commands: 36 | def __init__(self, bot: Tuxbot) -> None: 37 | for command in STANDARD_COMMANDS: 38 | bot.collection.add_module("Linux", command(bot=bot)) 39 | 40 | 41 | class Linux(ModuleABC, Commands): 42 | """Set of useful commands for GNU/Linux users.""" 43 | 44 | def __init__(self, bot: Tuxbot) -> None: 45 | self.bot = bot 46 | 47 | super().__init__(bot=self.bot) 48 | 49 | # ========================================================================= 50 | 51 | @commands.Cog.listener() 52 | async def on_command_error( 53 | self, ctx: commands.Context[TuxbotABC], error: Exception 54 | ) -> None: 55 | """Send errors raised by commands""" 56 | 57 | if isinstance(error, LinuxException): 58 | await ctx.send(str(error)) 59 | -------------------------------------------------------------------------------- /tuxbot/start.py: -------------------------------------------------------------------------------- 1 | """ 2 | Starter file 3 | """ 4 | import asyncio 5 | import os 6 | import traceback 7 | from distutils.util import strtobool 8 | 9 | from ddtrace.profiling.profiler import Profiler 10 | 11 | from tuxbot.core.config import config 12 | from tuxbot.core.logger import logger 13 | from tuxbot.core.Tuxbot import Tuxbot 14 | 15 | 16 | env = os.getenv("PYTHON_ENV", "production") 17 | 18 | 19 | async def run_bot(tuxbot: Tuxbot) -> None: 20 | """Run the instance 21 | 22 | Parameters 23 | ---------- 24 | tuxbot: :class:`Tuxbot` 25 | Tuxbot instance 26 | """ 27 | try: 28 | if env != "development" and strtobool(os.getenv("DD_ACTIVE", "false")): 29 | Profiler().start() # type: ignore 30 | 31 | await tuxbot.launch() 32 | except Exception as e: 33 | if env == "development": 34 | traceback.print_exc() 35 | 36 | Tuxbot.crash_report(tuxbot, e) 37 | 38 | 39 | def start() -> None: 40 | """Start function""" 41 | with open("tuxbot/misc/logo.txt", encoding="UTF-8") as f: 42 | logo = f.read() 43 | 44 | print(logo) 45 | logger.info("[C%s] Process %d online.", config.CLUSTER_ID, os.getpid()) 46 | 47 | if env == "development": 48 | from rich.traceback import install 49 | 50 | install(show_locals=True) 51 | 52 | tuxbot = None 53 | 54 | loop = asyncio.new_event_loop() 55 | asyncio.set_event_loop(loop) 56 | 57 | try: 58 | tuxbot = Tuxbot() 59 | loop.run_until_complete(run_bot(tuxbot)) 60 | except KeyboardInterrupt: 61 | if tuxbot is not None: 62 | loop.run_until_complete(tuxbot.shutdown()) 63 | except Exception as e: 64 | if env == "development": 65 | traceback.print_exc() 66 | 67 | if tuxbot: 68 | Tuxbot.crash_report(tuxbot, e) 69 | 70 | 71 | if __name__ == "__main__": 72 | start() 73 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Dog/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Dog.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Get a random picture of dog 6 | """ 7 | import asyncio 8 | import typing 9 | 10 | import aiohttp 11 | import discord 12 | from discord.ext import commands 13 | 14 | from tuxbot.abc.TuxbotABC import TuxbotABC 15 | from tuxbot.core.Tuxbot import Tuxbot 16 | 17 | from ..exceptions import APIException 18 | 19 | 20 | class DogCommand(commands.Cog): 21 | """Random dog picture""" 22 | 23 | def __init__(self, bot: Tuxbot) -> None: 24 | self.bot = bot 25 | 26 | # ========================================================================= 27 | # ========================================================================= 28 | 29 | async def __get_dog(self) -> dict[str, typing.Any]: 30 | try: 31 | async with aiohttp.ClientSession() as cs, cs.get( 32 | "https://dog.ceo/api/breeds/image/random" 33 | ) as s: 34 | if isinstance(res := await s.json(), dict): 35 | return res 36 | 37 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 38 | pass 39 | 40 | raise APIException("Something went wrong ...") 41 | 42 | # ========================================================================= 43 | # ========================================================================= 44 | 45 | @commands.command(name="dog", aliases=["randomdog"]) 46 | async def _dog(self, ctx: commands.Context[TuxbotABC]) -> None: 47 | dog = await self.__get_dog() 48 | 49 | e = discord.Embed( 50 | title="Here's your dog", 51 | color=self.bot.utils.colors.EMBED_BORDER, 52 | ) 53 | 54 | e.set_image(url=dog["message"]) 55 | e.set_footer(text="Powered by dog.ceo") 56 | 57 | await ctx.send(embed=e) 58 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Duck/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Duck.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Get a random picture of duck 6 | """ 7 | import asyncio 8 | import typing 9 | 10 | import aiohttp 11 | import discord 12 | from discord.ext import commands 13 | 14 | from tuxbot.abc.TuxbotABC import TuxbotABC 15 | from tuxbot.core.Tuxbot import Tuxbot 16 | 17 | from ..exceptions import APIException 18 | 19 | 20 | class DuckCommand(commands.Cog): 21 | """Random duck picture""" 22 | 23 | def __init__(self, bot: Tuxbot) -> None: 24 | self.bot = bot 25 | 26 | # ========================================================================= 27 | # ========================================================================= 28 | 29 | @staticmethod 30 | async def __get_duck() -> dict[str, typing.Any]: 31 | try: 32 | async with aiohttp.ClientSession() as cs, cs.get( 33 | "https://random-d.uk/api/v2/random" 34 | ) as s: 35 | if isinstance(res := await s.json(), dict): 36 | return res 37 | 38 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 39 | pass 40 | 41 | raise APIException("Something went wrong ...") 42 | 43 | # ========================================================================= 44 | # ========================================================================= 45 | 46 | @commands.command(name="duck", aliases=["randomduck"]) 47 | async def _duck(self, ctx: commands.Context[TuxbotABC]) -> None: 48 | duck = await self.__get_duck() 49 | 50 | e = discord.Embed( 51 | title="Here's your duck", 52 | color=self.bot.utils.colors.EMBED_BORDER, 53 | ) 54 | 55 | e.set_image(url=duck["url"]) 56 | e.set_footer(text=duck["message"]) 57 | 58 | await ctx.send(embed=e) 59 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Koala/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Koala.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Get a random picture of koala 6 | """ 7 | import asyncio 8 | import typing 9 | 10 | import aiohttp 11 | import discord 12 | from discord.ext import commands 13 | 14 | from tuxbot.abc.TuxbotABC import TuxbotABC 15 | from tuxbot.core.Tuxbot import Tuxbot 16 | 17 | from ..exceptions import APIException 18 | 19 | 20 | class KoalaCommand(commands.Cog): 21 | """Random koala picture""" 22 | 23 | def __init__(self, bot: Tuxbot) -> None: 24 | self.bot = bot 25 | 26 | # ========================================================================= 27 | # ========================================================================= 28 | 29 | @staticmethod 30 | async def __get_koala() -> dict[str, typing.Any]: 31 | try: 32 | async with aiohttp.ClientSession() as cs, cs.get( 33 | "https://some-random-api.ml/animal/koala" 34 | ) as s: 35 | if isinstance(res := await s.json(), dict): 36 | return res 37 | 38 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 39 | pass 40 | 41 | raise APIException("Something went wrong ...") 42 | 43 | # ========================================================================= 44 | # ========================================================================= 45 | 46 | @commands.command(name="koala", aliases=["randomkoala"]) 47 | async def _koala(self, ctx: commands.Context[TuxbotABC]) -> None: 48 | koala = await self.__get_koala() 49 | 50 | e = discord.Embed( 51 | title="Here's your koala", 52 | color=self.bot.utils.colors.EMBED_BORDER, 53 | ) 54 | 55 | e.set_image(url=koala["image"]) 56 | e.set_footer(text="Powered by some-random-api.ml") 57 | 58 | await ctx.send(embed=e) 59 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Panda/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Panda.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Get a random picture of panda 6 | """ 7 | import asyncio 8 | import typing 9 | 10 | import aiohttp 11 | import discord 12 | from discord.ext import commands 13 | 14 | from tuxbot.abc.TuxbotABC import TuxbotABC 15 | from tuxbot.core.Tuxbot import Tuxbot 16 | 17 | from ..exceptions import APIException 18 | 19 | 20 | class PandaCommand(commands.Cog): 21 | """Random panda picture""" 22 | 23 | def __init__(self, bot: Tuxbot) -> None: 24 | self.bot = bot 25 | 26 | # ========================================================================= 27 | # ========================================================================= 28 | 29 | @staticmethod 30 | async def __get_panda() -> dict[str, typing.Any]: 31 | try: 32 | async with aiohttp.ClientSession() as cs, cs.get( 33 | "https://some-random-api.ml/animal/panda" 34 | ) as s: 35 | if isinstance(res := await s.json(), dict): 36 | return res 37 | 38 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 39 | pass 40 | 41 | raise APIException("Something went wrong ...") 42 | 43 | # ========================================================================= 44 | # ========================================================================= 45 | 46 | @commands.command(name="panda", aliases=["randompanda"]) 47 | async def _panda(self, ctx: commands.Context[TuxbotABC]) -> None: 48 | panda = await self.__get_panda() 49 | 50 | e = discord.Embed( 51 | title="Here's your panda", 52 | color=self.bot.utils.colors.EMBED_BORDER, 53 | ) 54 | 55 | e.set_image(url=panda["image"]) 56 | e.set_footer(text="Powered by some-random-api.ml") 57 | 58 | await ctx.send(embed=e) 59 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/RedPanda/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.RedPanda.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Get a random picture of red panda 6 | """ 7 | import asyncio 8 | import typing 9 | 10 | import aiohttp 11 | import discord 12 | from discord.ext import commands 13 | 14 | from tuxbot.abc.TuxbotABC import TuxbotABC 15 | from tuxbot.core.Tuxbot import Tuxbot 16 | 17 | from ..exceptions import APIException 18 | 19 | 20 | class RedPandaCommand(commands.Cog): 21 | """Random red panda picture""" 22 | 23 | def __init__(self, bot: Tuxbot) -> None: 24 | self.bot = bot 25 | 26 | # ========================================================================= 27 | # ========================================================================= 28 | 29 | @staticmethod 30 | async def __get_redpanda() -> dict[str, typing.Any]: 31 | try: 32 | async with aiohttp.ClientSession() as cs, cs.get( 33 | "https://some-random-api.ml/animal/red_panda" 34 | ) as s: 35 | if isinstance(res := await s.json(), dict): 36 | return res 37 | 38 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 39 | pass 40 | 41 | raise APIException("Something went wrong ...") 42 | 43 | # ========================================================================= 44 | # ========================================================================= 45 | 46 | @commands.command(name="redpanda", aliases=["randomredpanda"]) 47 | async def _redpanda(self, ctx: commands.Context[TuxbotABC]) -> None: 48 | redpanda = await self.__get_redpanda() 49 | 50 | e = discord.Embed( 51 | title="Here's your red panda", 52 | color=self.bot.utils.colors.EMBED_BORDER, 53 | ) 54 | 55 | e.set_image(url=redpanda["image"]) 56 | e.set_footer(text="Powered by some-random-api.ml") 57 | 58 | await ctx.send(embed=e) 59 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 79 3 | target-version = ['py310'] 4 | include = '\.pyi?$' 5 | 6 | [tool.poetry] 7 | name = "tuxbot" 8 | version = "4.0.0" 9 | description = "A discord bot made for GnousEU's guild and OpenSource" 10 | authors = ["Romain J. "] 11 | license = "AGPL3" 12 | 13 | [tool.poetry.scripts] 14 | start = "tuxbot.start:start" 15 | test-database="scripts.test_database:test" 16 | migrate-to-envs="scripts.migrate_yaml_to_envs:main" 17 | merge-dotenvs-to-dotenv="scripts.merge_dotenvs_to_dotenv:main" 18 | 19 | [tool.poetry.dependencies] 20 | python = "^3.10" 21 | aiofiles = "^23.1.0" 22 | aioredis = "^2.0.1" 23 | asyncpg = "^0.27.0" 24 | "discord.py" = {git = "https://github.com/Rapptz/discord.py"} 25 | discord-ext-menus = "^1.1" 26 | jishaku = {git = "https://github.com/Gorialis/jishaku"} 27 | PyYAML = "^6.0" 28 | rich = "^12.6.0" 29 | tortoise-orm = "^0.19.2" 30 | websockets = "^10.4" 31 | beautifulsoup4 = "^4.11.1" 32 | graphviz = "^0.20.1" 33 | humanize = "^4.4.0" 34 | ipinfo = "^4.4.2" 35 | ipwhois = "^1.2.0" 36 | Pillow = "^9.4.0" 37 | psutil = "^5.9.4" 38 | pydig = "^0.4.0" 39 | sympy = "^1.11.1" 40 | wolframalpha = "^5.0.0" 41 | datadog = "^0.44.0" 42 | ddtrace = "^1.9.3" 43 | lxml = "^4.9.2" 44 | python-json-logger = "^2.0.7" 45 | sentry-sdk = "^1.18.0" 46 | django-environ = "^0.9.0" 47 | psycopg2 = "^2.9.5" 48 | 49 | [tool.poetry.dev-dependencies] 50 | flake8 = "^6.0.0" 51 | flake8-isort = "^6.0.0" 52 | coverage = "^6.5.0" 53 | black = "^22.8.0" 54 | pre-commit = "^2.20.0" 55 | types-aiofiles = "^22.1.0" 56 | types_PyYAML = "^6.0.12" 57 | bpython = "^0.24" 58 | types-PyYAML = "^6.0.11" 59 | mypy = "^0.991" 60 | types-beautifulsoup4 = "^4.11.6" 61 | types-psutil = "^5.9.5.11" 62 | types-boto = "^2.49.18.3" 63 | types-protobuf = "^3.20.4" 64 | types-pytz = "^2022.7.1" 65 | types-requests = "^2.28.11" 66 | types-setuptools = "^65.6.0.2" 67 | types-six = "^1.16.21" 68 | types-tabulate = "^0.9.0" 69 | 70 | [build-system] 71 | requires = ["poetry-core==1.0.0"] 72 | build-backend = "poetry.core.masonry.api" 73 | -------------------------------------------------------------------------------- /tuxbot/cogs/Utils/commands/Source/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Utils.commands.Source.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Gives tuxbot sources 6 | """ 7 | import inspect 8 | import os 9 | 10 | from discord.ext import commands 11 | 12 | from tuxbot.abc.TuxbotABC import TuxbotABC 13 | from tuxbot.core.Tuxbot import Tuxbot 14 | 15 | 16 | class SourceCommand(commands.Cog): 17 | """Gives tuxbot sources""" 18 | 19 | def __init__(self, bot: Tuxbot) -> None: 20 | self.bot = bot 21 | 22 | self.github_url = "https://github.com/Rom1-J/tuxbot-bot/" 23 | 24 | # ========================================================================= 25 | # ========================================================================= 26 | 27 | @commands.command(name="source", aliases=["sources"]) 28 | async def _source( 29 | self, ctx: commands.Context[TuxbotABC], *, name: str | None = None 30 | ) -> None: 31 | if not name: 32 | await ctx.send(self.github_url) 33 | return 34 | 35 | cmd = self.bot.get_command(name) 36 | 37 | if cmd: 38 | src = cmd.callback.__code__ 39 | rpath = src.co_filename 40 | else: 41 | await ctx.send(f"Unable to find `{name}`") 42 | return 43 | 44 | try: 45 | lines, start_line = inspect.getsourcelines(src) 46 | except OSError: 47 | await ctx.send(f"Unable to fetch lines for `{name}`") 48 | return 49 | 50 | if "venv" in rpath: 51 | location = ( 52 | os.path.relpath(rpath) 53 | .replace("\\", "/") 54 | .split("site-packages/")[-1] 55 | .lstrip("/") 56 | ) 57 | else: 58 | location = rpath.split("tuxbot_bot")[-1].lstrip("/") 59 | 60 | final_url = ( 61 | f"{self.github_url}/tree/master/{location}#L{start_line}" 62 | f"-L{start_line + len(lines) - 1}" 63 | ) 64 | 65 | await ctx.send(final_url) 66 | -------------------------------------------------------------------------------- /docker/local/bot/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PYTHON_VERSION=3.10.7-slim-bullseye 2 | 3 | # define an alias for the specfic python version used in this file. 4 | FROM python:${PYTHON_VERSION} as python 5 | 6 | # Python build stage 7 | FROM python as python-build-stage 8 | 9 | ARG BUILD_ENVIRONMENT=local 10 | 11 | # Python 'run' stage 12 | FROM python as python-run-stage 13 | 14 | ARG BUILD_ENVIRONMENT=local 15 | ARG APP_HOME=/app 16 | 17 | ENV PYTHONUNBUFFERED=1 \ 18 | PYTHONDONTWRITEBYTECODE=1 \ 19 | \ 20 | PIP_NO_CACHE_DIR=off \ 21 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 22 | PIP_DEFAULT_TIMEOUT=100 \ 23 | \ 24 | # poetry 25 | POETRY_VERSION=1.2.2 \ 26 | POETRY_HOME="/opt/poetry" \ 27 | POETRY_VIRTUALENVS_IN_PROJECT=false \ 28 | POETRY_NO_INTERACTION=1 29 | 30 | ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" 31 | 32 | # Install essentials 33 | RUN apt update \ 34 | && apt install --no-install-recommends -y \ 35 | git \ 36 | curl \ 37 | libpq-dev \ 38 | build-essential 39 | RUN apt install --no-install-recommends -y fonts-dejavu-core fonts-dejavu-extra 40 | 41 | COPY ./docker/scripts/get-poetry.py /tmp/get-poetry.py 42 | RUN python /tmp/get-poetry.py 43 | 44 | WORKDIR ${APP_HOME} 45 | COPY . ${APP_HOME} 46 | 47 | RUN poetry config virtualenvs.create false 48 | RUN poetry install --only main 49 | 50 | # Cleanup installation 51 | RUN apt remove -y build-essential 52 | RUN apt autoremove -y 53 | RUN rm -rf /var/lib/apt/lists/* 54 | RUN rm /tmp/get-poetry.py 55 | 56 | COPY ./docker/local/bot/entrypoint /entrypoint 57 | RUN sed -i 's/\r$//g' /entrypoint 58 | RUN chmod +x /entrypoint 59 | 60 | COPY ./docker/local/bot/start /start 61 | RUN sed -i 's/\r$//g' /start 62 | RUN chmod +x /start 63 | 64 | ARG user=tuxbot 65 | ARG group=tuxbot 66 | ARG uid=1000 67 | ARG gid=1000 68 | RUN groupadd -g ${gid} ${group} 69 | RUN useradd -u ${uid} -g ${group} -s /bin/nologin -m ${user} 70 | 71 | RUN chown ${user}:${group} -R ${APP_HOME} 72 | USER ${user}:${group} 73 | 74 | RUN poetry config virtualenvs.create false 75 | 76 | ENTRYPOINT ["/entrypoint"] 77 | -------------------------------------------------------------------------------- /tuxbot/cogs/Auto/listeners/RawReactionAdd/workers/AutoPin.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Auto.listeners.RawReactionAdd.workers.AutoPin 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | """ 6 | import discord 7 | 8 | from tuxbot.core.Tuxbot import Tuxbot 9 | 10 | from ....commands.AutoPin.models.AutoPin import AutoPinModel 11 | 12 | 13 | class AutoPin: 14 | """Automatically send process reaction add for pin""" 15 | 16 | def __init__(self, bot: Tuxbot) -> None: 17 | self.bot = bot 18 | 19 | async def process(self, payload: discord.RawReactionActionEvent) -> None: 20 | """Process worker""" 21 | 22 | if ( 23 | not payload.guild_id 24 | or payload.user_id == self.bot.client_options.get("id") 25 | ) or payload.emoji != discord.PartialEmoji(name="📌"): 26 | return 27 | 28 | if not self.bot.cached_config[payload.guild_id].get("AutoPin"): 29 | if model := await AutoPinModel.get_or_create( 30 | guild_id=payload.guild_id 31 | ): 32 | self.bot.cached_config[payload.guild_id]["AutoPin"] = { 33 | "activated": model[0].activated, 34 | "threshold": model[0].threshold, 35 | } 36 | 37 | threshold = self.bot.cached_config[payload.guild_id]["AutoPin"].get( 38 | "threshold" 39 | ) 40 | 41 | if self.bot.cached_config[payload.guild_id]["AutoPin"]["activated"]: 42 | if channel := await self.bot.fetch_channel_or_none( 43 | payload.channel_id 44 | ): 45 | if isinstance(channel, discord.abc.Messageable) and ( 46 | message := await self.bot.fetch_message_or_none( 47 | channel, payload.message_id 48 | ) 49 | ): 50 | if ( 51 | pins := discord.utils.get(message.reactions, emoji="📌") 52 | ) and pins.count >= threshold: 53 | await message.pin(reason="Auto pinned via reactions") 54 | -------------------------------------------------------------------------------- /docker/production/bot/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PYTHON_VERSION=3.10.7-slim-bullseye 2 | 3 | # define an alias for the specfic python version used in this file. 4 | FROM python:${PYTHON_VERSION} as python 5 | 6 | # Python build stage 7 | FROM python as python-build-stage 8 | 9 | ARG BUILD_ENVIRONMENT=local 10 | 11 | # Python 'run' stage 12 | FROM python as python-run-stage 13 | 14 | ARG BUILD_ENVIRONMENT=local 15 | ARG APP_HOME=/app 16 | 17 | ENV PYTHONUNBUFFERED=1 \ 18 | PYTHONDONTWRITEBYTECODE=1 \ 19 | \ 20 | PIP_NO_CACHE_DIR=off \ 21 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 22 | PIP_DEFAULT_TIMEOUT=100 \ 23 | \ 24 | # poetry 25 | POETRY_VERSION=1.2.2 \ 26 | POETRY_HOME="/opt/poetry" \ 27 | POETRY_VIRTUALENVS_IN_PROJECT=false \ 28 | POETRY_NO_INTERACTION=1 29 | 30 | ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" 31 | 32 | # Install essentials 33 | RUN apt update \ 34 | && apt install --no-install-recommends -y \ 35 | git \ 36 | curl \ 37 | libpq-dev \ 38 | build-essential 39 | RUN apt install --no-install-recommends -y fonts-dejavu-core fonts-dejavu-extra 40 | 41 | COPY ./docker/scripts/get-poetry.py /tmp/get-poetry.py 42 | RUN python /tmp/get-poetry.py 43 | 44 | WORKDIR ${APP_HOME} 45 | COPY . ${APP_HOME} 46 | 47 | RUN poetry config virtualenvs.create false 48 | RUN poetry install --only main 49 | 50 | # Cleanup installation 51 | RUN apt remove -y build-essential 52 | RUN apt autoremove -y 53 | RUN rm -rf /var/lib/apt/lists/* 54 | RUN rm /tmp/get-poetry.py 55 | 56 | COPY ./docker/production/bot/entrypoint /entrypoint 57 | RUN sed -i 's/\r$//g' /entrypoint 58 | RUN chmod +x /entrypoint 59 | 60 | COPY ./docker/local/bot/start /start 61 | RUN sed -i 's/\r$//g' /start 62 | RUN chmod +x /start 63 | 64 | ARG user=tuxbot 65 | ARG group=tuxbot 66 | ARG uid=1000 67 | ARG gid=1000 68 | RUN groupadd -g ${gid} ${group} 69 | RUN useradd -u ${uid} -g ${group} -s /bin/nologin -m ${user} 70 | 71 | RUN chown ${user}:${group} -R ${APP_HOME} 72 | USER ${user}:${group} 73 | 74 | RUN poetry config virtualenvs.create false 75 | 76 | ENTRYPOINT ["/entrypoint"] 77 | -------------------------------------------------------------------------------- /tuxbot/cogs/Dev/commands/HTTP/HTTPs/FiveXX.py: -------------------------------------------------------------------------------- 1 | from .HttpCode import HttpCode 2 | 3 | 4 | __all__ = ( 5 | "Http500", 6 | "Http501", 7 | "Http502", 8 | "Http503", 9 | "Http504", 10 | "Http505", 11 | "Http506", 12 | "Http507", 13 | "Http508", 14 | "Http509", 15 | "Http510", 16 | "Http511", 17 | ) 18 | 19 | 20 | class Http500(HttpCode): 21 | value = 500 22 | name = "Internal Server Error" 23 | mdn = True 24 | cat = True 25 | 26 | 27 | class Http501(HttpCode): 28 | value = 501 29 | name = "Not Implemented" 30 | mdn = True 31 | cat = True 32 | 33 | 34 | class Http502(HttpCode): 35 | value = 502 36 | name = "Bad Gateway" 37 | mdn = True 38 | cat = True 39 | 40 | 41 | class Http503(HttpCode): 42 | value = 503 43 | name = "Service Unavailable" 44 | mdn = True 45 | cat = True 46 | 47 | 48 | class Http504(HttpCode): 49 | value = 504 50 | name = "Gateway Timeout" 51 | mdn = True 52 | cat = True 53 | 54 | 55 | class Http505(HttpCode): 56 | value = 505 57 | name = "HTTP Version Not Supported" 58 | mdn = True 59 | cat = False 60 | 61 | 62 | class Http506(HttpCode): 63 | value = 506 64 | name = "Variant Also Negotiates" 65 | mdn = True 66 | cat = True 67 | 68 | 69 | class Http507(HttpCode): 70 | value = 507 71 | name = "Insufficient Storage (WebDAV)" 72 | mdn = True 73 | cat = True 74 | 75 | 76 | class Http508(HttpCode): 77 | value = 508 78 | name = "Loop Detected (WebDAV)" 79 | mdn = True 80 | cat = True 81 | 82 | 83 | class Http509(HttpCode): 84 | value = 509 85 | name = "Bandwidth Limit Exceeded" 86 | mdn = False 87 | cat = True 88 | 89 | 90 | class Http510(HttpCode): 91 | value = 510 92 | name = "Not Extended" 93 | mdn = True 94 | cat = True 95 | 96 | 97 | class Http511(HttpCode): 98 | value = 511 99 | name = "Network Authentication Required" 100 | mdn = True 101 | cat = True 102 | -------------------------------------------------------------------------------- /tuxbot/cogs/Random/commands/Cat/command.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Random.commands.Cat.command 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Get a random picture of cat 6 | """ 7 | import asyncio 8 | import random 9 | import typing 10 | 11 | import aiohttp 12 | import discord 13 | from discord.ext import commands 14 | 15 | from tuxbot.abc.TuxbotABC import TuxbotABC 16 | from tuxbot.core.Tuxbot import Tuxbot 17 | 18 | from ..exceptions import APIException 19 | 20 | 21 | class CatCommand(commands.Cog): 22 | """Random cat picture""" 23 | 24 | def __init__(self, bot: Tuxbot) -> None: 25 | self.bot = bot 26 | 27 | self.cataas_url = "https://cataas.com" 28 | 29 | # ========================================================================= 30 | # ========================================================================= 31 | 32 | async def __get_cat(self) -> dict[str, typing.Any]: 33 | try: 34 | endpoint = random.choices( 35 | ["cat", "cat/gif"], weights=[0.8, 0.2], k=1 36 | )[0] 37 | 38 | async with aiohttp.ClientSession() as cs, cs.get( 39 | f"{self.cataas_url}/{endpoint}?json=true" 40 | ) as s: 41 | if isinstance(res := await s.json(), dict): 42 | return res 43 | 44 | except (aiohttp.ClientError, asyncio.exceptions.TimeoutError): 45 | pass 46 | 47 | raise APIException("Something went wrong ...") 48 | 49 | # ========================================================================= 50 | # ========================================================================= 51 | 52 | @commands.command(name="cat", aliases=["randomcat"]) 53 | async def _cat(self, ctx: commands.Context[TuxbotABC]) -> None: 54 | cat = await self.__get_cat() 55 | 56 | e = discord.Embed( 57 | title="Here's your cat", 58 | color=self.bot.utils.colors.EMBED_BORDER, 59 | ) 60 | 61 | e.set_image(url=f"{self.cataas_url}/{cat['url']}") 62 | e.set_footer(text="Powered by cataas.com") 63 | 64 | await ctx.send(embed=e) 65 | -------------------------------------------------------------------------------- /tuxbot/cogs/Math/converters/ExprConverter.py: -------------------------------------------------------------------------------- 1 | """ 2 | tuxbot.cogs.Math.commands.Graph.converters.ExprConverter 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | Converter to parse user expr as sympy expr. 6 | """ 7 | import asyncio 8 | import typing 9 | 10 | from discord.ext import commands 11 | from sympy.parsing.sympy_parser import ( 12 | implicit_multiplication_application, 13 | parse_expr, 14 | standard_transformations, 15 | ) 16 | 17 | from tuxbot.abc.TuxbotABC import TuxbotABC 18 | 19 | 20 | abc_dict: dict[str, typing.Any] = {} 21 | functions_dict: dict[str, typing.Any] = {} 22 | core_dict: dict[str, typing.Any] = {} 23 | ConvertType = tuple[str, typing.Any] 24 | 25 | # pylint: disable=exec-used 26 | exec("from sympy.abc import *", abc_dict) 27 | exec("from sympy.functions import *", functions_dict) 28 | exec("from sympy.core import *", core_dict) 29 | 30 | global_dict = abc_dict | functions_dict | core_dict 31 | 32 | del global_dict["__builtins__"] 33 | 34 | 35 | class ExprConverter(commands.Converter[ConvertType]): 36 | """Ensure passed data is HTTP code.""" 37 | 38 | async def convert( # type: ignore[override] 39 | self, ctx: commands.Context[TuxbotABC], argument: str 40 | ) -> ConvertType: 41 | argument = argument.rstrip("`").lstrip("`") 42 | 43 | if "_" in argument: 44 | return argument, None 45 | 46 | def _parse_expr() -> typing.Any | None: 47 | try: 48 | return parse_expr( 49 | argument, 50 | transformations=( 51 | standard_transformations 52 | + (implicit_multiplication_application,) 53 | ), 54 | evaluate=False, 55 | global_dict=global_dict, 56 | ) 57 | except Exception: 58 | return None 59 | 60 | parsed_arg = await asyncio.get_running_loop().run_in_executor( 61 | None, _parse_expr 62 | ) 63 | 64 | if isinstance(parsed_arg, bool): 65 | return argument, None 66 | 67 | return argument, parsed_arg 68 | --------------------------------------------------------------------------------