smart-bot-factory 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. smart_bot_factory/__init__.py +3 -0
  2. smart_bot_factory/admin/__init__.py +18 -0
  3. smart_bot_factory/admin/admin_events.py +1223 -0
  4. smart_bot_factory/admin/admin_logic.py +553 -0
  5. smart_bot_factory/admin/admin_manager.py +156 -0
  6. smart_bot_factory/admin/admin_tester.py +157 -0
  7. smart_bot_factory/admin/timeout_checker.py +547 -0
  8. smart_bot_factory/aiogram_calendar/__init__.py +14 -0
  9. smart_bot_factory/aiogram_calendar/common.py +64 -0
  10. smart_bot_factory/aiogram_calendar/dialog_calendar.py +259 -0
  11. smart_bot_factory/aiogram_calendar/schemas.py +99 -0
  12. smart_bot_factory/aiogram_calendar/simple_calendar.py +224 -0
  13. smart_bot_factory/analytics/analytics_manager.py +414 -0
  14. smart_bot_factory/cli.py +806 -0
  15. smart_bot_factory/config.py +258 -0
  16. smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
  17. smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
  18. smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
  19. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
  20. smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
  21. smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
  22. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
  23. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
  24. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
  25. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +133 -0
  26. smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
  27. smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
  28. smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
  29. smart_bot_factory/configs/growthmed-october-24/welcome_file//342/225/250/320/267/342/225/250/342/225/241/342/225/250/342/225/221 /342/225/250/342/225/227/342/225/250/342/225/225/342/225/244/320/221/342/225/244/320/222 /342/225/250/342/224/220/342/225/250/342/225/233 152/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/225/225 323/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/224/244/342/225/250/342/225/227/342/225/244/320/237 /342/225/250/342/225/235/342/225/250/342/225/241/342/225/250/342/224/244/342/225/250/342/225/225/342/225/244/320/226/342/225/250/342/225/225/342/225/250/342/225/234/342/225/244/320/233.pdf +0 -0
  30. smart_bot_factory/core/bot_utils.py +1108 -0
  31. smart_bot_factory/core/conversation_manager.py +653 -0
  32. smart_bot_factory/core/decorators.py +2464 -0
  33. smart_bot_factory/core/message_sender.py +729 -0
  34. smart_bot_factory/core/router.py +347 -0
  35. smart_bot_factory/core/router_manager.py +218 -0
  36. smart_bot_factory/core/states.py +27 -0
  37. smart_bot_factory/creation/__init__.py +7 -0
  38. smart_bot_factory/creation/bot_builder.py +1093 -0
  39. smart_bot_factory/creation/bot_testing.py +1122 -0
  40. smart_bot_factory/dashboard/__init__.py +3 -0
  41. smart_bot_factory/event/__init__.py +7 -0
  42. smart_bot_factory/handlers/handlers.py +2013 -0
  43. smart_bot_factory/integrations/langchain_openai.py +542 -0
  44. smart_bot_factory/integrations/openai_client.py +513 -0
  45. smart_bot_factory/integrations/supabase_client.py +1678 -0
  46. smart_bot_factory/memory/__init__.py +8 -0
  47. smart_bot_factory/memory/memory_manager.py +299 -0
  48. smart_bot_factory/memory/static_memory.py +214 -0
  49. smart_bot_factory/message/__init__.py +56 -0
  50. smart_bot_factory/rag/__init__.py +5 -0
  51. smart_bot_factory/rag/decorators.py +29 -0
  52. smart_bot_factory/rag/router.py +54 -0
  53. smart_bot_factory/rag/templates/__init__.py +3 -0
  54. smart_bot_factory/rag/templates/create_table.sql +7 -0
  55. smart_bot_factory/rag/templates/create_table_and_function_template.py +94 -0
  56. smart_bot_factory/rag/templates/match_function.sql +61 -0
  57. smart_bot_factory/rag/templates/match_services_template.py +82 -0
  58. smart_bot_factory/rag/vectorstore.py +449 -0
  59. smart_bot_factory/router/__init__.py +10 -0
  60. smart_bot_factory/setup_checker.py +512 -0
  61. smart_bot_factory/supabase/__init__.py +7 -0
  62. smart_bot_factory/supabase/client.py +631 -0
  63. smart_bot_factory/utils/__init__.py +11 -0
  64. smart_bot_factory/utils/debug_routing.py +114 -0
  65. smart_bot_factory/utils/prompt_loader.py +529 -0
  66. smart_bot_factory/utils/tool_router.py +68 -0
  67. smart_bot_factory/utils/user_prompt_loader.py +55 -0
  68. smart_bot_factory/utm_link_generator.py +123 -0
  69. smart_bot_factory-1.1.1.dist-info/METADATA +1135 -0
  70. smart_bot_factory-1.1.1.dist-info/RECORD +73 -0
  71. smart_bot_factory-1.1.1.dist-info/WHEEL +4 -0
  72. smart_bot_factory-1.1.1.dist-info/entry_points.txt +2 -0
  73. smart_bot_factory-1.1.1.dist-info/licenses/LICENSE +24 -0
@@ -0,0 +1,259 @@
1
+ import calendar
2
+ from datetime import datetime
3
+
4
+ from aiogram.types import (CallbackQuery, InlineKeyboardButton,
5
+ InlineKeyboardMarkup)
6
+
7
+ from .common import GenericCalendar
8
+ from .schemas import (DialogCalAct, DialogCalendarCallback, highlight,
9
+ superscript)
10
+
11
+
12
+ class DialogCalendar(GenericCalendar):
13
+
14
+ ignore_callback = DialogCalendarCallback(
15
+ act=DialogCalAct.ignore
16
+ ).pack() # placeholder for no answer buttons
17
+
18
+ async def _get_month_kb(self, year: int):
19
+ """Creates an inline keyboard with months for specified year"""
20
+
21
+ today = datetime.now()
22
+ now_month, now_year = today.month, today.year
23
+ now_year = today.year
24
+
25
+ kb = []
26
+ # first row with year button
27
+ years_row = []
28
+ years_row.append(
29
+ InlineKeyboardButton(
30
+ text=self._labels.cancel_caption,
31
+ callback_data=DialogCalendarCallback(
32
+ act=DialogCalAct.cancel, year=year, month=1, day=1
33
+ ).pack(),
34
+ )
35
+ )
36
+ years_row.append(
37
+ InlineKeyboardButton(
38
+ text=str(year) if year != today.year else highlight(year),
39
+ callback_data=DialogCalendarCallback(
40
+ act=DialogCalAct.start, year=year, month=-1, day=-1
41
+ ).pack(),
42
+ )
43
+ )
44
+ years_row.append(
45
+ InlineKeyboardButton(text=" ", callback_data=self.ignore_callback)
46
+ )
47
+ kb.append(years_row)
48
+ # two rows with 6 months buttons
49
+ month6_row = []
50
+
51
+ def highlight_month():
52
+ month_str = self._labels.months[month - 1]
53
+ if now_month == month and now_year == year:
54
+ return highlight(month_str)
55
+ return month_str
56
+
57
+ for month in range(1, 7):
58
+ month6_row.append(
59
+ InlineKeyboardButton(
60
+ text=highlight_month(),
61
+ callback_data=DialogCalendarCallback(
62
+ act=DialogCalAct.set_m, year=year, month=month, day=-1
63
+ ).pack(),
64
+ )
65
+ )
66
+ month12_row = []
67
+
68
+ for month in range(7, 13):
69
+ month12_row.append(
70
+ InlineKeyboardButton(
71
+ text=highlight_month(),
72
+ callback_data=DialogCalendarCallback(
73
+ act=DialogCalAct.set_m, year=year, month=month, day=-1
74
+ ).pack(),
75
+ )
76
+ )
77
+
78
+ kb.append(month6_row)
79
+ kb.append(month12_row)
80
+ return InlineKeyboardMarkup(row_width=6, inline_keyboard=kb)
81
+
82
+ async def _get_days_kb(self, year: int, month: int):
83
+ """Creates an inline keyboard with calendar days of month for specified year and month"""
84
+
85
+ today = datetime.now()
86
+ now_weekday = self._labels.days_of_week[today.weekday()]
87
+ now_month, now_year, now_day = today.month, today.year, today.day
88
+
89
+ def highlight_month():
90
+ month_str = self._labels.months[month - 1]
91
+ if now_month == month and now_year == year:
92
+ return highlight(month_str)
93
+ return month_str
94
+
95
+ def highlight_weekday():
96
+ if now_month == month and now_year == year and now_weekday == weekday:
97
+ return highlight(weekday)
98
+ return weekday
99
+
100
+ def format_day_string():
101
+ date_to_check = datetime(year, month, day)
102
+ if self.min_date and date_to_check < self.min_date:
103
+ return superscript(str(day))
104
+ elif self.max_date and date_to_check > self.max_date:
105
+ return superscript(str(day))
106
+ return str(day)
107
+
108
+ def highlight_day():
109
+ day_string = format_day_string()
110
+ if now_month == month and now_year == year and now_day == day:
111
+ return highlight(day_string)
112
+ return day_string
113
+
114
+ kb = []
115
+ nav_row = []
116
+ nav_row.append(
117
+ InlineKeyboardButton(
118
+ text=self._labels.cancel_caption,
119
+ callback_data=DialogCalendarCallback(
120
+ act=DialogCalAct.cancel, year=year, month=1, day=1
121
+ ).pack(),
122
+ )
123
+ )
124
+ nav_row.append(
125
+ InlineKeyboardButton(
126
+ text=str(year) if year != now_year else highlight(year),
127
+ callback_data=DialogCalendarCallback(
128
+ act=DialogCalAct.start, year=year, month=-1, day=-1
129
+ ).pack(),
130
+ )
131
+ )
132
+ nav_row.append(
133
+ InlineKeyboardButton(
134
+ text=highlight_month(),
135
+ callback_data=DialogCalendarCallback(
136
+ act=DialogCalAct.set_y, year=year, month=-1, day=-1
137
+ ).pack(),
138
+ )
139
+ )
140
+ kb.append(nav_row)
141
+
142
+ week_days_labels_row = []
143
+ for weekday in self._labels.days_of_week:
144
+ week_days_labels_row.append(
145
+ InlineKeyboardButton(
146
+ text=highlight_weekday(), callback_data=self.ignore_callback
147
+ )
148
+ )
149
+ kb.append(week_days_labels_row)
150
+
151
+ month_calendar = calendar.monthcalendar(year, month)
152
+
153
+ for week in month_calendar:
154
+ days_row = []
155
+ for day in week:
156
+ if day == 0:
157
+ days_row.append(
158
+ InlineKeyboardButton(
159
+ text=" ", callback_data=self.ignore_callback
160
+ )
161
+ )
162
+ continue
163
+ days_row.append(
164
+ InlineKeyboardButton(
165
+ text=highlight_day(),
166
+ callback_data=DialogCalendarCallback(
167
+ act=DialogCalAct.day, year=year, month=month, day=day
168
+ ).pack(),
169
+ )
170
+ )
171
+ kb.append(days_row)
172
+ return InlineKeyboardMarkup(row_width=7, inline_keyboard=kb)
173
+
174
+ async def start_calendar(
175
+ self, year: int = datetime.now().year, month: int = None
176
+ ) -> InlineKeyboardMarkup:
177
+ today = datetime.now()
178
+ now_year = today.year
179
+
180
+ if month:
181
+ return await self._get_days_kb(year, month)
182
+ kb = []
183
+ # inline_kb = InlineKeyboardMarkup(row_width=5)
184
+ # first row - years
185
+ years_row = []
186
+ for value in range(year - 2, year + 3):
187
+ years_row.append(
188
+ InlineKeyboardButton(
189
+ text=str(value) if value != now_year else highlight(value),
190
+ callback_data=DialogCalendarCallback(
191
+ act=DialogCalAct.set_y, year=value, month=-1, day=-1
192
+ ).pack(),
193
+ )
194
+ )
195
+ kb.append(years_row)
196
+ # nav buttons
197
+ nav_row = []
198
+ nav_row.append(
199
+ InlineKeyboardButton(
200
+ text="<<",
201
+ callback_data=DialogCalendarCallback(
202
+ act=DialogCalAct.prev_y, year=year, month=-1, day=-1
203
+ ).pack(),
204
+ )
205
+ )
206
+ nav_row.append(
207
+ InlineKeyboardButton(
208
+ text=self._labels.cancel_caption,
209
+ callback_data=DialogCalendarCallback(
210
+ act=DialogCalAct.cancel, year=year, month=1, day=1
211
+ ).pack(),
212
+ )
213
+ )
214
+ nav_row.append(
215
+ InlineKeyboardButton(
216
+ text=">>",
217
+ callback_data=DialogCalendarCallback(
218
+ act=DialogCalAct.next_y, year=year, month=1, day=1
219
+ ).pack(),
220
+ )
221
+ )
222
+ kb.append(nav_row)
223
+ return InlineKeyboardMarkup(row_width=5, inline_keyboard=kb)
224
+
225
+ async def process_selection(
226
+ self, query: CallbackQuery, data: DialogCalendarCallback
227
+ ) -> tuple:
228
+ return_data = (False, None)
229
+ if data.act == DialogCalAct.ignore:
230
+ await query.answer(cache_time=60)
231
+ if data.act == DialogCalAct.set_y:
232
+ await query.message.edit_reply_markup(
233
+ reply_markup=await self._get_month_kb(int(data.year))
234
+ )
235
+ if data.act == DialogCalAct.prev_y:
236
+ new_year = int(data.year) - 5
237
+ await query.message.edit_reply_markup(
238
+ reply_markup=await self.start_calendar(year=new_year)
239
+ )
240
+ if data.act == DialogCalAct.next_y:
241
+ new_year = int(data.year) + 5
242
+ await query.message.edit_reply_markup(
243
+ reply_markup=await self.start_calendar(year=new_year)
244
+ )
245
+ if data.act == DialogCalAct.start:
246
+ await query.message.edit_reply_markup(
247
+ reply_markup=await self.start_calendar(int(data.year))
248
+ )
249
+ if data.act == DialogCalAct.set_m:
250
+ await query.message.edit_reply_markup(
251
+ reply_markup=await self._get_days_kb(int(data.year), int(data.month))
252
+ )
253
+ if data.act == DialogCalAct.day:
254
+
255
+ return await self.process_day_select(data, query)
256
+
257
+ if data.act == DialogCalAct.cancel:
258
+ await query.message.delete_reply_markup()
259
+ return return_data
@@ -0,0 +1,99 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+
4
+ from aiogram.filters.callback_data import CallbackData
5
+ from pydantic import BaseModel, Field, conlist
6
+
7
+
8
+ class SimpleCalAct(str, Enum):
9
+ ignore = "IGNORE"
10
+ prev_y = "PREV-YEAR"
11
+ next_y = "NEXT-YEAR"
12
+ prev_m = "PREV-MONTH"
13
+ next_m = "NEXT-MONTH"
14
+ cancel = "CANCEL"
15
+ today = "TODAY"
16
+ day = "DAY"
17
+
18
+
19
+ class DialogCalAct(str, Enum):
20
+ ignore = "IGNORE"
21
+ set_y = "SET-YEAR"
22
+ set_m = "SET-MONTH"
23
+ prev_y = "PREV-YEAR"
24
+ next_y = "NEXT-YEAR"
25
+ cancel = "CANCEL"
26
+ start = "START"
27
+ day = "SET-DAY"
28
+
29
+
30
+ class CalendarCallback(CallbackData, prefix="calendar"):
31
+ act: str
32
+ year: Optional[int] = None
33
+ month: Optional[int] = None
34
+ day: Optional[int] = None
35
+
36
+
37
+ class SimpleCalendarCallback(CalendarCallback, prefix="simple_calendar"):
38
+ act: SimpleCalAct
39
+
40
+
41
+ class DialogCalendarCallback(CalendarCallback, prefix="dialog_calendar"):
42
+ act: DialogCalAct
43
+
44
+
45
+ class CalendarLabels(BaseModel):
46
+ "Schema to pass labels for calendar. Can be used to put in different languages"
47
+
48
+ days_of_week: conlist(str, max_length=7, min_length=7) = [
49
+ "Пн",
50
+ "Вт",
51
+ "Ср",
52
+ "Чт",
53
+ "Пт",
54
+ "Сб",
55
+ "Вс",
56
+ ]
57
+ months: conlist(str, max_length=12, min_length=12) = [
58
+ "Янв",
59
+ "Фев",
60
+ "Мар",
61
+ "Апр",
62
+ "Май",
63
+ "Июн",
64
+ "Июл",
65
+ "Авг",
66
+ "Сен",
67
+ "Окт",
68
+ "Ноя",
69
+ "Дек",
70
+ ]
71
+ cancel_caption: str = Field(
72
+ default="Отмена", description="Caprion for Cancel button"
73
+ )
74
+ today_caption: str = Field(default="Сегодня", description="Caprion for Today button")
75
+
76
+
77
+ HIGHLIGHT_FORMAT = "[{}]"
78
+
79
+
80
+ def highlight(text):
81
+ return HIGHLIGHT_FORMAT.format(text)
82
+
83
+
84
+ def superscript(text):
85
+ normal = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-=()"
86
+ super_s = "ᴬᴮᶜᴰᴱᶠᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾQᴿˢᵀᵁⱽᵂˣʸᶻᵃᵇᶜᵈᵉᶠᵍʰᶦʲᵏˡᵐⁿᵒᵖ۹ʳˢᵗᵘᵛʷˣʸᶻ⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾"
87
+ output = ""
88
+ for i in text:
89
+ output += super_s[normal.index(i)] if i in normal else i
90
+ return output
91
+
92
+
93
+ def subscript(text):
94
+ normal = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-=()"
95
+ sub_s = "ₐ₈CDₑբGₕᵢⱼₖₗₘₙₒₚQᵣₛₜᵤᵥwₓᵧZₐ♭꜀ᑯₑբ₉ₕᵢⱼₖₗₘₙₒₚ૧ᵣₛₜᵤᵥwₓᵧ₂₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎"
96
+ output = ""
97
+ for i in text:
98
+ output += sub_s[normal.index(i)] if i in normal else i
99
+ return output
@@ -0,0 +1,224 @@
1
+ import calendar
2
+ from datetime import datetime, timedelta
3
+
4
+ from aiogram.types import (CallbackQuery, InlineKeyboardButton,
5
+ InlineKeyboardMarkup)
6
+
7
+ from .common import GenericCalendar
8
+ from .schemas import (SimpleCalAct, SimpleCalendarCallback, highlight,
9
+ superscript)
10
+
11
+
12
+ class SimpleCalendar(GenericCalendar):
13
+
14
+ ignore_callback = SimpleCalendarCallback(
15
+ act=SimpleCalAct.ignore
16
+ ).pack() # placeholder for no answer buttons
17
+
18
+ async def start_calendar(
19
+ self, year: int = datetime.now().year, month: int = datetime.now().month
20
+ ) -> InlineKeyboardMarkup:
21
+ """
22
+ Creates an inline keyboard with the provided year and month
23
+ :param int year: Year to use in the calendar, if None the current year is used.
24
+ :param int month: Month to use in the calendar, if None the current month is used.
25
+ :return: Returns InlineKeyboardMarkup object with the calendar.
26
+ """
27
+
28
+ today = datetime.now()
29
+ now_weekday = self._labels.days_of_week[today.weekday()]
30
+ now_month, now_year, now_day = today.month, today.year, today.day
31
+
32
+ def highlight_month():
33
+ month_str = self._labels.months[month - 1]
34
+ if now_month == month and now_year == year:
35
+ return highlight(month_str)
36
+ return month_str
37
+
38
+ def highlight_weekday():
39
+ if now_month == month and now_year == year and now_weekday == weekday:
40
+ return highlight(weekday)
41
+ return weekday
42
+
43
+ def format_day_string():
44
+ date_to_check = datetime(year, month, day)
45
+ if self.min_date and date_to_check < self.min_date:
46
+ return superscript(str(day))
47
+ elif self.max_date and date_to_check > self.max_date:
48
+ return superscript(str(day))
49
+ return str(day)
50
+
51
+ def highlight_day():
52
+ day_string = format_day_string()
53
+ if now_month == month and now_year == year and now_day == day:
54
+ return highlight(day_string)
55
+ return day_string
56
+
57
+ # building a calendar keyboard
58
+ kb = []
59
+
60
+ # inline_kb = InlineKeyboardMarkup(row_width=7)
61
+ # First row - Year
62
+ years_row = []
63
+ years_row.append(
64
+ InlineKeyboardButton(
65
+ text="<<",
66
+ callback_data=SimpleCalendarCallback(
67
+ act=SimpleCalAct.prev_y, year=year, month=month, day=1
68
+ ).pack(),
69
+ )
70
+ )
71
+ years_row.append(
72
+ InlineKeyboardButton(
73
+ text=str(year) if year != now_year else highlight(year),
74
+ callback_data=self.ignore_callback,
75
+ )
76
+ )
77
+ years_row.append(
78
+ InlineKeyboardButton(
79
+ text=">>",
80
+ callback_data=SimpleCalendarCallback(
81
+ act=SimpleCalAct.next_y, year=year, month=month, day=1
82
+ ).pack(),
83
+ )
84
+ )
85
+ kb.append(years_row)
86
+
87
+ # Month nav Buttons
88
+ month_row = []
89
+ month_row.append(
90
+ InlineKeyboardButton(
91
+ text="<",
92
+ callback_data=SimpleCalendarCallback(
93
+ act=SimpleCalAct.prev_m, year=year, month=month, day=1
94
+ ).pack(),
95
+ )
96
+ )
97
+ month_row.append(
98
+ InlineKeyboardButton(
99
+ text=highlight_month(), callback_data=self.ignore_callback
100
+ )
101
+ )
102
+ month_row.append(
103
+ InlineKeyboardButton(
104
+ text=">",
105
+ callback_data=SimpleCalendarCallback(
106
+ act=SimpleCalAct.next_m, year=year, month=month, day=1
107
+ ).pack(),
108
+ )
109
+ )
110
+ kb.append(month_row)
111
+
112
+ # Week Days
113
+ week_days_labels_row = []
114
+ for weekday in self._labels.days_of_week:
115
+ week_days_labels_row.append(
116
+ InlineKeyboardButton(
117
+ text=highlight_weekday(), callback_data=self.ignore_callback
118
+ )
119
+ )
120
+ kb.append(week_days_labels_row)
121
+
122
+ # Calendar rows - Days of month
123
+ month_calendar = calendar.monthcalendar(year, month)
124
+
125
+ for week in month_calendar:
126
+ days_row = []
127
+ for day in week:
128
+ if day == 0:
129
+ days_row.append(
130
+ InlineKeyboardButton(
131
+ text=" ", callback_data=self.ignore_callback
132
+ )
133
+ )
134
+ continue
135
+ days_row.append(
136
+ InlineKeyboardButton(
137
+ text=highlight_day(),
138
+ callback_data=SimpleCalendarCallback(
139
+ act=SimpleCalAct.day, year=year, month=month, day=day
140
+ ).pack(),
141
+ )
142
+ )
143
+ kb.append(days_row)
144
+
145
+ # nav today & cancel button
146
+ cancel_row = []
147
+ cancel_row.append(
148
+ InlineKeyboardButton(
149
+ text=self._labels.cancel_caption,
150
+ callback_data=SimpleCalendarCallback(
151
+ act=SimpleCalAct.cancel, year=year, month=month, day=day
152
+ ).pack(),
153
+ )
154
+ )
155
+ cancel_row.append(
156
+ InlineKeyboardButton(text=" ", callback_data=self.ignore_callback)
157
+ )
158
+ cancel_row.append(
159
+ InlineKeyboardButton(
160
+ text=self._labels.today_caption,
161
+ callback_data=SimpleCalendarCallback(
162
+ act=SimpleCalAct.today, year=year, month=month, day=day
163
+ ).pack(),
164
+ )
165
+ )
166
+ kb.append(cancel_row)
167
+ return InlineKeyboardMarkup(row_width=7, inline_keyboard=kb)
168
+
169
+ async def _update_calendar(self, query: CallbackQuery, with_date: datetime):
170
+ await query.message.edit_reply_markup(
171
+ reply_markup=await self.start_calendar(
172
+ int(with_date.year), int(with_date.month)
173
+ )
174
+ )
175
+
176
+ async def process_selection(
177
+ self, query: CallbackQuery, data: SimpleCalendarCallback
178
+ ) -> tuple:
179
+ """
180
+ Process the callback_query. This method generates a new calendar if forward or
181
+ backward is pressed. This method should be called inside a CallbackQueryHandler.
182
+ :param query: callback_query, as provided by the CallbackQueryHandler
183
+ :param data: callback_data, dictionary, set by calendar_callback
184
+ :return: Returns a tuple (Boolean,datetime), indicating if a date is selected
185
+ and returning the date if so.
186
+ """
187
+ return_data = (False, None)
188
+
189
+ # processing empty buttons, answering with no action
190
+ if data.act == SimpleCalAct.ignore:
191
+ await query.answer(cache_time=60)
192
+ return return_data
193
+
194
+ temp_date = datetime(int(data.year), int(data.month), 1)
195
+
196
+ # user picked a day button, return date
197
+ if data.act == SimpleCalAct.day:
198
+ return await self.process_day_select(data, query)
199
+
200
+ # user navigates to previous year, editing message with new calendar
201
+ if data.act == SimpleCalAct.prev_y:
202
+ prev_date = datetime(int(data.year) - 1, int(data.month), 1)
203
+ await self._update_calendar(query, prev_date)
204
+ # user navigates to next year, editing message with new calendar
205
+ if data.act == SimpleCalAct.next_y:
206
+ next_date = datetime(int(data.year) + 1, int(data.month), 1)
207
+ await self._update_calendar(query, next_date)
208
+ # user navigates to previous month, editing message with new calendar
209
+ if data.act == SimpleCalAct.prev_m:
210
+ prev_date = temp_date - timedelta(days=1)
211
+ await self._update_calendar(query, prev_date)
212
+ # user navigates to next month, editing message with new calendar
213
+ if data.act == SimpleCalAct.next_m:
214
+ next_date = temp_date + timedelta(days=31)
215
+ await self._update_calendar(query, next_date)
216
+ if data.act == SimpleCalAct.today:
217
+ # Возвращаем сегодняшнюю дату
218
+ await query.answer()
219
+ return (True, datetime.now())
220
+ if data.act == SimpleCalAct.cancel:
221
+ await query.message.delete_reply_markup()
222
+ return ("cancel", None)
223
+ # at some point user clicks DAY button, returning date
224
+ return return_data