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

1import textwrap 

2from typing import Tuple 

3 

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 

22 

23from .config import DESCRIPTION_LENGTH, KINOPOISK_ROOT 

24 

25 

26class FilmFormatter: 

27 film: FilmSearchResponseFilms | Film 

28 

29 def __init__(self, film: FilmSearchResponseFilms | Film) -> None: 

30 self.film = film 

31 

32 def get_id(self) -> int: 

33 return ( 

34 self.film.kinopoisk_id if isinstance(self.film, Film) else self.film.film_id 

35 ) 

36 

37 def is_valid(self) -> bool: 

38 return bool(self.get_id()) 

39 

40 def has_poster(self) -> bool: 

41 return bool(self.film.poster_url) and bool(self.film.poster_url_preview) 

42 

43 def get_poster(self) -> str | None: 

44 return self.film.poster_url if self.film.poster_url else None 

45 

46 def get_poster_preview(self) -> str | None: 

47 return self.film.poster_url_preview if self.film.poster_url_preview else None 

48 

49 def get_title(self) -> str: 

50 return self.film.name_ru or "Без названия" 

51 

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)] 

61 

62 def get_description(self) -> str: 

63 return self.film.description or "Без описания" 

64 

65 def get_url(self) -> str: 

66 return f"{KINOPOISK_ROOT}/film/{self.get_id()}/" 

67 

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 ) 

74 

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 ) 

92 

93 def get_title_description(self) -> str: 

94 return self.get_type() + ((", " + self.get_year()) if self.get_year() else "") 

95 

96 def inline_title(self) -> str: 

97 return f"{self.get_title()} ({self.get_title_description()})" 

98 

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 

104 

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 ) 

117 

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 ) 

129 

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 ) 

139 

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 ) 

147 

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 

160 

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 

172 

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 

177 

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 ) 

190 

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()) 

201 

202 return as_list(*components, sep="") 

203 

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 ) 

210 

211 content = self.text_message_content() 

212 return InputTextMessageContent( 

213 link_preview_options=link_options, 

214 **content.as_kwargs(text_key="message_text"), 

215 ) 

216 

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 )