Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link preview #447

Merged
merged 6 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@
# import os
# import sys
# sys.path.insert(0, os.path.abspath("."))

import datetime

# -- Project information -----------------------------------------------------

project = "aiogram-dialog"
copyright = f"{datetime.date.today().year}, Tishka17"
copyright = "%Y, Tishka17"
author = "Tishka17"
master_doc = "index"

Expand Down
8 changes: 5 additions & 3 deletions docs/widgets/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ Widgets and Rendering
Base information
********************

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

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

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>`.

Expand All @@ -37,5 +38,6 @@ Also there are 2 general types:
keyboard/index
input/index
media/index
link_preview/index
hiding/index
custom_widgets/index
custom_widgets/index
27 changes: 27 additions & 0 deletions docs/widgets/link_preview/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from aiogram.filters.state import State, StatesGroup

from aiogram_dialog import Window
from aiogram_dialog.widgets.link_preview import LinkPreview
from aiogram_dialog.widgets.text import Const


class SG(StatesGroup):
MAIN = State()
SECOND = State()


window = Window(
Const("https://nplus1.ru/news/2024/05/23/voyager-1-science-data"),
LinkPreview(is_disabled=True),
state=SG.MAIN,
)

second_window = Window(
Const("some text"),
LinkPreview(
url=Const("https://nplus1.ru/news/2024/05/23/voyager-1-science-data"),
prefer_small_media=True,
show_above_text=True,
),
state=SG.MAIN,
)
23 changes: 23 additions & 0 deletions docs/widgets/link_preview/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.. _link_preview:

LinkPreview
*************

The **LinkPreview** widget is used to manage link previews in messages.

Parameters:

* ``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.
* ``is_disabled``: that controls whether the link preview is displayed. If ``True``, the preview will be disabled.
* ``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.
* ``prefer_large_media``: that controls if the media in the link preview should be enlarged. Ignored if media size change is not supported.
* ``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.


Code example:

.. literalinclude:: ./example.py

.. autoclass:: aiogram_dialog.widgets.link_preview.LinkPreview
:special-members: __init__
:members: render_link_preview, _render_link_preview
2 changes: 2 additions & 0 deletions example/loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
setup_dialogs,
)
from aiogram_dialog.widgets.kbd import Button
from aiogram_dialog.widgets.link_preview import LinkPreview
from aiogram_dialog.widgets.text import Const, Multi, Progress

API_TOKEN = os.getenv("BOT_TOKEN")
Expand Down Expand Up @@ -75,6 +76,7 @@ async def background(callback: CallbackQuery, manager: BaseDialogManager):
Window(
Const("Press button to start processing"),
Button(Const("Start"), id="start", on_click=start_bg),
LinkPreview(url=Const("http://ya.ru")),
state=MainSG.main,
),
)
Expand Down
2 changes: 2 additions & 0 deletions example/mega/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from bot_dialogs.calendar import calendar_dialog
from bot_dialogs.counter import counter_dialog
from bot_dialogs.layouts import layouts_dialog
from bot_dialogs.link_preview import link_preview_dialog
from bot_dialogs.main import main_dialog
from bot_dialogs.mutltiwidget import multiwidget_dialog
from bot_dialogs.reply_buttons import reply_kbd_dialog
Expand Down Expand Up @@ -68,6 +69,7 @@ async def on_unknown_intent(event: ErrorEvent, dialog_manager: DialogManager):
multiwidget_dialog,
switch_dialog,
reply_kbd_dialog,
link_preview_dialog,
)


Expand Down
83 changes: 83 additions & 0 deletions example/mega/bot_dialogs/link_preview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from aiogram_dialog import (
Dialog,
Window,
)
from aiogram_dialog.widgets.kbd import SwitchTo
from aiogram_dialog.widgets.link_preview import LinkPreview
from aiogram_dialog.widgets.text import Const, Format

from . import states
from .common import MAIN_MENU_BUTTON


async def links_getter(**_):
return {
"main": "https://en.wikipedia.org/wiki/HTML_element",
"photo": "https://en.wikipedia.org/wiki/Hyperlink",
}


LinkPreview_MAIN_MENU_BUTTON = SwitchTo(
text=Const("Back"), id="back", state=states.LinkPreview.MAIN,
)
COMMON_TEXT = Format(
"This is demo of different link preview options.\n"
"Link in text: {main}\n"
"Link in preview can be different\n\n"
"Current mode is:",
)

BACK = SwitchTo(Const("back"), "_back", states.LinkPreview.MAIN)

link_preview_dialog = Dialog(
Window(
COMMON_TEXT,
Format("Default"),
SwitchTo(
Const("disable"), "_disable", states.LinkPreview.IS_DISABLED,
),
SwitchTo(
Const("prefer small media"), "_prefer_small_media",
states.LinkPreview.SMALL_MEDIA,
),
SwitchTo(
Const("prefer large media"), "_prefer_large_media",
states.LinkPreview.LARGE_MEDIA,
),
SwitchTo(
Const("show above text"), "_show_above_text",
states.LinkPreview.SHOW_ABOVE_TEXT,
),
MAIN_MENU_BUTTON,
state=states.LinkPreview.MAIN,
),
Window(
COMMON_TEXT,
Const("is_disabled=True"),
LinkPreview(is_disabled=True),
LinkPreview_MAIN_MENU_BUTTON,
state=states.LinkPreview.IS_DISABLED,
),
Window(
COMMON_TEXT,
Const("prefer_small_media=True"),
LinkPreview(Format("{photo}"), prefer_small_media=True),
LinkPreview_MAIN_MENU_BUTTON,
state=states.LinkPreview.SMALL_MEDIA,
),
Window(
COMMON_TEXT,
Const("prefer_large_media=True"),
LinkPreview(Format("{photo}"), prefer_large_media=True),
LinkPreview_MAIN_MENU_BUTTON,
state=states.LinkPreview.LARGE_MEDIA,
),
Window(
COMMON_TEXT,
Const("show_above_text=True"),
LinkPreview(Format("{photo}"), show_above_text=True),
LinkPreview_MAIN_MENU_BUTTON,
state=states.LinkPreview.SHOW_ABOVE_TEXT,
),
getter=links_getter,
)
5 changes: 5 additions & 0 deletions example/mega/bot_dialogs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
id="switch",
state=states.Switch.MAIN,
),
Start(
text=Const("🔗 Link Preview"),
id="linkpreview",
state=states.LinkPreview.MAIN,
),
Start(
text=Const("⌨️ Reply keyboard"),
id="reply",
Expand Down
8 changes: 8 additions & 0 deletions example/mega/bot_dialogs/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,11 @@ class Switch(StatesGroup):
MAIN = State()
INPUT = State()
LAST = State()


class LinkPreview(StatesGroup):
MAIN = State()
IS_DISABLED = State()
SMALL_MEDIA = State()
LARGE_MEDIA = State()
SHOW_ABOVE_TEXT = State()
3 changes: 2 additions & 1 deletion 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 @@ -43,5 +44,5 @@ class NewMessage:
reply_markup: Optional[MarkupVariant] = None
parse_mode: Optional[str] = None
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
16 changes: 9 additions & 7 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 @@ -348,7 +351,7 @@ async def edit_text(
text=new_message.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 All @@ -363,7 +366,6 @@ async def edit_media(
caption=new_message.text,
reply_markup=new_message.reply_markup,
parse_mode=new_message.parse_mode,
disable_web_page_preview=new_message.disable_web_page_preview,
media=await self.get_media_source(new_message.media, bot),
**new_message.media.kwargs,
)
Expand Down Expand Up @@ -392,9 +394,9 @@ async def send_text(self, bot: Bot, new_message: NewMessage) -> Message:
text=new_message.text,
message_thread_id=new_message.thread_id,
business_connection_id=new_message.business_connection_id,
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
Loading
Loading