Passed
Push — master ( 7ddc7a...97a1fc )
by torrua
02:14 queued 01:15
created

TelegramWord._get_delimiter()   A

Complexity

Conditions 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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