Skip to content

Commit

Permalink
link preview prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
Tishka17 committed Dec 11, 2024
1 parent a572eba commit d1b2e5c
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 8 deletions.
2 changes: 2 additions & 0 deletions src/aiogram_dialog/api/entities/new_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Chat,
ForceReply,
InlineKeyboardMarkup,
LinkPreviewOptions,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
Expand Down Expand Up @@ -45,3 +46,4 @@ class NewMessage:
show_mode: ShowMode = ShowMode.AUTO
disable_web_page_preview: Optional[bool] = None
media: Optional[MediaAttachment] = None
link_preview_options: Optional[LinkPreviewOptions] = None
3 changes: 2 additions & 1 deletion src/aiogram_dialog/api/internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"CALLBACK_DATA_KEY", "CONTEXT_KEY", "EVENT_SIMULATED",
"STACK_KEY", "STORAGE_KEY",
"ButtonVariant", "DataGetter", "InputWidget", "KeyboardWidget",
"MediaWidget", "RawKeyboard", "TextWidget", "Widget",
"LinkPreviewWidget", "MediaWidget", "RawKeyboard", "TextWidget", "Widget",
"WindowProtocol",
]

Expand All @@ -24,6 +24,7 @@
DataGetter,
InputWidget,
KeyboardWidget,
LinkPreviewWidget,
MediaWidget,
RawKeyboard,
TextWidget,
Expand Down
11 changes: 11 additions & 0 deletions src/aiogram_dialog/api/internal/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CallbackQuery,
InlineKeyboardButton,
KeyboardButton,
LinkPreviewOptions,
Message,
)

Expand Down Expand Up @@ -41,6 +42,16 @@ async def render_text(
raise NotImplementedError


@runtime_checkable
class LinkPreviewWidget(Widget, Protocol):
@abstractmethod
async def render_link_preview(
self, data: dict, manager: DialogManager,
) -> Optional[LinkPreviewOptions]:
"""Create link preview."""
raise NotImplementedError


ButtonVariant = Union[InlineKeyboardButton, KeyboardButton]
RawKeyboard = list[list[ButtonVariant]]

Expand Down
13 changes: 9 additions & 4 deletions src/aiogram_dialog/manager/message_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,13 @@ def need_voice(self, new_message: NewMessage) -> bool:
def _message_changed(
self, new_message: NewMessage, old_message: OldMessage,
) -> bool:
if new_message.text != old_message.text:
return True
# we cannot actually compare reply keyboards
if new_message.reply_markup or old_message.has_reply_keyboard:
if (
(new_message.text != old_message.text) or
# we cannot actually compare reply keyboards
(new_message.reply_markup or old_message.has_reply_keyboard) or
# we do not know if link preview changed
new_message.link_preview_options
):
return True

if self.had_media(old_message) != self.need_media(new_message):
Expand Down Expand Up @@ -349,6 +352,7 @@ async def edit_text(
reply_markup=new_message.reply_markup,
parse_mode=new_message.parse_mode,
disable_web_page_preview=new_message.disable_web_page_preview,
link_preview_options=new_message.link_preview_options,
)

async def edit_media(
Expand Down Expand Up @@ -395,6 +399,7 @@ async def send_text(self, bot: Bot, new_message: NewMessage) -> Message:
disable_web_page_preview=new_message.disable_web_page_preview,
reply_markup=new_message.reply_markup,
parse_mode=new_message.parse_mode,
link_preview_options=new_message.link_preview_options,
)

async def send_media(self, bot: Bot, new_message: NewMessage) -> Message:
Expand Down
3 changes: 3 additions & 0 deletions src/aiogram_dialog/widgets/link_preview/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__all__ = ["LinkPreviewBase", "LinkPreview"]

from .base import LinkPreview, LinkPreviewBase
61 changes: 61 additions & 0 deletions src/aiogram_dialog/widgets/link_preview/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from typing import Optional

from aiogram.types import LinkPreviewOptions

from aiogram_dialog import DialogManager
from aiogram_dialog.api.internal import LinkPreviewWidget, TextWidget
from aiogram_dialog.widgets.common import BaseWidget, Whenable, WhenCondition


class LinkPreviewBase(Whenable, BaseWidget, LinkPreviewWidget):
def __init__(self, when: WhenCondition = None):
super().__init__(when=when)

async def render_link_preview(
self, data: dict, manager: DialogManager,
) -> Optional[LinkPreviewOptions]:
if not self.is_(data, manager):
return None
return await self._render_link_preview(data, manager)

async def _render_link_preview(
self, data: dict, manager: DialogManager,
) -> Optional[LinkPreviewOptions]:
return None


class LinkPreview(LinkPreviewBase):
def __init__(
self,
url: TextWidget,
is_disabled: bool = False,
prefer_small_media: bool = False,
prefer_large_media: bool = False,
show_above_text: bool = False,
when: WhenCondition = None,
):
super().__init__(when=when)
self.url = url
self.is_disabled = is_disabled
self.prefer_small_media = prefer_small_media
self.prefer_large_media = prefer_large_media
self.show_above_text = show_above_text

async def render_link_preview(
self, data: dict, manager: DialogManager,
) -> Optional[LinkPreviewOptions]:
if not self.is_(data, manager):
return None
return await self._render_link_preview(data, manager)

async def _render_link_preview(
self, data: dict, manager: DialogManager,
) -> Optional[LinkPreviewOptions]:
url = await self.url.render_text(data, manager)
return LinkPreviewOptions(
url=url,
is_disabled=self.is_disabled,
prefer_small_media=self.prefer_small_media,
prefer_large_media=self.prefer_large_media,
show_above_text=self.show_above_text,
)
25 changes: 22 additions & 3 deletions src/aiogram_dialog/widgets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
from typing import Union

from aiogram_dialog.api.exceptions import InvalidWidgetType
from aiogram_dialog.api.internal import DataGetter
from aiogram_dialog.api.internal import DataGetter, LinkPreviewWidget

from .data.data_context import CompositeGetter, StaticGetter
from .input import BaseInput, CombinedInput, MessageHandlerFunc, MessageInput
from .kbd import Group, Keyboard
from .link_preview import LinkPreviewBase
from .media import Media
from .text import Format, Multi, Text
from .widget_event import WidgetEventProcessor

WidgetSrc = Union[str, Text, Keyboard, MessageHandlerFunc, Media, BaseInput]
WidgetSrc = Union[
str, Text, Keyboard, MessageHandlerFunc, Media, BaseInput, LinkPreviewBase,
]

SingleGetterBase = Union[DataGetter, dict]
GetterVariant = Union[
Expand Down Expand Up @@ -71,13 +74,26 @@ def ensure_media(widget: Union[Media, Sequence[Media]]) -> Media:
return Media()


def ensure_link_preview(
widget: Union[LinkPreviewWidget, Sequence[LinkPreviewWidget]],
) -> LinkPreviewWidget:
if isinstance(widget, LinkPreviewWidget):
return widget
if len(widget) > 1:
raise ValueError("Only one link preview widget is supported")
if len(widget) == 1:
return widget[0]
return LinkPreviewBase()


def ensure_widgets(
widgets: Sequence[WidgetSrc],
) -> tuple[Text, Keyboard, Union[BaseInput, None], Media]:
) -> tuple[Text, Keyboard, Union[BaseInput, None], Media, LinkPreviewWidget]:
texts = []
keyboards = []
inputs = []
media = []
link_preview = []

for w in widgets:
if isinstance(w, (str, Text)):
Expand All @@ -88,6 +104,8 @@ def ensure_widgets(
inputs.append(ensure_input(w))
elif isinstance(w, Media):
media.append(ensure_media(w))
elif isinstance(w, LinkPreviewBase):
link_preview.append(ensure_link_preview(w))
else:
raise InvalidWidgetType(
f"Cannot add widget of type {type(w)}. "
Expand All @@ -99,6 +117,7 @@ def ensure_widgets(
ensure_keyboard(keyboards),
ensure_input(inputs),
ensure_media(media),
ensure_link_preview(link_preview),
)


Expand Down
12 changes: 12 additions & 0 deletions src/aiogram_dialog/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from aiogram.types import (
UNSET_PARSE_MODE,
CallbackQuery,
LinkPreviewOptions,
Message,
)
from aiogram.types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW
Expand Down Expand Up @@ -55,6 +56,7 @@ def __init__(
self.keyboard,
self.on_message,
self.media,
self.link_preview,
) = ensure_widgets(widgets)
self.getter = PreviewAwareGetter(
ensure_data_getter(getter),
Expand Down Expand Up @@ -87,6 +89,13 @@ async def render_kbd(
data, manager, keyboard,
)

async def render_link_preview(
self, data: dict, manager: DialogManager,
) -> Optional[LinkPreviewOptions]:
if self.link_preview:
return await self.link_preview.render_link_preview(data, manager)
return None

async def load_data(
self, dialog: "DialogProtocol",
manager: DialogManager,
Expand Down Expand Up @@ -145,6 +154,9 @@ async def render(
parse_mode=self.parse_mode,
disable_web_page_preview=self.disable_web_page_preview,
media=await self.render_media(current_data, manager),
link_preview_options=await self.render_link_preview(
current_data, manager,
),
)
except Exception:
logger.error("Cannot render window for state %s", self.state)
Expand Down

0 comments on commit d1b2e5c

Please sign in to comment.