smart-bot-factory 0.3.2__py3-none-any.whl → 0.3.3__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.

Potentially problematic release.


This version of smart-bot-factory might be problematic. Click here for more details.

@@ -20,6 +20,42 @@ def setup_admin_handlers(dp):
20
20
  """Настройка админских обработчиков"""
21
21
  dp.include_router(admin_router)
22
22
 
23
+ @admin_router.message(Command(commands=["отмена", "cancel"]))
24
+ async def cancel_handler(message: Message, state: FSMContext):
25
+ """Отмена текущего действия и очистка state"""
26
+ from ..handlers.handlers import get_global_var
27
+ admin_manager = get_global_var('admin_manager')
28
+
29
+ # Получаем текущий state
30
+ current_state = await state.get_state()
31
+
32
+ # Очищаем state
33
+ await state.clear()
34
+
35
+ if current_state:
36
+ logger.info(f"State очищен для пользователя {message.from_user.id}: {current_state}")
37
+
38
+ # Если это админ, возвращаем в админ режим
39
+ if admin_manager.is_admin(message.from_user.id):
40
+ await state.set_state(AdminStates.admin_mode)
41
+ await message.answer(
42
+ "✅ Текущее действие отменено\n"
43
+ "Вы вернулись в админ режим\n\n"
44
+ "Используйте /admin для просмотра доступных команд",
45
+ parse_mode='Markdown'
46
+ )
47
+ else:
48
+ await message.answer(
49
+ "✅ Текущее действие отменено\n\n"
50
+ "Используйте /start для начала работы",
51
+ parse_mode='Markdown'
52
+ )
53
+ else:
54
+ await message.answer(
55
+ "ℹ️ Нет активных действий для отмены",
56
+ parse_mode='Markdown'
57
+ )
58
+
23
59
  async def admin_start_handler(message: Message, state: FSMContext):
24
60
  """Обработчик /start для админов в режиме администратора"""
25
61
  from ..handlers.handlers import get_global_var
@@ -43,16 +79,22 @@ async def admin_start_handler(message: Message, state: FSMContext):
43
79
 
44
80
  Доступные команды:
45
81
  • `/стат` - статистика воронки
46
- • `/история <user_id>` - история пользователя
47
- • `/чат <user_id>` - начать диалог
82
+ • `/история user_id` - история пользователя
83
+ • `/чат user_id` - начать диалог
48
84
  • `/чаты` - активные диалоги
49
85
  • `/стоп` - завершить диалог
50
86
  • `/админ` - переключить режим
87
+ • `/отмена` - отменить текущее действие
88
+
89
+ 📅 **Управление событиями:**
90
+ • `/создать_событие` - создать новое событие
91
+ • `/список_событий` - список активных событий
92
+ • `/удалить_событие название` - отменить событие
51
93
  """
52
94
 
53
95
  await message.answer(welcome_text, reply_markup=keyboard, parse_mode='Markdown')
54
96
 
55
- @admin_router.message(Command("стат"))
97
+ @admin_router.message(Command(commands=["стат", "stats"]))
56
98
  async def admin_stats_handler(message: Message, state: FSMContext):
57
99
  """Статистика воронки"""
58
100
  from ..handlers.handlers import get_global_var
@@ -73,13 +115,13 @@ async def admin_stats_handler(message: Message, state: FSMContext):
73
115
 
74
116
  full_text = f"{funnel_text}\n\n{events_text}"
75
117
 
76
- await message.answer(full_text, parse_mode='Markdown')
118
+ await message.answer(full_text)
77
119
 
78
120
  except Exception as e:
79
121
  logger.error(f"Ошибка получения статистики: {e}")
80
122
  await message.answer("❌ Ошибка получения статистики")
81
123
 
82
- @admin_router.message(Command("история"))
124
+ @admin_router.message(Command(commands=["история", "history"]))
83
125
  async def admin_history_handler(message: Message, state: FSMContext):
84
126
  """История пользователя"""
85
127
  from ..handlers.handlers import get_global_var
@@ -115,7 +157,7 @@ async def admin_history_handler(message: Message, state: FSMContext):
115
157
  logger.error(f"Ошибка получения истории: {e}")
116
158
  await message.answer("❌ Ошибка получения истории")
117
159
 
118
- @admin_router.message(Command("чат"))
160
+ @admin_router.message(Command(commands=["чат", "chat"]))
119
161
  async def admin_chat_handler(message: Message, state: FSMContext):
120
162
  """Начать диалог с пользователем"""
121
163
  from ..handlers.handlers import get_global_var
@@ -170,7 +212,7 @@ async def admin_chat_handler(message: Message, state: FSMContext):
170
212
  logger.error(f"❌ Ошибка начала диалога: {e}")
171
213
  await message.answer("❌ Ошибка начала диалога")
172
214
 
173
- @admin_router.message(Command("чаты"))
215
+ @admin_router.message(Command(commands=["чаты", "chats"]))
174
216
  async def admin_active_chats_command(message: Message, state: FSMContext):
175
217
  """Показать активные диалоги админов"""
176
218
  from ..handlers.handlers import get_global_var
@@ -191,7 +233,7 @@ async def admin_active_chats_command(message: Message, state: FSMContext):
191
233
  logger.error(f"Ошибка получения активных чатов: {e}")
192
234
  await message.answer("❌ Ошибка получения активных диалогов")
193
235
 
194
- @admin_router.message(Command("стоп"))
236
+ @admin_router.message(Command(commands=["стоп", "stop"]))
195
237
  async def admin_stop_handler(message: Message, state: FSMContext):
196
238
  """Завершить диалог"""
197
239
  from ..handlers.handlers import get_global_var
@@ -230,7 +272,7 @@ async def admin_stop_handler(message: Message, state: FSMContext):
230
272
  logger.error(f"Ошибка завершения диалога: {e}")
231
273
  await message.answer("❌ Ошибка завершения диалога")
232
274
 
233
- @admin_router.message(Command("админ"))
275
+ @admin_router.message(Command(commands=["админ", "admin"]))
234
276
  async def admin_toggle_handler(message: Message, state: FSMContext):
235
277
  """Переключение режима админа"""
236
278
  from ..handlers.handlers import get_global_var
@@ -391,7 +433,7 @@ async def admin_callback_handler(callback: CallbackQuery, state: FSMContext):
391
433
  logger.error(f"Ошибка обработки callback {data}: {e}")
392
434
  await callback.answer("Ошибка")
393
435
 
394
- @admin_router.message(StateFilter(AdminStates.admin_mode, AdminStates.in_conversation))
436
+ @admin_router.message(StateFilter(AdminStates.admin_mode, AdminStates.in_conversation), F.text, lambda message: not message.text.startswith('/'))
395
437
  async def admin_message_handler(message: Message, state: FSMContext):
396
438
  """Обработчик сообщений админов"""
397
439
  from ..handlers.handlers import get_global_var
@@ -417,8 +459,8 @@ async def admin_message_handler(message: Message, state: FSMContext):
417
459
 
418
460
  Доступные команды:
419
461
  • `/стат` - статистика воронки
420
- • `/история <user_id>` - история пользователя
421
- • `/чат <user_id>` - начать диалог
462
+ • `/история user_id` - история пользователя
463
+ • `/чат user_id` - начать диалог
422
464
  • `/стоп` - завершить диалог
423
465
  • `/админ` - переключить режим
424
466
 
@@ -0,0 +1,6 @@
1
+ from .common import get_user_locale
2
+ from .simple_calendar import SimpleCalendar
3
+ from .dialog_calendar import DialogCalendar
4
+ from .schemas import SimpleCalendarCallback, DialogCalendarCallback, CalendarLabels
5
+
6
+ __all__ = ['SimpleCalendar', 'DialogCalendar', 'SimpleCalendarCallback', 'DialogCalendarCallback', 'CalendarLabels', 'get_user_locale']
@@ -0,0 +1,70 @@
1
+ import calendar
2
+ import locale
3
+
4
+ from aiogram.types import User
5
+ from datetime import datetime
6
+
7
+ from .schemas import CalendarLabels
8
+
9
+
10
+ async def get_user_locale(from_user: User) -> str:
11
+ "Returns user locale in format en_US, accepts User instance from Message, CallbackData etc"
12
+ loc = from_user.language_code
13
+ return locale.locale_alias[loc].split(".")[0]
14
+
15
+
16
+ class GenericCalendar:
17
+
18
+ def __init__(
19
+ self,
20
+ locale: str = None,
21
+ cancel_btn: str = None,
22
+ today_btn: str = None,
23
+ show_alerts: bool = False
24
+ ) -> None:
25
+ """Pass labels if you need to have alternative language of buttons
26
+
27
+ Parameters:
28
+ locale (str): Locale calendar must have captions in (in format uk_UA), if None - default English will be used
29
+ cancel_btn (str): label for button Cancel to cancel date input
30
+ today_btn (str): label for button Today to set calendar back to todays date
31
+ show_alerts (bool): defines how the date range error would shown (defaults to False)
32
+ """
33
+ self._labels = CalendarLabels()
34
+ if locale:
35
+ # getting month names and days of week in specified locale
36
+ with calendar.different_locale(locale):
37
+ self._labels.days_of_week = list(calendar.day_abbr)
38
+ self._labels.months = calendar.month_abbr[1:]
39
+
40
+ if cancel_btn:
41
+ self._labels.cancel_caption = cancel_btn
42
+ if today_btn:
43
+ self._labels.today_caption = today_btn
44
+
45
+ self.min_date = None
46
+ self.max_date = None
47
+ self.show_alerts = show_alerts
48
+
49
+ def set_dates_range(self, min_date: datetime, max_date: datetime):
50
+ """Sets range of minimum & maximum dates"""
51
+ self.min_date = min_date
52
+ self.max_date = max_date
53
+
54
+ async def process_day_select(self, data, query):
55
+ """Checks selected date is in allowed range of dates"""
56
+ date = datetime(int(data.year), int(data.month), int(data.day))
57
+ if self.min_date and self.min_date > date:
58
+ await query.answer(
59
+ f'The date have to be later {self.min_date.strftime("%d/%m/%Y")}',
60
+ show_alert=self.show_alerts
61
+ )
62
+ return False, None
63
+ elif self.max_date and self.max_date < date:
64
+ await query.answer(
65
+ f'The date have to be before {self.max_date.strftime("%d/%m/%Y")}',
66
+ show_alert=self.show_alerts
67
+ )
68
+ return False, None
69
+ await query.message.delete_reply_markup() # removing inline keyboard
70
+ return True, date
@@ -0,0 +1,197 @@
1
+ import calendar
2
+ from datetime import datetime
3
+
4
+ from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
5
+ from aiogram.types import CallbackQuery
6
+
7
+ from .schemas import DialogCalendarCallback, DialogCalAct, highlight, superscript
8
+ from .common import GenericCalendar
9
+
10
+
11
+ class DialogCalendar(GenericCalendar):
12
+
13
+ ignore_callback = DialogCalendarCallback(act=DialogCalAct.ignore).pack() # placeholder for no answer buttons
14
+
15
+ async def _get_month_kb(self, year: int):
16
+ """Creates an inline keyboard with months for specified year"""
17
+
18
+ today = datetime.now()
19
+ now_month, now_year = today.month, today.year
20
+ now_year = today.year
21
+
22
+ kb = []
23
+ # first row with year button
24
+ years_row = []
25
+ years_row.append(
26
+ InlineKeyboardButton(
27
+ text=self._labels.cancel_caption,
28
+ callback_data=DialogCalendarCallback(act=DialogCalAct.cancel, year=year, month=1, day=1).pack()
29
+ )
30
+ )
31
+ years_row.append(InlineKeyboardButton(
32
+ text=str(year) if year != today.year else highlight(year),
33
+ callback_data=DialogCalendarCallback(act=DialogCalAct.start, year=year, month=-1, day=-1).pack()
34
+ ))
35
+ years_row.append(InlineKeyboardButton(text=" ", callback_data=self.ignore_callback))
36
+ kb.append(years_row)
37
+ # two rows with 6 months buttons
38
+ month6_row = []
39
+
40
+ def highlight_month():
41
+ month_str = self._labels.months[month - 1]
42
+ if now_month == month and now_year == year:
43
+ return highlight(month_str)
44
+ return month_str
45
+
46
+ for month in range(1, 7):
47
+ month6_row.append(InlineKeyboardButton(
48
+ text=highlight_month(),
49
+ callback_data=DialogCalendarCallback(
50
+ act=DialogCalAct.set_m, year=year, month=month, day=-1
51
+ ).pack()
52
+ ))
53
+ month12_row = []
54
+
55
+ for month in range(7, 13):
56
+ month12_row.append(InlineKeyboardButton(
57
+ text=highlight_month(),
58
+ callback_data=DialogCalendarCallback(
59
+ act=DialogCalAct.set_m, year=year, month=month, day=-1
60
+ ).pack()
61
+ ))
62
+
63
+ kb.append(month6_row)
64
+ kb.append(month12_row)
65
+ return InlineKeyboardMarkup(row_width=6, inline_keyboard=kb)
66
+
67
+ async def _get_days_kb(self, year: int, month: int):
68
+ """Creates an inline keyboard with calendar days of month for specified year and month"""
69
+
70
+ today = datetime.now()
71
+ now_weekday = self._labels.days_of_week[today.weekday()]
72
+ now_month, now_year, now_day = today.month, today.year, today.day
73
+
74
+ def highlight_month():
75
+ month_str = self._labels.months[month - 1]
76
+ if now_month == month and now_year == year:
77
+ return highlight(month_str)
78
+ return month_str
79
+
80
+ def highlight_weekday():
81
+ if now_month == month and now_year == year and now_weekday == weekday:
82
+ return highlight(weekday)
83
+ return weekday
84
+
85
+ def format_day_string():
86
+ date_to_check = datetime(year, month, day)
87
+ if self.min_date and date_to_check < self.min_date:
88
+ return superscript(str(day))
89
+ elif self.max_date and date_to_check > self.max_date:
90
+ return superscript(str(day))
91
+ return str(day)
92
+
93
+ def highlight_day():
94
+ day_string = format_day_string()
95
+ if now_month == month and now_year == year and now_day == day:
96
+ return highlight(day_string)
97
+ return day_string
98
+
99
+ kb = []
100
+ nav_row = []
101
+ nav_row.append(
102
+ InlineKeyboardButton(
103
+ text=self._labels.cancel_caption,
104
+ callback_data=DialogCalendarCallback(act=DialogCalAct.cancel, year=year, month=1, day=1).pack()
105
+ )
106
+ )
107
+ nav_row.append(InlineKeyboardButton(
108
+ text=str(year) if year != now_year else highlight(year),
109
+ callback_data=DialogCalendarCallback(act=DialogCalAct.start, year=year, month=-1, day=-1).pack()
110
+ ))
111
+ nav_row.append(InlineKeyboardButton(
112
+ text=highlight_month(),
113
+ callback_data=DialogCalendarCallback(act=DialogCalAct.set_y, year=year, month=-1, day=-1).pack()
114
+ ))
115
+ kb.append(nav_row)
116
+
117
+ week_days_labels_row = []
118
+ for weekday in self._labels.days_of_week:
119
+ week_days_labels_row.append(InlineKeyboardButton(
120
+ text=highlight_weekday(), callback_data=self.ignore_callback))
121
+ kb.append(week_days_labels_row)
122
+
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(InlineKeyboardButton(text=" ", callback_data=self.ignore_callback))
130
+ continue
131
+ days_row.append(InlineKeyboardButton(
132
+ text=highlight_day(),
133
+ callback_data=DialogCalendarCallback(act=DialogCalAct.day, year=year, month=month, day=day).pack()
134
+ ))
135
+ kb.append(days_row)
136
+ return InlineKeyboardMarkup(row_width=7, inline_keyboard=kb)
137
+
138
+ async def start_calendar(
139
+ self,
140
+ year: int = datetime.now().year,
141
+ month: int = None
142
+ ) -> InlineKeyboardMarkup:
143
+ today = datetime.now()
144
+ now_year = today.year
145
+
146
+ if month:
147
+ return await self._get_days_kb(year, month)
148
+ kb = []
149
+ # inline_kb = InlineKeyboardMarkup(row_width=5)
150
+ # first row - years
151
+ years_row = []
152
+ for value in range(year - 2, year + 3):
153
+ years_row.append(InlineKeyboardButton(
154
+ text=str(value) if value != now_year else highlight(value),
155
+ callback_data=DialogCalendarCallback(act=DialogCalAct.set_y, year=value, month=-1, day=-1).pack()
156
+ ))
157
+ kb.append(years_row)
158
+ # nav buttons
159
+ nav_row = []
160
+ nav_row.append(InlineKeyboardButton(
161
+ text='<<',
162
+ callback_data=DialogCalendarCallback(act=DialogCalAct.prev_y, year=year, month=-1, day=-1).pack()
163
+ ))
164
+ nav_row.append(InlineKeyboardButton(
165
+ text=self._labels.cancel_caption,
166
+ callback_data=DialogCalendarCallback(act=DialogCalAct.cancel, year=year, month=1, day=1).pack()
167
+ ))
168
+ nav_row.append(InlineKeyboardButton(
169
+ text='>>',
170
+ callback_data=DialogCalendarCallback(act=DialogCalAct.next_y, year=year, month=1, day=1).pack()
171
+ ))
172
+ kb.append(nav_row)
173
+ return InlineKeyboardMarkup(row_width=5, inline_keyboard=kb)
174
+
175
+ async def process_selection(self, query: CallbackQuery, data: DialogCalendarCallback) -> tuple:
176
+ return_data = (False, None)
177
+ if data.act == DialogCalAct.ignore:
178
+ await query.answer(cache_time=60)
179
+ if data.act == DialogCalAct.set_y:
180
+ await query.message.edit_reply_markup(reply_markup=await self._get_month_kb(int(data.year)))
181
+ if data.act == DialogCalAct.prev_y:
182
+ new_year = int(data.year) - 5
183
+ await query.message.edit_reply_markup(reply_markup=await self.start_calendar(year=new_year))
184
+ if data.act == DialogCalAct.next_y:
185
+ new_year = int(data.year) + 5
186
+ await query.message.edit_reply_markup(reply_markup=await self.start_calendar(year=new_year))
187
+ if data.act == DialogCalAct.start:
188
+ await query.message.edit_reply_markup(reply_markup=await self.start_calendar(int(data.year)))
189
+ if data.act == DialogCalAct.set_m:
190
+ await query.message.edit_reply_markup(reply_markup=await self._get_days_kb(int(data.year), int(data.month)))
191
+ if data.act == DialogCalAct.day:
192
+
193
+ return await self.process_day_select(data, query)
194
+
195
+ if data.act == DialogCalAct.cancel:
196
+ await query.message.delete_reply_markup()
197
+ return return_data
@@ -0,0 +1,78 @@
1
+ from typing import Optional
2
+ from enum import Enum
3
+
4
+ from pydantic import BaseModel, conlist, Field
5
+
6
+ from aiogram.filters.callback_data import CallbackData
7
+
8
+
9
+ class SimpleCalAct(str, Enum):
10
+ ignore = 'IGNORE'
11
+ prev_y = 'PREV-YEAR'
12
+ next_y = 'NEXT-YEAR'
13
+ prev_m = 'PREV-MONTH'
14
+ next_m = 'NEXT-MONTH'
15
+ cancel = 'CANCEL'
16
+ today = 'TODAY'
17
+ day = 'DAY'
18
+
19
+
20
+ class DialogCalAct(str, Enum):
21
+ ignore = 'IGNORE'
22
+ set_y = 'SET-YEAR'
23
+ set_m = 'SET-MONTH'
24
+ prev_y = 'PREV-YEAR'
25
+ next_y = 'NEXT-YEAR'
26
+ cancel = 'CANCEL'
27
+ start = 'START'
28
+ day = 'SET-DAY'
29
+
30
+
31
+ class CalendarCallback(CallbackData, prefix="calendar"):
32
+ act: str
33
+ year: Optional[int] = None
34
+ month: Optional[int] = None
35
+ day: Optional[int] = None
36
+
37
+
38
+ class SimpleCalendarCallback(CalendarCallback, prefix="simple_calendar"):
39
+ act: SimpleCalAct
40
+
41
+
42
+ class DialogCalendarCallback(CalendarCallback, prefix="dialog_calendar"):
43
+ act: DialogCalAct
44
+
45
+
46
+ class CalendarLabels(BaseModel):
47
+ "Schema to pass labels for calendar. Can be used to put in different languages"
48
+ days_of_week: conlist(str, max_length=7, min_length=7) = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
49
+ months: conlist(str, max_length=12, min_length=12) = [
50
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
51
+ ]
52
+ cancel_caption: str = Field(default='Cancel', description='Caprion for Cancel button')
53
+ today_caption: str = Field(default='Today', description='Caprion for Cancel button')
54
+
55
+
56
+ HIGHLIGHT_FORMAT = "[{}]"
57
+
58
+
59
+ def highlight(text):
60
+ return HIGHLIGHT_FORMAT.format(text)
61
+
62
+
63
+ def superscript(text):
64
+ normal = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-=()"
65
+ super_s = "ᴬᴮᶜᴰᴱᶠᴳᴴᴵᴶᴷᴸᴹᴺᴼᴾQᴿˢᵀᵁⱽᵂˣʸᶻᵃᵇᶜᵈᵉᶠᵍʰᶦʲᵏˡᵐⁿᵒᵖ۹ʳˢᵗᵘᵛʷˣʸᶻ⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾"
66
+ output = ''
67
+ for i in text:
68
+ output += (super_s[normal.index(i)] if i in normal else i)
69
+ return output
70
+
71
+
72
+ def subscript(text):
73
+ normal = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-=()"
74
+ sub_s = "ₐ₈CDₑբGₕᵢⱼₖₗₘₙₒₚQᵣₛₜᵤᵥwₓᵧZₐ♭꜀ᑯₑբ₉ₕᵢⱼₖₗₘₙₒₚ૧ᵣₛₜᵤᵥwₓᵧ₂₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎"
75
+ output = ''
76
+ for i in text:
77
+ output += (sub_s[normal.index(i)] if i in normal else i)
78
+ return output
@@ -0,0 +1,180 @@
1
+ import calendar
2
+ from datetime import datetime, timedelta
3
+
4
+ from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
5
+ from aiogram.types import CallbackQuery
6
+
7
+ from .schemas import SimpleCalendarCallback, SimpleCalAct, highlight, superscript
8
+ from .common import GenericCalendar
9
+
10
+
11
+ class SimpleCalendar(GenericCalendar):
12
+
13
+ ignore_callback = SimpleCalendarCallback(act=SimpleCalAct.ignore).pack() # placeholder for no answer buttons
14
+
15
+ async def start_calendar(
16
+ self,
17
+ year: int = datetime.now().year,
18
+ month: int = datetime.now().month
19
+ ) -> InlineKeyboardMarkup:
20
+ """
21
+ Creates an inline keyboard with the provided year and month
22
+ :param int year: Year to use in the calendar, if None the current year is used.
23
+ :param int month: Month to use in the calendar, if None the current month is used.
24
+ :return: Returns InlineKeyboardMarkup object with the calendar.
25
+ """
26
+
27
+ today = datetime.now()
28
+ now_weekday = self._labels.days_of_week[today.weekday()]
29
+ now_month, now_year, now_day = today.month, today.year, today.day
30
+
31
+ def highlight_month():
32
+ month_str = self._labels.months[month - 1]
33
+ if now_month == month and now_year == year:
34
+ return highlight(month_str)
35
+ return month_str
36
+
37
+ def highlight_weekday():
38
+ if now_month == month and now_year == year and now_weekday == weekday:
39
+ return highlight(weekday)
40
+ return weekday
41
+
42
+ def format_day_string():
43
+ date_to_check = datetime(year, month, day)
44
+ if self.min_date and date_to_check < self.min_date:
45
+ return superscript(str(day))
46
+ elif self.max_date and date_to_check > self.max_date:
47
+ return superscript(str(day))
48
+ return str(day)
49
+
50
+ def highlight_day():
51
+ day_string = format_day_string()
52
+ if now_month == month and now_year == year and now_day == day:
53
+ return highlight(day_string)
54
+ return day_string
55
+
56
+ # building a calendar keyboard
57
+ kb = []
58
+
59
+ # inline_kb = InlineKeyboardMarkup(row_width=7)
60
+ # First row - Year
61
+ years_row = []
62
+ years_row.append(InlineKeyboardButton(
63
+ text="<<",
64
+ callback_data=SimpleCalendarCallback(act=SimpleCalAct.prev_y, year=year, month=month, day=1).pack()
65
+ ))
66
+ years_row.append(InlineKeyboardButton(
67
+ text=str(year) if year != now_year else highlight(year),
68
+ callback_data=self.ignore_callback
69
+ ))
70
+ years_row.append(InlineKeyboardButton(
71
+ text=">>",
72
+ callback_data=SimpleCalendarCallback(act=SimpleCalAct.next_y, year=year, month=month, day=1).pack()
73
+ ))
74
+ kb.append(years_row)
75
+
76
+ # Month nav Buttons
77
+ month_row = []
78
+ month_row.append(InlineKeyboardButton(
79
+ text="<",
80
+ callback_data=SimpleCalendarCallback(act=SimpleCalAct.prev_m, year=year, month=month, day=1).pack()
81
+ ))
82
+ month_row.append(InlineKeyboardButton(
83
+ text=highlight_month(),
84
+ callback_data=self.ignore_callback
85
+ ))
86
+ month_row.append(InlineKeyboardButton(
87
+ text=">",
88
+ callback_data=SimpleCalendarCallback(act=SimpleCalAct.next_m, year=year, month=month, day=1).pack()
89
+ ))
90
+ kb.append(month_row)
91
+
92
+ # Week Days
93
+ week_days_labels_row = []
94
+ for weekday in self._labels.days_of_week:
95
+ week_days_labels_row.append(
96
+ InlineKeyboardButton(text=highlight_weekday(), callback_data=self.ignore_callback)
97
+ )
98
+ kb.append(week_days_labels_row)
99
+
100
+ # Calendar rows - Days of month
101
+ month_calendar = calendar.monthcalendar(year, month)
102
+
103
+ for week in month_calendar:
104
+ days_row = []
105
+ for day in week:
106
+ if day == 0:
107
+ days_row.append(InlineKeyboardButton(text=" ", callback_data=self.ignore_callback))
108
+ continue
109
+ days_row.append(InlineKeyboardButton(
110
+ text=highlight_day(),
111
+ callback_data=SimpleCalendarCallback(act=SimpleCalAct.day, year=year, month=month, day=day).pack()
112
+ ))
113
+ kb.append(days_row)
114
+
115
+ # nav today & cancel button
116
+ cancel_row = []
117
+ cancel_row.append(InlineKeyboardButton(
118
+ text=self._labels.cancel_caption,
119
+ callback_data=SimpleCalendarCallback(act=SimpleCalAct.cancel, year=year, month=month, day=day).pack()
120
+ ))
121
+ cancel_row.append(InlineKeyboardButton(text=" ", callback_data=self.ignore_callback))
122
+ cancel_row.append(InlineKeyboardButton(
123
+ text=self._labels.today_caption,
124
+ callback_data=SimpleCalendarCallback(act=SimpleCalAct.today, year=year, month=month, day=day).pack()
125
+ ))
126
+ kb.append(cancel_row)
127
+ return InlineKeyboardMarkup(row_width=7, inline_keyboard=kb)
128
+
129
+ async def _update_calendar(self, query: CallbackQuery, with_date: datetime):
130
+ await query.message.edit_reply_markup(
131
+ reply_markup=await self.start_calendar(int(with_date.year), int(with_date.month))
132
+ )
133
+
134
+ async def process_selection(self, query: CallbackQuery, data: SimpleCalendarCallback) -> tuple:
135
+ """
136
+ Process the callback_query. This method generates a new calendar if forward or
137
+ backward is pressed. This method should be called inside a CallbackQueryHandler.
138
+ :param query: callback_query, as provided by the CallbackQueryHandler
139
+ :param data: callback_data, dictionary, set by calendar_callback
140
+ :return: Returns a tuple (Boolean,datetime), indicating if a date is selected
141
+ and returning the date if so.
142
+ """
143
+ return_data = (False, None)
144
+
145
+ # processing empty buttons, answering with no action
146
+ if data.act == SimpleCalAct.ignore:
147
+ await query.answer(cache_time=60)
148
+ return return_data
149
+
150
+ temp_date = datetime(int(data.year), int(data.month), 1)
151
+
152
+ # user picked a day button, return date
153
+ if data.act == SimpleCalAct.day:
154
+ return await self.process_day_select(data, query)
155
+
156
+ # user navigates to previous year, editing message with new calendar
157
+ if data.act == SimpleCalAct.prev_y:
158
+ prev_date = datetime(int(data.year) - 1, int(data.month), 1)
159
+ await self._update_calendar(query, prev_date)
160
+ # user navigates to next year, editing message with new calendar
161
+ if data.act == SimpleCalAct.next_y:
162
+ next_date = datetime(int(data.year) + 1, int(data.month), 1)
163
+ await self._update_calendar(query, next_date)
164
+ # user navigates to previous month, editing message with new calendar
165
+ if data.act == SimpleCalAct.prev_m:
166
+ prev_date = temp_date - timedelta(days=1)
167
+ await self._update_calendar(query, prev_date)
168
+ # user navigates to next month, editing message with new calendar
169
+ if data.act == SimpleCalAct.next_m:
170
+ next_date = temp_date + timedelta(days=31)
171
+ await self._update_calendar(query, next_date)
172
+ if data.act == SimpleCalAct.today:
173
+ # Возвращаем сегодняшнюю дату
174
+ await query.answer()
175
+ return (True, datetime.now())
176
+ if data.act == SimpleCalAct.cancel:
177
+ await query.message.delete_reply_markup()
178
+ return ('cancel', None)
179
+ # at some point user clicks DAY button, returning date
180
+ return return_data