An open API service indexing awesome lists of open source software.

https://github.com/sushichan044/ductile-ui

A typed, declarative UI library for discord.py
https://github.com/sushichan044/ductile-ui

bot discord discord-bot discord-py python python-3

Last synced: 4 months ago
JSON representation

A typed, declarative UI library for discord.py

Awesome Lists containing this project

README

          

# ductile-ui

![PyPI - Version](https://img.shields.io/pypi/v/ductile-ui)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ductile-ui)

A library provides declarative UI for [discord.py](https://github.com/Rapptz/discord.py).

## Features

- Declarative UI, inspired by [React](https://react.dev/).
- Component-oriented with State
- Fully typed

## Installation

**Python3.10 or higher is required**

**discord.py^2.2.0 is required; any compatibility under 2.1.x or 1.x is not guaranteed**

Using the latest stable release of discord.py is recommended

```bash
pip install ductile-ui
```

## Usage/Examples

You can define component as return value of `View.render()`.
To store state, use `State`.

```python
# This example requires the 'message_content' privileged intent to function.

import random

import discord
from discord.ext import commands
from ductile import State, View, ViewObject
from ductile.controller import MessageableController
from ductile.ui import Button

class Bot(commands.Bot):
def __init__(self) -> None:
super().__init__(command_prefix="!", intents=discord.Intents.all())

async def on_ready(self) -> None:
print(f"Logged in as {self.user}")
print("Ready!")

class CounterView(View):
def __init__(self) -> None:
super().__init__()
self.count = State(0, self)

def render(self) -> ViewObject:
e = discord.Embed(title="Counter", description=f"Count: {self.count.get_state()}")

async def handle_increment(interaction: discord.Interaction) -> None:
await interaction.response.defer()
self.count.set_state(lambda x: x + 1)

async def handle_decrement(interaction: discord.Interaction) -> None:
await interaction.response.defer()
self.count.set_state(lambda x: x - 1)

async def stop(interaction: discord.Interaction) -> None:
await interaction.response.defer()
self.stop()

# Define UI using ViewObject
return ViewObject(
embeds=[e],
components=[
Button("+1", style={"color": "blurple", "row": 0}, on_click=handle_increment),
Button("-1", style={"color": "blurple", "row": 0}, on_click=handle_decrement),
Button(
"random",
style={"color": "green", "row": 1},
# if you passed synchronous function to Button.on_click,
# library automatically calls `await interaction.response.defer()`.
on_click=lambda _: self.count.set_state(random.randint(0, 100)),
),
Button("stop", style={"color": "red", "row": 1}, on_click=stop),
],
)

bot = Bot()

@bot.command(name="counter")
async def send_counter(ctx: commands.Context) -> None:
controller = MessageableController(CounterView(), messageable=ctx.channel)
await controller.send()
timed_out, states = await controller.wait()
await ctx.send(f"Timed out: {timed_out}\nCount: {states['count']}")

bot.run("MY_COOL_BOT_TOKEN")

```