Skip to content

Commit fefadf9

Browse files
committed
link preview prototype
1 parent a572eba commit fefadf9

File tree

8 files changed

+115
-4
lines changed

8 files changed

+115
-4
lines changed

Diff for: src/aiogram_dialog/api/entities/new_message.py

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Chat,
88
ForceReply,
99
InlineKeyboardMarkup,
10+
LinkPreviewOptions,
1011
ReplyKeyboardMarkup,
1112
ReplyKeyboardRemove,
1213
)
@@ -45,3 +46,4 @@ class NewMessage:
4546
show_mode: ShowMode = ShowMode.AUTO
4647
disable_web_page_preview: Optional[bool] = None
4748
media: Optional[MediaAttachment] = None
49+
link_preview_options: Optional[LinkPreviewOptions] = None

Diff for: src/aiogram_dialog/api/internal/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"CALLBACK_DATA_KEY", "CONTEXT_KEY", "EVENT_SIMULATED",
55
"STACK_KEY", "STORAGE_KEY",
66
"ButtonVariant", "DataGetter", "InputWidget", "KeyboardWidget",
7-
"MediaWidget", "RawKeyboard", "TextWidget", "Widget",
7+
"LinkPreviewWidget", "MediaWidget", "RawKeyboard", "TextWidget", "Widget",
88
"WindowProtocol",
99
]
1010

@@ -24,6 +24,7 @@
2424
DataGetter,
2525
InputWidget,
2626
KeyboardWidget,
27+
LinkPreviewWidget,
2728
MediaWidget,
2829
RawKeyboard,
2930
TextWidget,

Diff for: src/aiogram_dialog/api/internal/widgets.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
CallbackQuery,
1313
InlineKeyboardButton,
1414
KeyboardButton,
15-
Message,
15+
Message, LinkPreviewOptions,
1616
)
1717

1818
from aiogram_dialog import DialogManager
@@ -41,6 +41,16 @@ async def render_text(
4141
raise NotImplementedError
4242

4343

44+
@runtime_checkable
45+
class LinkPreviewWidget(Widget, Protocol):
46+
@abstractmethod
47+
async def render_link_preview(
48+
self, data: dict, manager: DialogManager,
49+
) -> Optional[LinkPreviewOptions]:
50+
"""Create link preview."""
51+
raise NotImplementedError
52+
53+
4454
ButtonVariant = Union[InlineKeyboardButton, KeyboardButton]
4555
RawKeyboard = list[list[ButtonVariant]]
4656

Diff for: src/aiogram_dialog/manager/message_manager.py

+5
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ def _message_changed(
134134
# we cannot actually compare reply keyboards
135135
if new_message.reply_markup or old_message.has_reply_keyboard:
136136
return True
137+
# we do not know if link preview changed
138+
if new_message.link_preview_options:
139+
return True
137140

138141
if self.had_media(old_message) != self.need_media(new_message):
139142
return True
@@ -349,6 +352,7 @@ async def edit_text(
349352
reply_markup=new_message.reply_markup,
350353
parse_mode=new_message.parse_mode,
351354
disable_web_page_preview=new_message.disable_web_page_preview,
355+
link_preview_options=new_message.link_preview_options,
352356
)
353357

354358
async def edit_media(
@@ -395,6 +399,7 @@ async def send_text(self, bot: Bot, new_message: NewMessage) -> Message:
395399
disable_web_page_preview=new_message.disable_web_page_preview,
396400
reply_markup=new_message.reply_markup,
397401
parse_mode=new_message.parse_mode,
402+
link_preview_options=new_message.link_preview_options,
398403
)
399404

400405
async def send_media(self, bot: Bot, new_message: NewMessage) -> Message:

Diff for: src/aiogram_dialog/widgets/link_preview/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__all__ = ["LinkPreviewBase", "LinkPreview"]
2+
3+
from .base import LinkPreviewBase, LinkPreview

Diff for: src/aiogram_dialog/widgets/link_preview/base.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from typing import Optional
2+
3+
from aiogram.types import LinkPreviewOptions
4+
5+
from aiogram_dialog import DialogManager
6+
from aiogram_dialog.api.internal import LinkPreviewWidget, TextWidget
7+
from aiogram_dialog.widgets.common import BaseWidget, Whenable, WhenCondition
8+
9+
10+
class LinkPreviewBase(Whenable, BaseWidget, LinkPreviewWidget):
11+
def __init__(self, when: WhenCondition = None):
12+
super().__init__(when=when)
13+
14+
async def render_link_preview(
15+
self, data: dict, manager: DialogManager,
16+
) -> Optional[LinkPreviewOptions]:
17+
if not self.is_(data, manager):
18+
return None
19+
return await self._render_link_preview(data, manager)
20+
21+
async def _render_link_preview(
22+
self, data: dict, manager: DialogManager,
23+
) -> Optional[LinkPreviewOptions]:
24+
return None
25+
26+
27+
class LinkPreview(Whenable, BaseWidget, LinkPreviewWidget):
28+
def __init__(
29+
self,
30+
url: TextWidget,
31+
is_disabled: bool = False,
32+
prefer_small_media: bool = False,
33+
prefer_large_media: bool = False,
34+
show_above_text: bool = False,
35+
when: WhenCondition = None,
36+
):
37+
super().__init__(when=when)
38+
self.url = url
39+
self.is_disabled = is_disabled
40+
self.prefer_small_media = prefer_small_media
41+
self.prefer_large_media = prefer_large_media
42+
self.show_above_text = show_above_text
43+
44+
async def render_link_preview(
45+
self, data: dict, manager: DialogManager,
46+
) -> Optional[LinkPreviewOptions]:
47+
if not self.is_(data, manager):
48+
return None
49+
return await self._render_link_preview(data, manager)
50+
51+
async def _render_link_preview(
52+
self, data: dict, manager: DialogManager,
53+
) -> Optional[LinkPreviewOptions]:
54+
url = await self.url.render_text(data, manager)
55+
return LinkPreviewOptions(
56+
url=url,
57+
is_disabled=self.is_disabled,
58+
prefer_small_media=self.prefer_small_media,
59+
prefer_large_media=self.prefer_large_media,
60+
show_above_text=self.show_above_text,
61+
)

Diff for: src/aiogram_dialog/widgets/utils.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
from typing import Union
33

44
from aiogram_dialog.api.exceptions import InvalidWidgetType
5-
from aiogram_dialog.api.internal import DataGetter
5+
from aiogram_dialog.api.internal import DataGetter, LinkPreviewWidget
66

77
from .data.data_context import CompositeGetter, StaticGetter
88
from .input import BaseInput, CombinedInput, MessageHandlerFunc, MessageInput
99
from .kbd import Group, Keyboard
1010
from .media import Media
11+
from .link_preview import LinkPreviewBase
1112
from .text import Format, Multi, Text
1213
from .widget_event import WidgetEventProcessor
1314

@@ -71,13 +72,26 @@ def ensure_media(widget: Union[Media, Sequence[Media]]) -> Media:
7172
return Media()
7273

7374

75+
def ensure_link_preview(
76+
widget: Union[LinkPreviewWidget, Sequence[LinkPreviewWidget]],
77+
) -> LinkPreviewWidget:
78+
if isinstance(widget, LinkPreviewWidget):
79+
return widget
80+
if len(widget) > 1:
81+
raise ValueError("Only one link preview widget is supported")
82+
if len(widget) == 1:
83+
return widget[0]
84+
return LinkPreviewBase()
85+
86+
7487
def ensure_widgets(
7588
widgets: Sequence[WidgetSrc],
76-
) -> tuple[Text, Keyboard, Union[BaseInput, None], Media]:
89+
) -> tuple[Text, Keyboard, Union[BaseInput, None], Media, LinkPreviewWidget]:
7790
texts = []
7891
keyboards = []
7992
inputs = []
8093
media = []
94+
link_preview = []
8195

8296
for w in widgets:
8397
if isinstance(w, (str, Text)):
@@ -88,6 +102,8 @@ def ensure_widgets(
88102
inputs.append(ensure_input(w))
89103
elif isinstance(w, Media):
90104
media.append(ensure_media(w))
105+
elif isinstance(w, LinkPreviewBase):
106+
media.append(ensure_link_preview(w))
91107
else:
92108
raise InvalidWidgetType(
93109
f"Cannot add widget of type {type(w)}. "
@@ -99,6 +115,7 @@ def ensure_widgets(
99115
ensure_keyboard(keyboards),
100116
ensure_input(inputs),
101117
ensure_media(media),
118+
ensure_link_preview(link_preview),
102119
)
103120

104121

Diff for: src/aiogram_dialog/window.py

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from aiogram.types import (
66
UNSET_PARSE_MODE,
77
CallbackQuery,
8+
LinkPreviewOptions,
89
Message,
910
)
1011
from aiogram.types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW
@@ -55,6 +56,7 @@ def __init__(
5556
self.keyboard,
5657
self.on_message,
5758
self.media,
59+
self.link_preview,
5860
) = ensure_widgets(widgets)
5961
self.getter = PreviewAwareGetter(
6062
ensure_data_getter(getter),
@@ -87,6 +89,13 @@ async def render_kbd(
8789
data, manager, keyboard,
8890
)
8991

92+
async def render_link_preview(
93+
self, data: dict, manager: DialogManager,
94+
) -> Optional[LinkPreviewOptions]:
95+
if self.link_preview:
96+
return await self.link_preview.render_link_preview(data, manager)
97+
return None
98+
9099
async def load_data(
91100
self, dialog: "DialogProtocol",
92101
manager: DialogManager,
@@ -145,6 +154,9 @@ async def render(
145154
parse_mode=self.parse_mode,
146155
disable_web_page_preview=self.disable_web_page_preview,
147156
media=await self.render_media(current_data, manager),
157+
link_preview_options=await self.render_link_preview(
158+
current_data, manager,
159+
),
148160
)
149161
except Exception:
150162
logger.error("Cannot render window for state %s", self.state)

0 commit comments

Comments
 (0)