Skip to content

Commit 8a30174

Browse files
authored
Merge pull request #447 from Tishka17/feature/link_preview
Link preview
2 parents a572eba + eacc7bc commit 8a30174

File tree

17 files changed

+304
-24
lines changed

17 files changed

+304
-24
lines changed

docs/conf.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@
1313
# import os
1414
# import sys
1515
# sys.path.insert(0, os.path.abspath("."))
16-
17-
import datetime
18-
1916
# -- Project information -----------------------------------------------------
2017

2118
project = "aiogram-dialog"
22-
copyright = f"{datetime.date.today().year}, Tishka17"
19+
copyright = "%Y, Tishka17"
2320
author = "Tishka17"
2421
master_doc = "index"
2522

docs/widgets/index.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ Widgets and Rendering
44
Base information
55
********************
66

7-
Currently there are 4 kinds of widgets: :ref:`texts <text_widgets>`, :ref:`keyboards <keyboard_widgets>`,
8-
:ref:`input <input_widgets>`, :ref:`media<media_widgets>` and you can create your own :ref:`widgets<custom_widgets>`.
7+
Currently there are 5 kinds of widgets: :ref:`texts <text_widgets>`, :ref:`keyboards <keyboard_widgets>`,
8+
:ref:`input <input_widgets>`, :ref:`media<media_widgets>`, :ref:`link preview<link_preview>` and you can create your own :ref:`widgets<custom_widgets>`.
99

1010
* **Texts** used to render text anywhere in dialog. It can be message text, button title and so on.
1111
* **Keyboards** represent parts of ``InlineKeyboard``
1212
* **Media** represent media attachment to message
1313
* **Input** allows to process incoming messages from user. Is has no representation.
14+
* **Link Preview** used to manage link previews in messages.
1415

1516
Widgets can display static (e.g. ``Const``) and dynamic (e.g. ``Format``) content. To use dynamic data you have to set it. See :ref:`passing data <passing_data>`.
1617

@@ -37,5 +38,6 @@ Also there are 2 general types:
3738
keyboard/index
3839
input/index
3940
media/index
41+
link_preview/index
4042
hiding/index
41-
custom_widgets/index
43+
custom_widgets/index

docs/widgets/link_preview/example.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from aiogram.filters.state import State, StatesGroup
2+
3+
from aiogram_dialog import Window
4+
from aiogram_dialog.widgets.link_preview import LinkPreview
5+
from aiogram_dialog.widgets.text import Const
6+
7+
8+
class SG(StatesGroup):
9+
MAIN = State()
10+
SECOND = State()
11+
12+
13+
window = Window(
14+
Const("https://nplus1.ru/news/2024/05/23/voyager-1-science-data"),
15+
LinkPreview(is_disabled=True),
16+
state=SG.MAIN,
17+
)
18+
19+
second_window = Window(
20+
Const("some text"),
21+
LinkPreview(
22+
url=Const("https://nplus1.ru/news/2024/05/23/voyager-1-science-data"),
23+
prefer_small_media=True,
24+
show_above_text=True,
25+
),
26+
state=SG.MAIN,
27+
)

docs/widgets/link_preview/index.rst

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.. _link_preview:
2+
3+
LinkPreview
4+
*************
5+
6+
The **LinkPreview** widget is used to manage link previews in messages.
7+
8+
Parameters:
9+
10+
* ``url``: A ``TextWidget`` with URL to be used in the link preview. If not provided, the first URL found in the message will be used.
11+
* ``is_disabled``: that controls whether the link preview is displayed. If ``True``, the preview will be disabled.
12+
* ``prefer_small_media``: that controls if the media in the link preview should be displayed in a smaller size. Ignored if media size change is not supported.
13+
* ``prefer_large_media``: that controls if the media in the link preview should be enlarged. Ignored if media size change is not supported.
14+
* ``show_above_text``: that specifies whether the link preview should be displayed above the message text. If ``True``, link preview be displayed above the message text.
15+
16+
17+
Code example:
18+
19+
.. literalinclude:: ./example.py
20+
21+
.. autoclass:: aiogram_dialog.widgets.link_preview.LinkPreview
22+
:special-members: __init__
23+
:members: render_link_preview, _render_link_preview

example/loading.py

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
setup_dialogs,
1818
)
1919
from aiogram_dialog.widgets.kbd import Button
20+
from aiogram_dialog.widgets.link_preview import LinkPreview
2021
from aiogram_dialog.widgets.text import Const, Multi, Progress
2122

2223
API_TOKEN = os.getenv("BOT_TOKEN")
@@ -75,6 +76,7 @@ async def background(callback: CallbackQuery, manager: BaseDialogManager):
7576
Window(
7677
Const("Press button to start processing"),
7778
Button(Const("Start"), id="start", on_click=start_bg),
79+
LinkPreview(url=Const("http://ya.ru")),
7880
state=MainSG.main,
7981
),
8082
)

example/mega/bot.py

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from bot_dialogs.calendar import calendar_dialog
1212
from bot_dialogs.counter import counter_dialog
1313
from bot_dialogs.layouts import layouts_dialog
14+
from bot_dialogs.link_preview import link_preview_dialog
1415
from bot_dialogs.main import main_dialog
1516
from bot_dialogs.mutltiwidget import multiwidget_dialog
1617
from bot_dialogs.reply_buttons import reply_kbd_dialog
@@ -68,6 +69,7 @@ async def on_unknown_intent(event: ErrorEvent, dialog_manager: DialogManager):
6869
multiwidget_dialog,
6970
switch_dialog,
7071
reply_kbd_dialog,
72+
link_preview_dialog,
7173
)
7274

7375

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from aiogram_dialog import (
2+
Dialog,
3+
Window,
4+
)
5+
from aiogram_dialog.widgets.kbd import SwitchTo
6+
from aiogram_dialog.widgets.link_preview import LinkPreview
7+
from aiogram_dialog.widgets.text import Const, Format
8+
9+
from . import states
10+
from .common import MAIN_MENU_BUTTON
11+
12+
13+
async def links_getter(**_):
14+
return {
15+
"main": "https://en.wikipedia.org/wiki/HTML_element",
16+
"photo": "https://en.wikipedia.org/wiki/Hyperlink",
17+
}
18+
19+
20+
LinkPreview_MAIN_MENU_BUTTON = SwitchTo(
21+
text=Const("Back"), id="back", state=states.LinkPreview.MAIN,
22+
)
23+
COMMON_TEXT = Format(
24+
"This is demo of different link preview options.\n"
25+
"Link in text: {main}\n"
26+
"Link in preview can be different\n\n"
27+
"Current mode is:",
28+
)
29+
30+
BACK = SwitchTo(Const("back"), "_back", states.LinkPreview.MAIN)
31+
32+
link_preview_dialog = Dialog(
33+
Window(
34+
COMMON_TEXT,
35+
Format("Default"),
36+
SwitchTo(
37+
Const("disable"), "_disable", states.LinkPreview.IS_DISABLED,
38+
),
39+
SwitchTo(
40+
Const("prefer small media"), "_prefer_small_media",
41+
states.LinkPreview.SMALL_MEDIA,
42+
),
43+
SwitchTo(
44+
Const("prefer large media"), "_prefer_large_media",
45+
states.LinkPreview.LARGE_MEDIA,
46+
),
47+
SwitchTo(
48+
Const("show above text"), "_show_above_text",
49+
states.LinkPreview.SHOW_ABOVE_TEXT,
50+
),
51+
MAIN_MENU_BUTTON,
52+
state=states.LinkPreview.MAIN,
53+
),
54+
Window(
55+
COMMON_TEXT,
56+
Const("is_disabled=True"),
57+
LinkPreview(is_disabled=True),
58+
LinkPreview_MAIN_MENU_BUTTON,
59+
state=states.LinkPreview.IS_DISABLED,
60+
),
61+
Window(
62+
COMMON_TEXT,
63+
Const("prefer_small_media=True"),
64+
LinkPreview(Format("{photo}"), prefer_small_media=True),
65+
LinkPreview_MAIN_MENU_BUTTON,
66+
state=states.LinkPreview.SMALL_MEDIA,
67+
),
68+
Window(
69+
COMMON_TEXT,
70+
Const("prefer_large_media=True"),
71+
LinkPreview(Format("{photo}"), prefer_large_media=True),
72+
LinkPreview_MAIN_MENU_BUTTON,
73+
state=states.LinkPreview.LARGE_MEDIA,
74+
),
75+
Window(
76+
COMMON_TEXT,
77+
Const("show_above_text=True"),
78+
LinkPreview(Format("{photo}"), show_above_text=True),
79+
LinkPreview_MAIN_MENU_BUTTON,
80+
state=states.LinkPreview.SHOW_ABOVE_TEXT,
81+
),
82+
getter=links_getter,
83+
)

example/mega/bot_dialogs/main.py

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
id="switch",
4545
state=states.Switch.MAIN,
4646
),
47+
Start(
48+
text=Const("🔗 Link Preview"),
49+
id="linkpreview",
50+
state=states.LinkPreview.MAIN,
51+
),
4752
Start(
4853
text=Const("⌨️ Reply keyboard"),
4954
id="reply",

example/mega/bot_dialogs/states.py

+8
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,11 @@ class Switch(StatesGroup):
5252
MAIN = State()
5353
INPUT = State()
5454
LAST = State()
55+
56+
57+
class LinkPreview(StatesGroup):
58+
MAIN = State()
59+
IS_DISABLED = State()
60+
SMALL_MEDIA = State()
61+
LARGE_MEDIA = State()
62+
SHOW_ABOVE_TEXT = State()

src/aiogram_dialog/api/entities/new_message.py

+2-1
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
)
@@ -43,5 +44,5 @@ class NewMessage:
4344
reply_markup: Optional[MarkupVariant] = None
4445
parse_mode: Optional[str] = None
4546
show_mode: ShowMode = ShowMode.AUTO
46-
disable_web_page_preview: Optional[bool] = None
4747
media: Optional[MediaAttachment] = None
48+
link_preview_options: Optional[LinkPreviewOptions] = None

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,

src/aiogram_dialog/api/internal/widgets.py

+11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
CallbackQuery,
1313
InlineKeyboardButton,
1414
KeyboardButton,
15+
LinkPreviewOptions,
1516
Message,
1617
)
1718

@@ -41,6 +42,16 @@ async def render_text(
4142
raise NotImplementedError
4243

4344

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

src/aiogram_dialog/manager/message_manager.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,13 @@ def need_voice(self, new_message: NewMessage) -> bool:
129129
def _message_changed(
130130
self, new_message: NewMessage, old_message: OldMessage,
131131
) -> bool:
132-
if new_message.text != old_message.text:
133-
return True
134-
# we cannot actually compare reply keyboards
135-
if new_message.reply_markup or old_message.has_reply_keyboard:
132+
if (
133+
(new_message.text != old_message.text) or
134+
# we cannot actually compare reply keyboards
135+
(new_message.reply_markup or old_message.has_reply_keyboard) or
136+
# we do not know if link preview changed
137+
new_message.link_preview_options
138+
):
136139
return True
137140

138141
if self.had_media(old_message) != self.need_media(new_message):
@@ -348,7 +351,7 @@ async def edit_text(
348351
text=new_message.text,
349352
reply_markup=new_message.reply_markup,
350353
parse_mode=new_message.parse_mode,
351-
disable_web_page_preview=new_message.disable_web_page_preview,
354+
link_preview_options=new_message.link_preview_options,
352355
)
353356

354357
async def edit_media(
@@ -363,7 +366,6 @@ async def edit_media(
363366
caption=new_message.text,
364367
reply_markup=new_message.reply_markup,
365368
parse_mode=new_message.parse_mode,
366-
disable_web_page_preview=new_message.disable_web_page_preview,
367369
media=await self.get_media_source(new_message.media, bot),
368370
**new_message.media.kwargs,
369371
)
@@ -392,9 +394,9 @@ async def send_text(self, bot: Bot, new_message: NewMessage) -> Message:
392394
text=new_message.text,
393395
message_thread_id=new_message.thread_id,
394396
business_connection_id=new_message.business_connection_id,
395-
disable_web_page_preview=new_message.disable_web_page_preview,
396397
reply_markup=new_message.reply_markup,
397398
parse_mode=new_message.parse_mode,
399+
link_preview_options=new_message.link_preview_options,
398400
)
399401

400402
async def send_media(self, bot: Bot, new_message: NewMessage) -> Message:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__all__ = ["LinkPreviewBase", "LinkPreview"]
2+
3+
from .base import LinkPreview, LinkPreviewBase

0 commit comments

Comments
 (0)