Skip to content

Commit

Permalink
WIP charcreate flow
Browse files Browse the repository at this point in the history
  • Loading branch information
ttgc committed Apr 5, 2024
1 parent ea08fed commit 785194a
Show file tree
Hide file tree
Showing 17 changed files with 847 additions and 32 deletions.
21 changes: 20 additions & 1 deletion Lang/EN.lang
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,23 @@ rollindep=You rolled : `{}`
goto_input=Goto:
goto_placeholder=Type page number
goto_modal=Goto page
goto=Goto page
goto=Goto page
hp=HP
mp=MP
int=intuition
str=strength
spr=spirit
cha=charisma
agi=agility
prec=precision
luck=luck
charcreate_verify_title=Verify the character is correct
name=name
karma=karma
gmod=gamemod
charcreate_ext_dd=Select an extension
charcreate_race_dd=Select a race
charcreate_class_dd=Select a class
gmod_offensive=offensive
gmod_defensive=defensive
charcreate_setstat_modal=Define character stats
13 changes: 0 additions & 13 deletions src/dpylib/cogs/botmanage.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@
from discord.ext import commands
from config import Log, Config, Environment, LogConfig

# test imports
from ..common.contextext import ExtendedContext
from ..ui import EmbedBrowserView
from ..common.embed import DiscordEmbedMeta, EmbedFieldMeta


class BotManage(commands.Cog, name="Bot Management", command_attrs=dict(hidden=True)):
def __init__(self, client: commands.Bot) -> None:
Expand Down Expand Up @@ -63,11 +58,3 @@ async def shutdown(self, ctx: commands.Context) -> None:
Log.warn("Shutdown requested by %s", ctx.author)
await self.bot.close()
sys.exit(0)

@commands.is_owner()
@commands.hybrid_command()
async def test(self, ctx: ExtendedContext) -> None:
fields = [EmbedFieldMeta(f'item {x}', 'Lorem ipsum dolor sit amet') for x in range(156)]
embed = DiscordEmbedMeta(title="Title", color="FF0000", descr="Lorem ipsum dolor sit amet", fields=fields)
browser = EmbedBrowserView(embed, owner=ctx.author)
await browser.send(ctx)
48 changes: 48 additions & 0 deletions src/dpylib/cogs/tcog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!usr/bin/env python3
#-*-coding:utf-8-*-

## TtgcBot - a bot for discord
## Copyright (C) 2017-2024 Thomas PIOT
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>


from discord.ext import commands
from ..common.contextext import ExtendedContext
from ..ui import EmbedBrowserView
from ..common.embed import DiscordEmbedMeta, EmbedFieldMeta
from ..workflow import CharcreateWorkflow


class TCog(commands.Cog, name="Test", command_attrs=dict(hidden=True)):
def __init__(self, client: commands.Bot) -> None:
self.bot = client

@commands.hybrid_group()
async def test(self, ctx: ExtendedContext) -> None:
pass

@commands.is_owner()
@test.command(name='browser')
async def test_browser(self, ctx: ExtendedContext) -> None:
fields = [EmbedFieldMeta(f'item {x}', 'Lorem ipsum dolor sit amet') for x in range(156)]
embed = DiscordEmbedMeta(title="Title", color="FF0000", descr="Lorem ipsum dolor sit amet", fields=fields)
browser = EmbedBrowserView(embed, owner=ctx.author)
await browser.send(ctx)

@commands.is_owner()
@test.command(name='charcreate')
async def test_charcreate(self, ctx: ExtendedContext, charkey: str) -> None:
flow = CharcreateWorkflow(ctx, charkey)
await flow(ctx)
63 changes: 52 additions & 11 deletions src/dpylib/common/embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
## along with this program. If not, see <http://www.gnu.org/licenses/>


from typing import Optional, Self
from dataclasses import dataclass, field
from typing import Optional, Self, override
from dataclasses import dataclass, field as datafield
from enum import IntEnum, auto
import discord
from dpylib.common.contextext import ExtendedContext
from utils import get_color
from lang import LocalizedStr, ILocalizable
from .exceptions import DiscordLimitOverflowException


Expand Down Expand Up @@ -77,26 +79,38 @@ def __str__(self) -> str:


@dataclass
class EmbedIconTexttMeta:
class EmbedIconTextMeta(ILocalizable[str]):
text: str
icon_url: str

def __len__(self) -> int:
return len(self.text)

@override
async def localize(self, ctx: ExtendedContext, *args, **kwargs) -> str:
if isinstance(self.text, LocalizedStr):
self.text = await self.text.localize(ctx, *args, **kwargs)
return self.text


@dataclass
class EmbedAuthorMeta:
class EmbedAuthorMeta(ILocalizable[str]):
name: str
url: Optional[str]
icon_url: Optional[str]
url: Optional[str] = None
icon_url: Optional[str] = None

def __len__(self) -> int:
return len(self.name)

@override
async def localize(self, ctx: ExtendedContext, *args, **kwargs) -> str:
if isinstance(self.name, LocalizedStr):
self.name = await self.name.localize(ctx, *args, **kwargs)
return self.name


@dataclass
class EmbedFieldMeta:
class EmbedFieldMeta(ILocalizable[None]):
name: str
content: str
inlined: bool = True
Expand All @@ -107,18 +121,25 @@ def truncate(self) -> Self:
if len(self.content) > EmbedLimits.FIELD_CONTENT_LENGTH else self.content
return self.__class__(name, content, self.inlined)

@override
async def localize(self, ctx: ExtendedContext, *args, **kwargs) -> None:
if isinstance(self.name, LocalizedStr):
self.name = await self.name.localize(ctx, *args, **kwargs)
if isinstance(self.content, LocalizedStr):
self.content = await self.content.localize(ctx, *args, **kwargs)


@dataclass
class DiscordEmbedMeta:
class DiscordEmbedMeta(ILocalizable[None]):
title: str
color: str
descr: Optional[str] = None
link: Optional[str] = None
img: Optional[str] = None
thumbnail: Optional[str] = None
author: Optional[str | EmbedAuthorMeta] = None
footer: Optional[str | EmbedIconTexttMeta] = None
fields: list[EmbedFieldMeta] = field(default_factory=list)
footer: Optional[str | EmbedIconTextMeta] = None
fields: list[EmbedFieldMeta] = datafield(default_factory=list)

def __iadd__(self, field: EmbedFieldMeta) -> Self:
self.fields.append(field)
Expand Down Expand Up @@ -199,10 +220,30 @@ def convert(self, *, policy: EmbedConversionPolicy = EmbedConversionPolicy.RAISE

if isinstance(self.footer, str):
embed.set_footer(text=self.footer)
elif isinstance(self.footer, EmbedIconTexttMeta):
elif isinstance(self.footer, EmbedIconTextMeta):
embed.set_footer(text=self.footer.text, icon_url=self.footer.icon_url)

for field in self.fields:
embed.add_field(name=field.name, value=field.content, inline=field.inlined)

return embed

@override
async def localize(self, ctx: ExtendedContext, *args, **kwargs) -> None:
if isinstance(self.title, LocalizedStr):
self.title = await self.title.localize(ctx, *args, **kwargs)
if isinstance(self.descr, LocalizedStr):
self.descr = await self.descr.localize(ctx, *args, **kwargs)

if isinstance(self.author, LocalizedStr):
self.author = await self.author.localize(ctx, *args, **kwargs)
elif isinstance(self.author, EmbedAuthorMeta):
await self.author.localize(ctx, *args, **kwargs)

if isinstance(self.footer, LocalizedStr):
self.footer = await self.footer.localize(ctx, *args, **kwargs)
elif isinstance(self.footer, EmbedIconTextMeta):
await self.footer.localize(ctx, *args, **kwargs)

for field in self.fields:
await field.localize(ctx, *args, **kwargs)
4 changes: 4 additions & 0 deletions src/dpylib/events/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
from ..cogs import BotManage
from ..cogs.jdr import Jdr

# test cog - REMOVE before release
from ..cogs.tcog import TCog


@catch(AlreadyCalledFunctionException,
logger=functools.partial(Log.critical, kill_code=ExitCode.UNREGISTERED_COGS),
Expand All @@ -38,6 +41,7 @@ async def _add_cogs(client: commands.Bot) -> None:
Log.debug_v4('Registering V4 cogs')
await client.add_cog(BotManage(client))
await client.add_cog(Jdr(client))
await client.add_cog(TCog(client))
Log.debug_v4('End of registering V4 cogs')


Expand Down
1 change: 1 addition & 0 deletions src/dpylib/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
## along with this program. If not, see <http://www.gnu.org/licenses/>

from .embed_browser import EmbedBrowserView
from .embed_view import EmbedView
1 change: 1 addition & 0 deletions src/dpylib/ui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
from .button import Button, button
from .modal import Modal, modal
from .textinput import TextInput
from .dropdown import Dropdown, DropdownOption, dropdown
150 changes: 150 additions & 0 deletions src/dpylib/ui/components/dropdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!usr/bin/env python3
#-*-coding:utf-8-*-

## TtgcBot - a bot for discord
## Copyright (C) 2017-2024 Thomas PIOT
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <http://www.gnu.org/licenses/>


import functools
from dataclasses import dataclass
from typing import Any, Optional, Callable, Sequence, override, TYPE_CHECKING
import discord
from discord import ui
from utils.aliases import AsyncCallable
from utils.emojis import Emoji
from lang import ILocalizable, LocalizedStr

if TYPE_CHECKING:
from ...common.contextext import ExtendedContext


@dataclass
class DropdownOption:
name: str
value: str
description: Optional[str] = None
emoji: Optional[Emoji] = None

def __str__(self) -> str:
return self.name


type OptionType = str | DropdownOption


class Dropdown(ui.Select, ILocalizable[None]):
def __init__(
self, *,
options: list[OptionType],
default: Optional[str | list[str]] = None,
on_select: Optional[AsyncCallable[Any]] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
custom_id: str = discord.utils.MISSING,
row: Optional[int] = None
):
super().__init__(
custom_id=custom_id,
placeholder=placeholder,
min_values=min_values,
max_values=max_values,
disabled=disabled,
row=row
)
self.on_select = on_select
self += options

if default:
self.default = default

def __iadd__(self, option: OptionType | Sequence[OptionType]):
if isinstance(option, str):
self.add_option(label=option, value=option)
elif isinstance(option, DropdownOption):
emoji = str(option.emoji) if option.emoji else None
self.add_option(label=option.name, value=option.value, description=option.description, emoji=emoji)
else:
for opt in option:
self += opt

return self

@property
def default(self) -> list[str] | str:
items = [x.value for x in self.options if x.default]
return items if len(items) != 1 else items[0]

@default.setter
def default(self, value: list[str] | str) -> None:
for opt in self.options:
is_strdefault = isinstance(value, str) and opt.value == value
is_lsdefault = isinstance(value, list) and opt.value in value
opt.default = is_strdefault or is_lsdefault

@default.deleter
def default(self) -> None:
for opt in self.options:
opt.default = False

@property
def value(self) -> Optional[str]:
return self.values[0] if self.values else None

@override
async def callback(self, interaction: discord.Interaction) -> Any:
if self.on_select:
return await self.on_select(self, interaction)
return None

@override
async def localize(self, ctx: 'ExtendedContext', *args, **kwargs) -> None:
if isinstance(self.placeholder, LocalizedStr):
self.placeholder = await self.placeholder.localize(ctx, *args, **kwargs)

for opt in self.options:
if isinstance(opt.label, LocalizedStr):
opt.label = await opt.label.localize(ctx, *args, **kwargs)
if isinstance(opt.description, LocalizedStr):
opt.description = await opt.description.localize(ctx, *args, **kwargs)


def dropdown(
*, options: list[OptionType],
default: Optional[str | list[str]] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
custom_id: str = discord.utils.MISSING,
row: Optional[int] = None
) -> Callable[[AsyncCallable[Any]], Callable[..., Dropdown]]:
def _decorator(func: AsyncCallable[Any]) -> Callable[..., Dropdown]:
def _wrapper(*args, **kwargs) -> Dropdown:
return Dropdown(
options=options,
default=default,
on_select=functools.partial(func, *args, **kwargs),
placeholder=placeholder,
min_values=min_values,
max_values=max_values,
disabled=disabled,
custom_id=custom_id,
row=row
)
return _wrapper
return _decorator
Loading

0 comments on commit 785194a

Please sign in to comment.