Passed
Push — master ( 605616...3ced04 )
by torrua
01:17
created

app.bot.telegram.models   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 323
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 37
eloc 182
dl 0
loc 323
rs 9.44
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A TelegramWord.format_rank() 0 2 2
A TelegramWord.format_authors() 0 5 2
A TelegramDefinition.export() 0 22 3
A TelegramWord.format_year() 0 2 2
A TelegramWord.format_affixes() 0 5 2
A TelegramWord.translation_by_key() 0 26 2
A TelegramWord.by_request() 0 12 2
A TelegramWord._keyboard_hide() 0 15 1
A TelegramWord.keyboard_cpx() 0 33 4
A TelegramWord._keyboard_show() 0 18 1
A TelegramWord._get_delimiter() 0 14 4
A TelegramWord.send_card_to_user() 0 11 1
A TelegramWord._keyboard_data() 0 20 1
A TelegramWord.export_as_str() 0 18 2
A TelegramWord._keyboard_navi() 0 38 3
A TelegramWord.format_origin() 0 7 3
A TelegramWord.get_definitions() 0 15 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A kb_close() 0 5 1
1
# -*- coding: utf-8 -*-
2
"""Model of LOD database for Telegram"""
3
4
from collections import defaultdict
5
6
from callbaker import callback_from_info
7
from keyboa import Keyboa
8
from loglan_core.addons.definition_selector import DefinitionSelector
9
from loglan_core.addons.word_selector import WordSelector
10
from loglan_core import Word, Definition
11
12
from app.bot.telegram import MIN_NUMBER_OF_BUTTONS
13
from app.bot.telegram.variables import (
14
    t,
15
    cbd,
16
    mark_action,
17
    mark_entity,
18
    mark_record_id,
19
    mark_slice_start,
20
    action_predy_send_card,
21
    entity_predy,
22
    action_predy_kb_cpx_show,
23
    action_predy_kb_cpx_hide,
24
)
25
26
27
class TelegramDefinition(Definition):
28
    """Definition class extensions for Telegram"""
29
30
    def export(self):
31
        """
32
        Convert definition's data to str for sending as a telegram messages
33
        :return: Adopted for posting in telegram string
34
        """
35
        d_usage = f"<b>{self.usage.replace('%', '—')}</b> " if self.usage else ""
36
        d_grammar = (
37
            f"({self.slots if self.slots is not None else ''}{self.grammar_code}) "
38
        )
39
        d_body = (
40
            self.body.replace("<", "&#60;")
41
            .replace(">", "&#62;")
42
            .replace("«", "<i>")
43
            .replace("»", "</i>")
44
            .replace("{", "<code>")
45
            .replace("}", "</code>")
46
            .replace("....", "….")
47
            .replace("...", "…")
48
        )
49
50
        d_case_tags = f" [{self.case_tags}]" if self.case_tags else ""
51
        return f"{d_usage}{d_grammar}{d_body}{d_case_tags}"
52
53
54
class TelegramWord(Word):
55
    """Word class extensions for Telegram"""
56
57
    def format_affixes(self):
58
        return (
59
            f" ({' '.join([w.name for w in self.affixes]).strip()}"
60
            if self.affixes
61
            else ""
62
        )
63
64
    def format_year(self):
65
        return "'" + str(self.year.year)[-2:] + " " if self.year else ""
66
67
    def format_origin(self):
68
        if self.origin or self.origin_x:
69
            return (
70
                f"\n<i>&#60;{self.origin}"
71
                f"{' = ' + self.origin_x if self.origin_x else ''}&#62;</i>"
72
            )
73
        return ""
74
75
    def format_authors(self):
76
        return (
77
            "/".join([a.abbreviation for a in self.authors]) + " "
78
            if self.authors
79
            else ""
80
        )
81
82
    def format_rank(self):
83
        return self.rank + " " if self.rank else ""
84
85
    def export_as_str(self, session) -> str:
86
        """
87
        Convert word's data to str for sending as a telegram messages
88
        :return: List of str with technical info, definitions, used_in part
89
        """
90
        w_affixes = self.format_affixes()
91
        w_match = self.match + " " if self.match else ""
92
        w_year = self.format_year()
93
        w_orig = self.format_origin()
94
        w_authors = self.format_authors()
95
        w_type = self.type.type + " "
96
        w_rank = self.format_rank()
97
98
        word_str = (
99
            f"<b>{self.name}</b>{w_affixes},"
100
            f"\n{w_match}{w_type}{w_authors}{w_year}{w_rank}{w_orig}"
101
        )
102
        return f"{word_str}\n\n{self.get_definitions(session=session)}"
103
104
    def get_definitions(self, session) -> str:
105
        """
106
        Get all definitions of the word
107
        :param session: Session
108
        :return: List of Definition objects ordered by position
109
        """
110
111
        definitions = (
112
            DefinitionSelector(class_=TelegramDefinition)
113
            .filter(TelegramDefinition.word_id == self.id)
114
            .order_by(TelegramDefinition.position.asc())
115
            .all(session=session)
116
        )
117
118
        return "\n\n".join([d.export() for d in definitions])
119
120
    @classmethod
121
    def translation_by_key(cls, session, request: str, language: str = None) -> str:
122
        """
123
        We get information about loglan words by key in a foreign language
124
        :param session: Session
125
        :param request: Requested string
126
        :param language: Key language
127
        :return: Search results string formatted for sending to Telegram
128
        """
129
130
        result = defaultdict(list)
131
        definitions = (
132
            DefinitionSelector(class_=TelegramDefinition)
133
            .by_key(key=request, language=language)
134
            .all(session=session)
135
        )
136
137
        for definition in definitions:
138
            result[definition.source_word.name].append(definition.export())
139
140
        new = "\n"
141
        word_items = [
142
            f"/{word_name},\n{new.join(definitions)}\n"
143
            for word_name, definitions in result.items()
144
        ]
145
        return new.join(word_items).strip()
146
147
    def _keyboard_navi(self, index_start: int, index_end: int, delimiter: int):
148
        """
149
        :param index_start:
150
        :param index_end:
151
        :param delimiter:
152
        :return:
153
        """
154
        text_arrow_back = "\U0000276E" * 2
155
        text_arrow_forward = "\U0000276F" * 2
156
        button_back, button_forward = None, None
157
158
        common_data = {
159
            mark_entity: entity_predy,
160
            mark_action: action_predy_kb_cpx_show,
161
            mark_record_id: self.id,
162
        }
163
        if index_start != 0:
164
            cbd_predy_kb_cpx_back = {
165
                **common_data,
166
                mark_slice_start: index_start - delimiter,
167
            }
168
            button_back = {
169
                t: text_arrow_back,
170
                cbd: callback_from_info(cbd_predy_kb_cpx_back),
171
            }
172
173
        if index_end != len(self.complexes):
174
            cbd_predy_kb_cpx_forward = {
175
                **common_data,
176
                mark_slice_start: index_end,
177
            }
178
            button_forward = {
179
                t: text_arrow_forward,
180
                cbd: callback_from_info(cbd_predy_kb_cpx_forward),
181
            }
182
183
        nav_row = [b for b in [button_back, button_forward] if b]
184
        return Keyboa(nav_row, items_in_row=2)()
185
186
    def _keyboard_hide(self, total_number_of_complexes: int):
187
        """
188
        :param total_number_of_complexes:
189
        :return:
190
        """
191
        text_cpx_hide = f"Hide Complex{'es' if total_number_of_complexes > 1 else ''}"
192
        cbd_predy_kb_cpx_hide = {
193
            mark_entity: entity_predy,
194
            mark_action: action_predy_kb_cpx_hide,
195
            mark_record_id: self.id,
196
        }
197
        button_predy_kb_cpx_hide = [
198
            {t: text_cpx_hide, cbd: callback_from_info(cbd_predy_kb_cpx_hide)},
199
        ]
200
        return Keyboa(button_predy_kb_cpx_hide)()
201
202
    def _keyboard_show(self, total_number_of_complexes: int):
203
        """
204
        :param total_number_of_complexes:
205
        :return:
206
        """
207
        text_cpx_show = (
208
            f"Show Complex{'es' if total_number_of_complexes > 1 else ''}"
209
            f" ({total_number_of_complexes})"
210
        )
211
        cbd_predy_kb_cpx_show = {
212
            mark_entity: entity_predy,
213
            mark_action: action_predy_kb_cpx_show,
214
            mark_record_id: self.id,
215
        }
216
        button_show = [
217
            {t: text_cpx_show, cbd: callback_from_info(cbd_predy_kb_cpx_show)},
218
        ]
219
        return Keyboa.combine((Keyboa(button_show)(), kb_close()))
220
221
    @staticmethod
222
    def _get_delimiter(total_number_of_complexes: int):
223
        """
224
        :param total_number_of_complexes:
225
        :return:
226
        """
227
        allowed_range = list(range(MIN_NUMBER_OF_BUTTONS, MIN_NUMBER_OF_BUTTONS + 11))
228
        lst = [(total_number_of_complexes % i, i) for i in allowed_range]
229
        delimiter = min(lst, key=lambda x: abs(x[0] - MIN_NUMBER_OF_BUTTONS))[1]
230
        for i in lst:
231
            if i[0] == 0:
232
                delimiter = i[1]
233
                break
234
        return delimiter
235
236
    @staticmethod
237
    def _keyboard_data(current_complexes: list):
238
        """
239
        :param current_complexes:
240
        :return:
241
        """
242
        cpx_items = [
243
            {
244
                t: cpx.name,
245
                cbd: callback_from_info(
246
                    {
247
                        mark_entity: entity_predy,
248
                        mark_action: action_predy_send_card,
249
                        mark_record_id: cpx.id,
250
                    }
251
                ),
252
            }
253
            for cpx in current_complexes
254
        ]
255
        return Keyboa(items=cpx_items, alignment=True, items_in_row=4)()
256
257
    def keyboard_cpx(self, show_list: bool = False, slice_start: int = 0):
258
        """
259
        :param show_list:
260
        :param slice_start:
261
        :return:
262
        """
263
264
        total_num_of_cpx = len(self.complexes)
265
266
        if not total_num_of_cpx:
267
            return kb_close()
268
269
        if not show_list:
270
            return self._keyboard_show(total_num_of_cpx)
271
272
        current_delimiter = self._get_delimiter(total_num_of_cpx)
273
274
        kb_cpx_hide = self._keyboard_hide(total_num_of_cpx)
275
276
        last_allowed_item = slice_start + current_delimiter
277
        slice_end = min(last_allowed_item, total_num_of_cpx)
278
279
        current_cpx_set = self.complexes[slice_start:slice_end]
280
        kb_cpx_data = self._keyboard_data(current_cpx_set)
281
282
        kb_cpx_nav = None
283
284
        if total_num_of_cpx > current_delimiter:
285
            kb_cpx_nav = self._keyboard_navi(slice_start, slice_end, current_delimiter)
286
287
        kb_combo = (kb_cpx_hide, kb_cpx_data, kb_cpx_nav, kb_close())
288
289
        return Keyboa.combine(kb_combo)
290
291
    def send_card_to_user(self, session, bot, user_id: int | str):
292
        """
293
        :param session:
294
        :param bot:
295
        :param user_id:
296
        :return:
297
        """
298
        bot.send_message(
299
            chat_id=user_id,
300
            text=self.export_as_str(session),
301
            reply_markup=self.keyboard_cpx(),
302
        )
303
304
    @classmethod
305
    def by_request(cls, session, request: str) -> list:
306
        """
307
        :param session:
308
        :param request:
309
        :return:
310
        """
311
        if isinstance(request, int):
312
            return [
313
                cls.get_by_id(session, request),
314
            ]
315
        return WordSelector(TelegramWord).by_name(request).all(session)
316
317
318
def kb_close():
319
    """
320
    :return:
321
    """
322
    return Keyboa({t: "Close", cbd: "close"})()
323