├── 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 |
4 |
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 |
5 |
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 | / / / /_/ /> /_/ / /_/ / /_ | |/ /__ __/
5 | /_/ \__,_/_/|_/_.___/\____/\__/ |___/ /_/
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
5 |
6 |
7 |
8 |
9 |
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 |
5 |
6 |
7 |
8 |
9 |
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
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 |
20 |
21 |
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 |
--------------------------------------------------------------------------------