Skip to content

Mmesek/MFramework.py

Repository files navigation

MFramework.py

Code style: black GitHub Open in Visual Studio Code

CodeFactor Grade Lines of code GitHub code size in bytes GitHub repo size

GitHub issues GitHub pull requests GitHub contributors Discord

Docker Image Size (latest by date)

Low boilerplate command framework for Discord's REST API with Database, external Cache, Localization, Event Logging support and more. Bleeding-edge low boilerplate command framework for Discord's REST API extending mDiscord wrapper. Batteries Included.

Features

  • Command's metadata inferred from Python syntax itself
  • Dual availability of commands - define once and use as either message or interaction based
  • Implicit modals inferred from argument typehints available as modal or message-based argument polling depending on invocation
  • Hierarchy-based internal permission system
  • Reaction-based commands
  • Basic extendable runtime-defined message custom command parser
  • Webhook-based logging of Discord events
  • Leaderboard builder
  • Built-in Database support (SQLAlchemy)
  • Built-in remote cache support (Redis)
  • Built-in localization support (i18n)
  • Run as module
  • Easly extendable

Features against using it

  • Volatile
  • Repository was used as a monorepo and a "testbed" for unrelated features, certain parts were moved to separate repositories however there are still remnants of them here
  • Barely any documentation
  • Horror in internal files

Installation


System package requirements for Postgres support:

sudo apt install libpq5

Required packages:

python3 -m pip install -r requirements.txt

Examples

More examples can be found directly in my M_Bot repo.

Unlike other libraries, there is no bot or cog - everything is registered globally and a lot of information is taken from docstrings and function signatures, mainly for ease of extending.

Command example

from MFramework import register, Groups, Context

@register(group=Groups.GLOBAL)
async def say(ctx: Context, text: str, i: int=1) -> str:
    '''
    Sends a specifed message x times and finishes with replying with Done
    Params
    ------
    text:
        Message to send
    i:
        Amount of times
    '''
    for x in range(i):
        await ctx.send(f"{text} x {x}")
    return "Done"

Button example

from MFramework import Context, Button, Select

class CoolButton(Button):
    @classmethod
    async def execute(self, ctx: Context, data: str):
        # Any kind of logic that happens upon pressing a button
        pass

Select Button example

from typing import List
from MFramework import Context, Select, Select_Option

class NiceSelection(Select):
    @classmethod
    async def execute(self, ctx: Context, data: str, values: List[str], not_selected: List[Select_Option]):
        # Any kind of logic that happens upon selecting options from Menu
        pass

Using components example

from MFramework import Row

@register(group=Groups.GLOBAL)
async def new_cool_button(ctx: Context, cool_argument: str = "It's cool! Trust!") -> CoolButton:
    '''
    Sends new message with cool button
    Params
    ------
    cool_argument:
        Label's text of this button
    '''
    return CoolButton(label=cool_argument)

@register(group=Groups.GLOBAL)
async def new_cool_button(ctx: Context, name: str = "Cool Option") -> List[Row]:
    '''
    Sends new message with 2 rows, first with Selection with two options and second with two buttons
    Params
    ------
    name:
        name of first option
    '''
    rows = [
        Row(
            NiceSelection(
                Option(
                    label=name, value=123
                ),
                Option(
                    label="Second option", value=256
                ),
                placeholder= "Options"
            )
        ),
        Row(
            CoolButton(label="Cool Button!", style=Button_Styles.SECONDARY),
            Button(label="Not cool button", style=Button_Styles.DANGER)
        )
    ]
    return rows

Modal example

from MFramework import register, Groups, Context, Embed
from MFramework.commands.components import TextInput

@register(group=Groups.GLOBAL)
async def suggestion(ctx: Context, title: TextInput[1, 100], your_suggestion: TextInput[1, 2000]) -> Embed:
    '''
    Make a Suggestion!
    Params
    ------
    title:
        Title of suggestion
    your_suggestion:
        Your suggestion
    '''
    return Embed(title=title).set_description(your_suggestion)

Autocomplete example

from MFramework import register, Groups, Context, Interaction

async def animals(interaction: Interaction, current: str) -> list[str]:
    """Current represents what user typed so far"""
    return ["Sheep", "Dog", "Cat"]

@register(group=Groups.GLOBAL)
async def choose(ctx: Context, animal: animals) -> str:
    """
    Choose an animal
    Params
    ------
    animal:
        Autocomplete that suggests animal names
    """
    return f"Selected animal: {animal}"

Localization example

locale/en-US/bot.json

{
    "hi": {
        "hello": "Hello!"
    }
}

locale/pl/bot.json

{
    "hi": {
        "hello": "Cześć!"
    }
}

bot.py

@register(group=Groups.GLOBAL)
async def hi(ctx: Context) -> str:
    '''Replies in your language'''
    return ctx.t("hello")

Running Bot

Docker

Note: This section is not entirely tested, it's more a PoC rather than final version

via docker compose

docker-compose up

Manually

docker run -it \
    -v data:/app/data \
    -v bot:/app/bot \
    Mmesek/mframework

Build docker image

docker build --target base -t mframework:latest .

Run built image with autoremoving

docker run -it \
    -v "PATH_TO_DATA_FOLDER:/app/data" \
    -v "PATH_TO_BOT_CODE:/app/bot" \
    --rm mframework

Locally

python -m MFramework bot --cfg=tokens.ini --log=INFO

Contributing

Any sort of contribution, be it a Documentation, feature implementation, feature suggestion, bug fix, bug report or even a typo fix is welcome. For bigger changes open an issue first.