Coverage for kpoisk_bot/format.py: 97%
92 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-19 21:13 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-19 21:13 +0000
1import textwrap
2from typing import Tuple
4from aiogram.types import (
5 InlineKeyboardButton,
6 InlineKeyboardMarkup,
7 InlineQueryResult,
8 InlineQueryResultArticle,
9 InputTextMessageContent,
10 LinkPreviewOptions,
11)
12from aiogram.utils.formatting import (
13 Bold,
14 HashTag,
15 Italic,
16 Text,
17 TextLink,
18 as_line,
19 as_list,
20)
21from kinopoisk_unofficial_api_client.models import Film, FilmSearchResponseFilms
23from .config import DESCRIPTION_LENGTH, KINOPOISK_ROOT
26class FilmFormatter:
27 film: FilmSearchResponseFilms | Film
29 def __init__(self, film: FilmSearchResponseFilms | Film) -> None:
30 self.film = film
32 def get_id(self) -> int:
33 return (
34 self.film.kinopoisk_id if isinstance(self.film, Film) else self.film.film_id
35 )
37 def is_valid(self) -> bool:
38 return bool(self.get_id())
40 def has_poster(self) -> bool:
41 return bool(self.film.poster_url) and bool(self.film.poster_url_preview)
43 def get_poster(self) -> str | None:
44 return self.film.poster_url if self.film.poster_url else None
46 def get_poster_preview(self) -> str | None:
47 return self.film.poster_url_preview if self.film.poster_url_preview else None
49 def get_title(self) -> str:
50 return self.film.name_ru or "Без названия"
52 def get_type(self) -> str:
53 DESCRIPTION = {
54 "FILM": "фильм",
55 "MINI_SERIES": "мини-сериал",
56 "TV_SERIES": "сериал",
57 "TV_SHOW": "ТВ-шоу",
58 "VIDEO": "видео",
59 }
60 return DESCRIPTION[str(self.film.type)]
62 def get_description(self) -> str:
63 return self.film.description or "Без описания"
65 def get_url(self) -> str:
66 return f"{KINOPOISK_ROOT}/film/{self.get_id()}/"
68 def get_year(self) -> str | None:
69 return (
70 str(self.film.year)
71 if self.film.year and isinstance(self.film, Film)
72 else (self.film.year if self.film.year else None)
73 )
75 def get_rating(self) -> Tuple[str, int] | None:
76 if isinstance(self.film, Film):
77 return (
78 (
79 str(self.film.rating_kinopoisk),
80 self.film.rating_kinopoisk_vote_count,
81 )
82 if self.film.rating_kinopoisk and self.film.rating_kinopoisk_vote_count
83 else None
84 )
85 return (
86 (self.film.rating, self.film.rating_vote_count)
87 if self.film.rating
88 and self.film.rating != "null"
89 and self.film.rating_vote_count
90 else None
91 )
93 def get_title_description(self) -> str:
94 return self.get_type() + ((", " + self.get_year()) if self.get_year() else "")
96 def inline_title(self) -> str:
97 return f"{self.get_title()} ({self.get_title_description()})"
99 def inline_description(self) -> str:
100 res = ""
101 if self.film.genres:
102 res += ", ".join(map(lambda g: g.genre, self.film.genres))
103 return res
105 def as_inline_content(self) -> InputTextMessageContent:
106 link_options = None
107 content = Text(self.inline_title())
108 if self.has_poster():
109 link_options = LinkPreviewOptions(
110 is_disabled=False, show_above_text=True, force_large_media=True
111 )
112 content = TextLink(self.inline_title(), url=self.get_poster())
113 return InputTextMessageContent(
114 link_preview_options=link_options,
115 **content.as_kwargs(text_key="message_text"),
116 )
118 def create_inline_markup(self) -> InlineKeyboardMarkup:
119 return InlineKeyboardMarkup(
120 inline_keyboard=[
121 [
122 InlineKeyboardButton(
123 text="Посмотреть на КиноПоиске",
124 url=self.get_url(),
125 )
126 ]
127 ]
128 )
130 def as_inline(self) -> InlineQueryResult:
131 return InlineQueryResultArticle(
132 id=str(self.get_id()),
133 title=self.inline_title(),
134 description=self.inline_description(),
135 thumbnail_url=self.get_poster_preview(),
136 input_message_content=self.as_inline_content(),
137 reply_markup=self.create_inline_markup(),
138 )
140 def title_component(self) -> Text:
141 return as_line(
142 Bold("Название:"),
143 TextLink(self.get_title(), url=self.get_url()),
144 Text(f"({self.get_title_description()})"),
145 sep=" ",
146 )
148 def genres_component(self) -> Text | None:
149 if self.film.genres:
150 return as_line(
151 Bold("Жанры:"),
152 as_line(
153 *[HashTag(f"#{g.genre}") for g in self.film.genres],
154 end="",
155 sep=", ",
156 ),
157 sep=" ",
158 )
159 return None
161 def rating_component(self) -> Text | None:
162 rating_pair = self.get_rating()
163 if rating_pair:
164 rating, vote_count = rating_pair
165 return as_line(
166 Bold("Рейтинг:"),
167 rating,
168 Italic(f"({vote_count} оценок)"),
169 sep=" ",
170 )
171 return None
173 def length_component(self) -> Text | None:
174 if self.film.film_length:
175 return as_line(Bold("Продолжительность:"), Text(f"{self.film.film_length}m"), sep=" ")
176 return None
178 def description_component(self) -> Text:
179 return as_line(
180 Bold("Описание:"),
181 Text(
182 textwrap.shorten(
183 self.get_description(),
184 DESCRIPTION_LENGTH,
185 placeholder="...",
186 )
187 ),
188 sep=" ",
189 )
191 def text_message_content(self) -> Text:
192 components = [self.title_component()]
193 if genres_component := self.genres_component():
194 components.append(genres_component)
195 if rating_component := self.rating_component():
196 components.append(rating_component)
197 if length_component := self.length_component():
198 components.append(length_component)
199 components.append(Text("\n"))
200 components.append(self.description_component())
202 return as_list(*components, sep="")
204 def as_text_message(self) -> InputTextMessageContent:
205 link_options = None
206 if self.has_poster():
207 link_options = LinkPreviewOptions(
208 is_disabled=False, url=self.get_poster(), show_above_text=True
209 )
211 content = self.text_message_content()
212 return InputTextMessageContent(
213 link_preview_options=link_options,
214 **content.as_kwargs(text_key="message_text"),
215 )
217 def create_result_markup(self) -> InlineKeyboardMarkup:
218 return InlineKeyboardMarkup(
219 inline_keyboard=[
220 [
221 InlineKeyboardButton(
222 text="Посмотреть на КиноПоиске",
223 url=self.get_url(),
224 )
225 ]
226 ]
227 )