Added example mod to README

master
Jef Roosens 2020-08-26 12:22:18 +02:00
parent 0801762f07
commit 78290554bd
5 changed files with 96 additions and 4 deletions

View File

@ -1,2 +1,76 @@
# Frank
Frank is a modular tool for quickly creating Discord bots.
## Description
Playing around with creating a Discord bot is a fun pass-time, and a good way to learn a programming language. Sadly,
however, discord.py can be a little hard to work with at times. That's when I got the idea to create Frank. The goal of
Frank is to make creating Discord bots easier. It handles all the bot-related stuff in the background, so you can focus
on writing the functionality of the bot itself, not how the bot works/interacts with Discord.
Frank works by dividing the bot into modules. Each module has its own prefix, commands, and daemons. Frank handles
routing the Discord commands to their respective functions.
## Example Module
In this section, I've written an example module for you, to understand the basic mechanics behind Frank.
```python
import frank
class ExampleMod(frank.Module):
PREFIX = 'examp'
NAME = 'example'
HELP = 'an example module'
```
This first part shows the three important variables in any module.
- PREFIX defines the string used to use the commands defined in this module. This means you can use the module as such
inside your Discord server:
```
fr examp [NAME_OF_COMMAND] [ARGS]
```
With fr being the default prefix for Frank (can be overwritten). As you define more modules, they should all have a
unique prefix. This is how Frank's modular system works, and any modules added to the list will automatically be
picked up by Frank. The PREFIX value can also be list, allowing for multiple prefixes: for example a long,
description one, and a short, easy to type one (e.g. minecraft and mc).
```python
def pre_start(self):
self.some_var = 'a value needed for working'
```
The pre_start function is where you define any variables which should be created before any daemons are started or
commands are run. I don't recommend overwriting `__init__`, as this might break compatibility with future versions of
Frank.
```python
@frank.command('command', help_str='a small description of the command')
async def some_command(self, cmd, author, channel, mid):
# do some stuff
pass
@frank.daemon()
async def some_daemon(self):
while True:
# do some stuff
pass
@frank.default()
async def default_cmd(prefix, author, channel, mid):
# do some default action
pass
```
These three decorators are the bread and butter of Frank. Let's break them down:
- `frank.command` defines a command. The first argument is its keyword, which will be used to execute the command. The
help_str value is used in the help command, to show some information about the module. The syntax is the same as
before:
```
fr examp command [ARGS]
```
This is how you can define as many Discord commands as you want, without needing to know how to parse the messages
etc. Each command gets the `author`, `channel`, and `id` of the message. The `cmd` variable contains all the arguments passed
to the command.
- `frank.daemon` defines a daemon, a process that should run in the background for as long as the bot is active. It
should contain a while loop and preferably a sleep function using `asyncio.sleep()` (there are plans to improve this
behavior). Because a daemon is just a method of the module class, it has access to all class variables, including
those defined in `pre_start`.
- `frank.default` defines the command that should be run if the module is called without explicitely giving a command.
For example, if you call `fr examp` without specifying a command, it will run the default command. This is useful for
making a command that's used very often easier to execute.

View File

@ -17,6 +17,7 @@ if TYPE_CHECKING:
class ModuleMeta:
def _filter_attrs(self, condition: callable[[Any], bool]) -> List[Any]:
# This prevents an infinite loop of getting the attribute
illegal_names = ['commands', 'daemons', 'default']
output = []

View File

@ -1,4 +1,4 @@
jedi~=0.17.2
jedi>=0.17.2,<1.0.0
flake8~=3.8.3
flake8-bugbear~=20.1.4
flake8-builtins~=1.5.3

View File

@ -1,7 +1,9 @@
from frank import Module, command, default
from frank import Module, command, default, daemon
class ModuleTester(Module):
PREFIX = 'tester'
@command('test')
async def test(cmd, author, channel, mid):
pass
@ -9,3 +11,7 @@ class ModuleTester(Module):
@default()
async def test2(prefix, cmd, author, channel, mid):
pass
@daemon()
async def test_daemon(self):
pass

View File

@ -5,6 +5,10 @@ import pytest
def test_property_types():
"""
Test wether the cached_property's return the expected value
"""
test_mod = ModuleTester(None)
assert isinstance(test_mod.default, Default)
@ -15,7 +19,7 @@ def test_property_types():
isinstance(item, Command) for item in test_mod.commands
))
assert isinstance(test_mod.commands, list)
assert isinstance(test_mod.daemons, list)
assert all((
isinstance(item, Daemon) for item in test_mod.daemons
))
@ -28,3 +32,10 @@ async def test_invalid_command():
with pytest.raises(InvalidCommand):
# None is just a placeholder here
await test_mod('aninvalidcommand', None, None, None)
def test_match():
test_mod = ModuleTester(None)
assert test_mod.match('tester')
assert not test_mod.match('testerrr')