Skip to content

Commit

Permalink
Merge pull request #162 from YousefEZ/changeExpansive
Browse files Browse the repository at this point in the history
🐛 Fix expansive
  • Loading branch information
YousefEZ authored Dec 26, 2023
2 parents 3f37afb + f6d0254 commit b2d0b06
Show file tree
Hide file tree
Showing 30 changed files with 1,571 additions and 1,080 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Discord templating engine built on discord.py, to help separate text of embeds f
-----
Key Features:

- use of xml files to hold the various template responses
- allows for pagination, in an abstract form simplifying the interface in the source code
* use of xml files to hold the various template responses
* allows for pagination, in an abstract form simplifying the interface in the source code

-----

Expand Down
49 changes: 42 additions & 7 deletions docs/usage/simple-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,22 +272,37 @@ the ``.render(key, callables, keywords, events)`` method. With the events allowi
- ``ModalEvents.ON_TIMEOUT``: when the view times out.

```py
async def on_timeout(view: discord.ui.Modal) -> None:
import qalib.translators.json.components

import qalib.translators.json


async def on_timeout(view: qalib.translators.json.components.Modal) -> None:
...
```

- ``ModalEvents.ON_CHECK``: when the view is being interacted with, have a check to return a bool to call any callbacks

```py
async def check(view: discord.ui.Modal, interaction: discord.Interaction) -> bool:
import qalib.translators.json.components

import qalib.translators.json


async def check(view: qalib.translators.json.components.Modal, interaction: discord.Interaction) -> bool:
...
```

- ``ModalEvents.ON_ERROR``: when the view throws out an exception, this event is called

```py
import qalib.translators.json.components

import qalib.translators.json


async def error_handling(
view: discord.ui.Modal,
view: qalib.translators.json.components.Modal,
interaction: discord.Interaction,
error: Exception
) -> None:
Expand All @@ -297,7 +312,12 @@ async def error_handling(
- ``ModalEvents.ON_SUBMIT``: when the modal is submitted this event is called

```py
async def submit(view: discord.ui.Modal, interaction: discord.Interaction) -> None:
import qalib.translators.json.components

import qalib.translators.json


async def submit(view: qalib.translators.json.components.Modal, interaction: discord.Interaction) -> None:
...
```

Expand Down Expand Up @@ -395,22 +415,37 @@ Views:
- ``ViewEvents.ON_TIMEOUT``: when the view times out.

```py
async def on_timeout(view: discord.ui.View) -> None:
import qalib.translators.json.components

import qalib.translators.json


async def on_timeout(view: qalib.translators.json.components.View) -> None:
...
```

- ``ViewEvents.ON_CHECK``: when the view is being interacted with, have a check to return a bool to call any callbacks

```py
async def check(view: discord.ui.View, interaction: discord.Interaction) -> bool:
import qalib.translators.json.components

import qalib.translators.json


async def check(view: qalib.translators.json.components.View, interaction: discord.Interaction) -> bool:
...
```

- ``ViewEvents.ON_ERROR``: when the view throws out an exception, this event is called

```py
import qalib.translators.json.components

import qalib.translators.json


async def error_handling(
view: discord.ui.View,
view: qalib.translators.json.components.View,
interaction: discord.Interaction,
error: Exception,
item: discord.ui.Item
Expand Down
831 changes: 484 additions & 347 deletions poetry.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dynamic = ["dependencies"]
dynamic = ["dependencies", "license"]

[tool.poetry]
name = "discord-qalib"
version = "2.4.5"
authors = ["YousefEZ syberprojects@gmail.com", ]
authors = ["YousefEZ <syberprojects@gmail.com>", ]
description = "Discord library built on discord.py to simplify source code by rendering templates of embeds and menus"
packages = [{ include = "qalib" }]

Expand Down Expand Up @@ -49,6 +49,9 @@ typing-extensions = "^4.4.0"
discord-py = "^2.1.0"
emoji = "^2.2.0"
jinja2 = "^3.1.2"
pyright = "^1.1.342"
discord = "^2.3.2"
deprecated = "^1.2.14"

[tool.setuptools.dynamic]
dependencies = { file = ["requirements.txt"] }
Expand Down
8 changes: 4 additions & 4 deletions qalib/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

from qalib.renderer import Renderer
from qalib.translators import Callback, Message
from qalib.translators.events import EventCallback, EventCallbacks
from qalib.translators.deserializer import K_contra
from qalib.translators.events import EventCallbacks
from qalib.translators.menu import Menu


Expand Down Expand Up @@ -81,7 +81,7 @@ async def rendered_send(
message = self._renderer.render(identifier, callables, keywords, events)

if isinstance(message, Menu):
message.front = 0 if "page" not in kwargs else kwargs["page"]
message.set_front_page(kwargs.get("page", 0))
message = message.front

assert isinstance(message, Message)
Expand Down Expand Up @@ -109,7 +109,7 @@ async def display(
"""
message = self._renderer.render(key, callables, keywords, events)
if isinstance(message, Menu):
message.front = 0 if "page" not in kwargs else kwargs["page"]
message.set_front_page(kwargs.get("page", 0))
message = message.front

assert isinstance(message, Message)
Expand All @@ -135,7 +135,7 @@ async def menu(
key: K_contra,
callbacks: Optional[Dict[str, Callback]] = None,
keywords: Optional[Dict[str, Any]] = None,
events: Optional[EventCallback] = None,
events: Optional[EventCallbacks] = None,
**kwargs,
) -> None:
"""This method is used to create a menu for the user to select from.
Expand Down
4 changes: 2 additions & 2 deletions qalib/interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async def rendered_send(
message = self._renderer.render(identifier, callables, keywords, events)

if isinstance(message, Menu):
message.front = 0 if "page" not in kwargs else kwargs["page"]
message.set_front_page(kwargs.get("page", 0))
message = message.front

if isinstance(message, Message):
Expand Down Expand Up @@ -116,7 +116,7 @@ async def display(
message = self._renderer.render(key, callables, keywords, events)

if isinstance(message, Menu):
message.front = 0 if "page" not in kwargs else kwargs["page"]
message.set_front_page(kwargs.get("page", 0))
message = message.front

assert isinstance(message, Message)
Expand Down
Empty file.
46 changes: 46 additions & 0 deletions qalib/translators/element/embed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations

import dataclasses
from typing import List, Union, Protocol

import discord

from qalib.translators.element.types.embed import Field, EmbedBaseAdapter, EmbedBaseData

__all__ = "EmbedAdapter", "EmbedData", "render"


class EmbedAdapter(EmbedBaseAdapter, Protocol):

@property
def fields(self) -> List[Field]:
raise NotImplementedError


@dataclasses.dataclass(frozen=True)
class EmbedData(EmbedBaseData):
fields: List[Field] = dataclasses.field(default_factory=list)


def render(embed_data: Union[EmbedData, EmbedAdapter]) -> discord.Embed:
embed = discord.Embed(
title=embed_data.title,
colour=embed_data.colour,
type=embed_data.type,
description=embed_data.description,
timestamp=embed_data.timestamp
)

for field in embed_data.fields:
embed.add_field(**field)

if (footer := embed_data.footer) is not None:
embed.set_footer(**footer)

embed.set_thumbnail(url=embed_data.thumbnail)
embed.set_image(url=embed_data.image)

if (author := embed_data.author) is not None:
embed.set_author(**author)

return embed
139 changes: 139 additions & 0 deletions qalib/translators/element/expansive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from __future__ import annotations

from functools import wraps
from typing import List, Optional, Callable, TypeVar

import discord

from qalib.translators.element.embed import EmbedData, render
from qalib.translators.element.types.embed import Field, EmbedBaseAdapter, Footer

__all__ = "ExpansiveEmbedAdapter", "expand"

MAX_FIELD_LENGTH = 1_024


class ExpansiveEmbedAdapter(EmbedBaseAdapter):

def __init__(self, page_number_key: Optional[str] = None):
super().__init__()
self._page_number_key = page_number_key

@property
def field(self) -> Field:
raise NotImplementedError

@property
def page_number_key(self) -> Optional[str]:
return self._page_number_key


def _split_field(field: Field, key: Optional[str]) -> List[Field]:
start = 0
lines = [string.strip() for string in field["value"].strip().split("\n")]

values: List[Field] = []

def compile_lines(end: Optional[int] = None) -> str:
if end is None:
return "\n".join([string.replace("\\n", "\n") for string in lines[start:]])
return "\n".join([string.replace("\\n", "\n") for string in lines[start: end]])

def compile_field(end: Optional[int] = None) -> Field:
return {
"name": field["name"].replace(key, str(len(values) + 1)) if key else field["name"],
"value": compile_lines(end).replace(key, str(len(values) + 1)) if key else compile_lines(end),
"inline": field.get("inline", True)
}

for i in range(len(lines)):
diff_char = key is not None and len(str(len(values))) - len(key)
var_char: int = key is not None and lines[start: i + 1].count(key) * diff_char
if sum(map(len, lines[start: i + 1])) + var_char >= MAX_FIELD_LENGTH:
values.append(compile_field(i))
start = i

values.append(compile_field())
return values


_T = TypeVar("_T")


def _page_key_guard(
func: Callable[[str, _T, int], _T]
) -> Callable[[Optional[str], _T, int], _T]:
"""A decorator that guards a function so that it only runs if the embed proxy has a page key.
Args:
func (Callable[[ExpansiveEmbedProxy, _T, int], Optional[_T]]): The function to guard.
Returns:
Callable[[ExpansiveEmbedProxy, _T, int], Optional[_T]]: The guarded function.
"""

@wraps(func)
def wrapper(page_key: Optional[str], replaceable: _T, page: int) -> _T:
if page_key is None:
return replaceable
return func(page_key, replaceable, page)

return wrapper


@_page_key_guard
def _replace_footer_with_page_key(
page_key: str,
footer: Optional[Footer],
page: int
) -> Optional[Footer]:
"""Render the footer, if it exists, with the page key.
Args:
page_key (str): the string to replace with the page number
page (int): The page number to render the footer with.
Returns:
Optional[discord.EmbedFooter]: The rendered footer, if it exists.
"""
if footer is None:
return None

if "text" not in footer:
return footer

return {
"text": footer['text'].replace(page_key, str(page)),
"icon_url": footer['icon_url']
}


@_page_key_guard
def replace(page_key: str, value: str, page: int) -> str:
return value.replace(page_key, str(page))


def expand(embed: ExpansiveEmbedAdapter) -> List[discord.Embed]:
"""Render the desired templated embed in discord.Embed instance.
Args:
embed (ExpansiveEmbedAdapter): The embed proxy to render.
Returns:
Embed: Embed Object, discord compatible.
"""

return [
render(EmbedData(
title=replace(embed.page_number_key, embed.title, page + 1),
colour=embed.colour,
type=embed.type,
description=replace(embed.page_number_key, embed.description, page + 1) if embed.description else None,
timestamp=embed.timestamp,
fields=[field],
footer=_replace_footer_with_page_key(embed.page_number_key, embed.footer, page + 1),
thumbnail=embed.thumbnail,
image=embed.image,
author=embed.author
)) for page, field in enumerate(_split_field(embed.field, embed.page_number_key))
]
Empty file.
Loading

0 comments on commit b2d0b06

Please sign in to comment.