smart-bot-factory 0.3.2__py3-none-any.whl → 0.3.4__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,47 @@ 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
+ # Очищаем временные файлы если это создание события
33
+ if current_state and current_state.startswith('AdminStates:create_event'):
34
+ from .admin_events import cleanup_temp_files
35
+ await cleanup_temp_files(state)
36
+
37
+ # Очищаем state
38
+ await state.clear()
39
+
40
+ if current_state:
41
+ logger.info(f"State очищен для пользователя {message.from_user.id}: {current_state}")
42
+
43
+ # Если это админ, возвращаем в админ режим
44
+ if admin_manager.is_admin(message.from_user.id):
45
+ await state.set_state(AdminStates.admin_mode)
46
+ await message.answer(
47
+ "✅ Текущее действие отменено\n"
48
+ "Вы вернулись в админ режим\n\n"
49
+ "Используйте /admin для просмотра доступных команд",
50
+ parse_mode='Markdown'
51
+ )
52
+ else:
53
+ await message.answer(
54
+ "✅ Текущее действие отменено\n\n"
55
+ "Используйте /start для начала работы",
56
+ parse_mode='Markdown'
57
+ )
58
+ else:
59
+ await message.answer(
60
+ "ℹ️ Нет активных действий для отмены",
61
+ parse_mode='Markdown'
62
+ )
63
+
23
64
  async def admin_start_handler(message: Message, state: FSMContext):
24
65
  """Обработчик /start для админов в режиме администратора"""
25
66
  from ..handlers.handlers import get_global_var
@@ -43,16 +84,22 @@ async def admin_start_handler(message: Message, state: FSMContext):
43
84
 
44
85
  Доступные команды:
45
86
  • `/стат` - статистика воронки
46
- • `/история <user_id>` - история пользователя
47
- • `/чат <user_id>` - начать диалог
87
+ • `/история user_id` - история пользователя
88
+ • `/чат user_id` - начать диалог
48
89
  • `/чаты` - активные диалоги
49
90
  • `/стоп` - завершить диалог
50
91
  • `/админ` - переключить режим
92
+ • `/отмена` - отменить текущее действие
93
+
94
+ 📅 **Управление событиями:**
95
+ • `/создать_событие` - создать новое событие
96
+ • `/список_событий` - список активных событий
97
+ • `/удалить_событие название` - отменить событие
51
98
  """
52
99
 
53
100
  await message.answer(welcome_text, reply_markup=keyboard, parse_mode='Markdown')
54
101
 
55
- @admin_router.message(Command("стат"))
102
+ @admin_router.message(Command(commands=["стат", "stats"]))
56
103
  async def admin_stats_handler(message: Message, state: FSMContext):
57
104
  """Статистика воронки"""
58
105
  from ..handlers.handlers import get_global_var
@@ -73,13 +120,13 @@ async def admin_stats_handler(message: Message, state: FSMContext):
73
120
 
74
121
  full_text = f"{funnel_text}\n\n{events_text}"
75
122
 
76
- await message.answer(full_text, parse_mode='Markdown')
123
+ await message.answer(full_text)
77
124
 
78
125
  except Exception as e:
79
126
  logger.error(f"Ошибка получения статистики: {e}")
80
127
  await message.answer("❌ Ошибка получения статистики")
81
128
 
82
- @admin_router.message(Command("история"))
129
+ @admin_router.message(Command(commands=["история", "history"]))
83
130
  async def admin_history_handler(message: Message, state: FSMContext):
84
131
  """История пользователя"""
85
132
  from ..handlers.handlers import get_global_var
@@ -115,7 +162,7 @@ async def admin_history_handler(message: Message, state: FSMContext):
115
162
  logger.error(f"Ошибка получения истории: {e}")
116
163
  await message.answer("❌ Ошибка получения истории")
117
164
 
118
- @admin_router.message(Command("чат"))
165
+ @admin_router.message(Command(commands=["чат", "chat"]))
119
166
  async def admin_chat_handler(message: Message, state: FSMContext):
120
167
  """Начать диалог с пользователем"""
121
168
  from ..handlers.handlers import get_global_var
@@ -170,7 +217,7 @@ async def admin_chat_handler(message: Message, state: FSMContext):
170
217
  logger.error(f"❌ Ошибка начала диалога: {e}")
171
218
  await message.answer("❌ Ошибка начала диалога")
172
219
 
173
- @admin_router.message(Command("чаты"))
220
+ @admin_router.message(Command(commands=["чаты", "chats"]))
174
221
  async def admin_active_chats_command(message: Message, state: FSMContext):
175
222
  """Показать активные диалоги админов"""
176
223
  from ..handlers.handlers import get_global_var
@@ -191,7 +238,7 @@ async def admin_active_chats_command(message: Message, state: FSMContext):
191
238
  logger.error(f"Ошибка получения активных чатов: {e}")
192
239
  await message.answer("❌ Ошибка получения активных диалогов")
193
240
 
194
- @admin_router.message(Command("стоп"))
241
+ @admin_router.message(Command(commands=["стоп", "stop"]))
195
242
  async def admin_stop_handler(message: Message, state: FSMContext):
196
243
  """Завершить диалог"""
197
244
  from ..handlers.handlers import get_global_var
@@ -230,7 +277,7 @@ async def admin_stop_handler(message: Message, state: FSMContext):
230
277
  logger.error(f"Ошибка завершения диалога: {e}")
231
278
  await message.answer("❌ Ошибка завершения диалога")
232
279
 
233
- @admin_router.message(Command("админ"))
280
+ @admin_router.message(Command(commands=["админ", "admin"]))
234
281
  async def admin_toggle_handler(message: Message, state: FSMContext):
235
282
  """Переключение режима админа"""
236
283
  from ..handlers.handlers import get_global_var
@@ -391,7 +438,7 @@ async def admin_callback_handler(callback: CallbackQuery, state: FSMContext):
391
438
  logger.error(f"Ошибка обработки callback {data}: {e}")
392
439
  await callback.answer("Ошибка")
393
440
 
394
- @admin_router.message(StateFilter(AdminStates.admin_mode, AdminStates.in_conversation))
441
+ @admin_router.message(StateFilter(AdminStates.admin_mode, AdminStates.in_conversation), F.text, lambda message: not message.text.startswith('/'))
395
442
  async def admin_message_handler(message: Message, state: FSMContext):
396
443
  """Обработчик сообщений админов"""
397
444
  from ..handlers.handlers import get_global_var
@@ -417,8 +464,8 @@ async def admin_message_handler(message: Message, state: FSMContext):
417
464
 
418
465
  Доступные команды:
419
466
  • `/стат` - статистика воронки
420
- • `/история <user_id>` - история пользователя
421
- • `/чат <user_id>` - начать диалог
467
+ • `/история user_id` - история пользователя
468
+ • `/чат user_id` - начать диалог
422
469
  • `/стоп` - завершить диалог
423
470
  • `/админ` - переключить режим
424
471
 
@@ -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