diff options
| -rw-r--r-- | .envrc | 4 | ||||
| -rw-r--r-- | .gitignore | 4 | ||||
| l---------[-rw-r--r--] | .pre-commit-config.yaml | 21 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | default.nix | 14 | ||||
| -rw-r--r-- | flake.lock | 153 | ||||
| -rw-r--r-- | flake.nix | 66 | ||||
| -rw-r--r-- | pyproject.toml | 302 | ||||
| -rw-r--r-- | teawie_bot/__init__.py (renamed from src/teawie_bot/__init__.py) | 0 | ||||
| -rw-r--r-- | teawie_bot/apis/guzzle.py (renamed from src/teawie_bot/apis/guzzle.py) | 0 | ||||
| -rw-r--r-- | teawie_bot/bot.py (renamed from src/teawie_bot/bot.py) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/__init__.py (renamed from src/teawie_bot/copypastas/__init__.py) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/dvd.txt (renamed from src/teawie_bot/copypastas/dvd.txt) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/egrill.txt (renamed from src/teawie_bot/copypastas/egrill.txt) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/happymeal.txt (renamed from src/teawie_bot/copypastas/happymeal.txt) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/ismah.txt (renamed from src/teawie_bot/copypastas/ismah.txt) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/navyseal.txt (renamed from src/teawie_bot/copypastas/navyseal.txt) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/sus.txt (renamed from src/teawie_bot/copypastas/sus.txt) | 0 | ||||
| -rw-r--r-- | teawie_bot/copypastas/ticktock.txt (renamed from src/teawie_bot/copypastas/ticktock.txt) | 0 | ||||
| -rw-r--r-- | teawie_bot/utils.py (renamed from src/teawie_bot/utils.py) | 0 |
20 files changed, 248 insertions, 318 deletions
@@ -0,0 +1,4 @@ +if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" +fi +use flake @@ -127,3 +127,7 @@ dmypy.json # Pyre type checker .pyre/ + +# nix stuff +.direnv/ +result diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e0c147..9ad99c0 100644..120000 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1 @@ -repos: -- repo: https://github.com/pycqa/isort - rev: 5.11.2 - hooks: - - id: isort - name: isort (python) - -- repo: local - hooks: - - id: pylint - name: pylint - entry: pylint - language: system - types: [python] - -- repo: https://github.com/pre-commit/mirrors-yapf - rev: "v0.32.0" # Use the sha / tag you want to point at - hooks: - - id: yapf - additional_dependencies: ["toml"] +/nix/store/sj4hklv5klfh4wk3pvc8fxd5242qp12q-pre-commit-config.json
\ No newline at end of file @@ -23,6 +23,6 @@ python -m venv .env source .env/bin/activate # for linux pip install flit -flit install --deps=develop --only-deps +flit install --only-deps pre-commit install ``` diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..c7d0c26 --- /dev/null +++ b/default.nix @@ -0,0 +1,14 @@ +( + import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + {src = ./.;} +) +.defaultNix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..19f48df --- /dev/null +++ b/flake.lock @@ -0,0 +1,153 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1676283394, + "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1677050843, + "narHash": "sha256-3fcFxn58eCtrXrVPeW/nAg6NR5wUERVEf8zOtjPDzuM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9e0eed654c705c7cafe192a8eba1610217f70544", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1673800717, + "narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1676879534, + "narHash": "sha256-HU4RXcwsAX1u7AUbGOBDxkYQkeODcn+HZjXqKa1y/hk=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "c9495f017f67a11e9c9909b032dc7762dfc853cf", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7ec30ab --- /dev/null +++ b/flake.nix @@ -0,0 +1,66 @@ +{ + description = "teawie moment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + pre-commit-hooks = { + url = "github:cachix/pre-commit-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + self, + nixpkgs, + pre-commit-hooks, + flake-compat, + flake-utils, + }: let + version = "0.0.1"; + supportedSystems = with flake-utils.lib.system; [x86_64-linux x86_64-darwin aarch64-linux aarch64-darwin]; + in + flake-utils.lib.eachSystem supportedSystems (system: let + pkgs = import nixpkgs {inherit system;}; + in { + packages = + { + teawiebot = with pkgs; + python39Packages.buildPythonPackage { + pname = "teawiebot"; + inherit version; + src = ./.; + format = "flit"; + propagatedBuildInputs = with pkgs.python39Packages; [hatchling discordpy requests]; + }; + } + // {default = self.packages.${system}.teawiebot;}; + + checks = { + pre-commit-check = pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + isort.enable = true; + pylint.enable = true; + yapf = { + enable = true; + name = "yapf"; + entry = "${pkgs.python39Packages.yapf}/bin/yapf -i"; + types = ["file" "python"]; + }; + }; + }; + }; + + devShells = with pkgs; { + default = mkShell { + packages = with pkgs.python39Packages; [python39 discordpy flit pylint requests toml yapf]; + inherit (self.checks.${system}.pre-commit-check) shellHook; + }; + }; + }); +} diff --git a/pyproject.toml b/pyproject.toml index 0d1d1fb..3567aa0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,8 @@ authors = [ readme = "README.md" requires-python = ">=3.8" dependencies = [ - "discord.py >=2.0.0", + "discord.py~=2.0", + "requests~=2.0", ] [project.optional-dependencies] @@ -33,302 +34,9 @@ teawiebot = "teawie_bot:main" [tool.yapf] use_tabs = true -[tool.pylint.main] -# Specify a score threshold under which the program will exit with error. -fail-under = 10 - -# Files or directories to be skipped. They should be base names, not paths. -ignore = ["CVS"] - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use, and will cap the count on Windows to -# avoid hangs. -jobs = 1 - -# Control the amount of potential inferred values when inferring a single object. -# This can help the performance when dealing with large functions or complex, -# nested conditions. -limit-inference-results = 100 - -# Pickle collected data for later comparisons. -persistent = true - -# Minimum Python version to use for version dependent checks. Will default to the -# version used to run pylint. -py-version = "3.11" - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode = true - -[tool.pylint.basic] -# Naming style matching correct argument names. -argument-naming-style = "snake_case" - -# Naming style matching correct attribute names. -attr-naming-style = "snake_case" - -# Bad variable names which should always be refused, separated by a comma. -bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"] - -# Naming style matching correct class attribute names. -class-attribute-naming-style = "any" - -# Naming style matching correct class constant names. -class-const-naming-style = "UPPER_CASE" - -# Naming style matching correct class names. -class-naming-style = "PascalCase" - -# Naming style matching correct constant names. -const-naming-style = "UPPER_CASE" - -# Minimum line length for functions/classes that require docstrings, shorter ones -# are exempt. -docstring-min-length = -1 - -# Naming style matching correct function names. -function-naming-style = "snake_case" - -# Good variable names which should always be accepted, separated by a comma. -good-names = ["i", "j", "k", "ex", "Run", "_"] - -# Naming style matching correct inline iteration names. -inlinevar-naming-style = "any" - -# Naming style matching correct method names. -method-naming-style = "snake_case" - -# Naming style matching correct module names. -module-naming-style = "snake_case" - -# Regular expression which should only match function or class names that do not -# require a docstring. -no-docstring-rgx = "^_" - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. These -# decorators are taken in consideration only for invalid-name. -property-classes = ["abc.abstractproperty"] - -# Naming style matching correct variable names. -variable-naming-style = "snake_case" - -[tool.pylint.classes] -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods = ["__init__", "__new__", "setUp", "__post_init__"] - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make"] - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg = ["cls"] - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg = ["cls"] - -[tool.pylint.design] -# Maximum number of arguments for function / method. -max-args = 5 - -# Maximum number of attributes for a class (see R0902). -max-attributes = 7 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr = 5 - -# Maximum number of branch for function / method body. -max-branches = 12 - -# Maximum number of locals for function / method body. -max-locals = 15 - -# Maximum number of parents for a class (see R0901). -max-parents = 7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods = 20 - -# Maximum number of return / yield for function / method body. -max-returns = 6 - -# Maximum number of statements in function / method body. -max-statements = 50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods = 2 - -[tool.pylint.exceptions] -# Exceptions that will emit a warning when caught. -overgeneral-exceptions = ["BaseException", "Exception"] - [tool.pylint.format] -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format = "LF" - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines = "^\\s*(# )?<?https?://\\S+>?$" - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren = 4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string = " " - -# Maximum number of characters on a single line. -max-line-length = 100 - -# Maximum number of lines in a module. -max-module-lines = 1000 - -[tool.pylint.imports] -# Force import order to recognize a module as part of a third party library. -known-third-party = ["enchant"] - -[tool.pylint.logging] -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style = "new" - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules = ["logging"] +indent-after-paren = 1 +indent-string = "\t" [tool.pylint."messages control"] -# Only show warnings with the listed confidence levels. Leave empty to show all. -# Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] - -# Disable the message, report, category or checker with the given id(s). You can -# either give multiple identifiers separated by comma (,) or put this option -# multiple times (only on the command line, not in the configuration file where -# it should appear only once). You can also use "--disable=all" to disable -# everything first and then re-enable specific checks. For example, if you want -# to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "missing-function-docstring", "missing-module-docstring"] - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where it -# should appear only once). See also the "--disable" option for examples. -enable = ["c-extension-no-member"] - -[tool.pylint.method_args] -# List of qualified names (i.e., library.method) which require a timeout -# parameter e.g. 'requests.api.get,requests.api.post' -timeout-methods = ["requests.api.delete", "requests.api.get", "requests.api.head", "requests.api.options", "requests.api.patch", "requests.api.post", "requests.api.put", "requests.api.request"] - -[tool.pylint.miscellaneous] -# List of note tags to take in consideration, separated by a comma. -notes = ["FIXME", "XXX", "TODO"] - -[tool.pylint.refactoring] -# Maximum number of nested blocks for function / method body -max-nested-blocks = 5 - -# Complete name of functions that never returns. When checking for inconsistent- -# return-statements if a never returning function is called then it will be -# considered as an explicit return statement and no message will be printed. -never-returning-functions = ["sys.exit", "argparse.parse_error"] - -[tool.pylint.reports] -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'fatal', 'error', 'warning', 'refactor', -# 'convention', and 'info' which contain the number of messages in each category, -# as well as 'statement' which is the total number of statements analyzed. This -# score is used by the global evaluation report (RP0004). -evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))" - -# Activate the evaluation score. -score = true - -[tool.pylint.similarities] -# Comments are removed from the similarity computation -ignore-comments = true - -# Docstrings are removed from the similarity computation -ignore-docstrings = true - -# Imports are removed from the similarity computation -ignore-imports = true - -# Signatures are removed from the similarity computation -ignore-signatures = true - -# Minimum lines number of a similarity. -min-similarity-lines = 4 - -[tool.pylint.spelling] -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions = 4 - -# List of comma separated words that should be considered directives if they -# appear at the beginning of a comment and should not be checked. -spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:" - -[tool.pylint.typecheck] -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators = ["contextlib.contextmanager"] - -# Tells whether missing members accessed in mixin class should be ignored. A -# class is considered mixin if its name matches the mixin-class-rgx option. -# Tells whether to warn about missing members when the owner of the attribute is -# inferred to be None. -ignore-none = true - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference can -# return multiple potential results while evaluating a Python object, but some -# branches might not be evaluated, which results in partial inference. In that -# case, it might be useful to still emit no-member and other checks for the rest -# of the inferred objects. -ignore-on-opaque-inference = true - -# List of symbolic message names to ignore for Mixin members. -ignored-checks-for-mixins = ["no-member", "not-async-context-manager", "not-context-manager", "attribute-defined-outside-init"] - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes = ["optparse.Values", "thread._local", "_thread._local", "argparse.Namespace"] - -# Show a hint with possible names when a member name was not found. The aspect of -# finding the hint is based on edit distance. -missing-member-hint = true - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance = 1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices = 1 - -# Regex pattern to define which classes are considered mixins. -mixin-class-rgx = ".*[Mm]ixin" - -[tool.pylint.variables] -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables = true - -# List of strings which can identify a callback function by name. A callback name -# must start or end with one of those strings. -callbacks = ["cb_", "_cb"] - -# A regular expression matching the name of dummy variables (i.e. expected to not -# be used). -dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_" - -# Argument names that match this expression will be ignored. -ignored-argument-names = "_.*|^ignored_|^unused_" - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules = ["six.moves", "past.builtins", "future.builtins", "builtins", "io"] +disable = ["missing-function-docstring", "missing-module-docstring"] diff --git a/src/teawie_bot/__init__.py b/teawie_bot/__init__.py index e3b5d8f..e3b5d8f 100644 --- a/src/teawie_bot/__init__.py +++ b/teawie_bot/__init__.py diff --git a/src/teawie_bot/apis/guzzle.py b/teawie_bot/apis/guzzle.py index 7599606..7599606 100644 --- a/src/teawie_bot/apis/guzzle.py +++ b/teawie_bot/apis/guzzle.py diff --git a/src/teawie_bot/bot.py b/teawie_bot/bot.py index 835783a..835783a 100644 --- a/src/teawie_bot/bot.py +++ b/teawie_bot/bot.py diff --git a/src/teawie_bot/copypastas/__init__.py b/teawie_bot/copypastas/__init__.py index e69de29..e69de29 100644 --- a/src/teawie_bot/copypastas/__init__.py +++ b/teawie_bot/copypastas/__init__.py diff --git a/src/teawie_bot/copypastas/dvd.txt b/teawie_bot/copypastas/dvd.txt index 4c4e848..4c4e848 100644 --- a/src/teawie_bot/copypastas/dvd.txt +++ b/teawie_bot/copypastas/dvd.txt diff --git a/src/teawie_bot/copypastas/egrill.txt b/teawie_bot/copypastas/egrill.txt index d97e8ae..d97e8ae 100644 --- a/src/teawie_bot/copypastas/egrill.txt +++ b/teawie_bot/copypastas/egrill.txt diff --git a/src/teawie_bot/copypastas/happymeal.txt b/teawie_bot/copypastas/happymeal.txt index a186e78..a186e78 100644 --- a/src/teawie_bot/copypastas/happymeal.txt +++ b/teawie_bot/copypastas/happymeal.txt diff --git a/src/teawie_bot/copypastas/ismah.txt b/teawie_bot/copypastas/ismah.txt index 8d3a87f..8d3a87f 100644 --- a/src/teawie_bot/copypastas/ismah.txt +++ b/teawie_bot/copypastas/ismah.txt diff --git a/src/teawie_bot/copypastas/navyseal.txt b/teawie_bot/copypastas/navyseal.txt index cb8eb67..cb8eb67 100644 --- a/src/teawie_bot/copypastas/navyseal.txt +++ b/teawie_bot/copypastas/navyseal.txt diff --git a/src/teawie_bot/copypastas/sus.txt b/teawie_bot/copypastas/sus.txt index a381e3e..a381e3e 100644 --- a/src/teawie_bot/copypastas/sus.txt +++ b/teawie_bot/copypastas/sus.txt diff --git a/src/teawie_bot/copypastas/ticktock.txt b/teawie_bot/copypastas/ticktock.txt index 22beb4a..22beb4a 100644 --- a/src/teawie_bot/copypastas/ticktock.txt +++ b/teawie_bot/copypastas/ticktock.txt diff --git a/src/teawie_bot/utils.py b/teawie_bot/utils.py index c75135b..c75135b 100644 --- a/src/teawie_bot/utils.py +++ b/teawie_bot/utils.py |
