├── requirements.txt ├── aiogram_timepicker ├── clock │ ├── __init__.py │ └── single │ │ ├── base │ │ ├── __init__.py │ │ └── timepicker.py │ │ ├── __init__.py │ │ ├── c24 │ │ ├── __init__.py │ │ ├── timepicker.py │ │ └── utils.py │ │ ├── c24_ts3 │ │ ├── __init__.py │ │ ├── timepicker.py │ │ └── utils.py │ │ ├── c60_ts3 │ │ ├── __init__.py │ │ ├── timepicker.py │ │ └── utils.py │ │ └── c60_ts5 │ │ ├── __init__.py │ │ ├── timepicker.py │ │ └── utils.py ├── panel │ ├── single │ │ ├── __init__.py │ │ ├── hour │ │ │ ├── __init__.py │ │ │ ├── utils.py │ │ │ └── timepicker.py │ │ ├── hour2 │ │ │ ├── __init__.py │ │ │ ├── utils.py │ │ │ └── timepicker.py │ │ ├── minute │ │ │ ├── __init__.py │ │ │ ├── utils.py │ │ │ └── timepicker.py │ │ └── second │ │ │ ├── __init__.py │ │ │ ├── utils.py │ │ │ └── timepicker.py │ ├── full │ │ ├── adapter │ │ │ ├── __init__.py │ │ │ ├── minute.py │ │ │ └── second.py │ │ ├── __init__.py │ │ ├── utils.py │ │ └── timepicker.py │ ├── min_sec │ │ ├── adapter │ │ │ ├── __init__.py │ │ │ ├── minute.py │ │ │ └── second.py │ │ ├── __init__.py │ │ ├── utils.py │ │ └── timepicker.py │ └── __init__.py ├── __init__.py ├── carousel │ ├── __init__.py │ └── full │ │ ├── __init__.py │ │ ├── utils.py │ │ └── timepicker.py ├── utils.py └── result.py ├── .gitignore ├── pyproject.toml ├── LICENSE.txt ├── README.md └── bot_example.py /requirements.txt: -------------------------------------------------------------------------------- 1 | aiogram~=2.12 2 | aiogram_timepicker 3 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram_timepicker.clock import single 2 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/base/__init__.py: -------------------------------------------------------------------------------- 1 | from .timepicker import TimePicker as BaseTimePicker 2 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram_timepicker.panel.single import hour, minute, second 2 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/full/adapter/__init__.py: -------------------------------------------------------------------------------- 1 | import aiogram_timepicker.panel.full.adapter.second as second 2 | import aiogram_timepicker.panel.full.adapter.minute as minute 3 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/min_sec/adapter/__init__.py: -------------------------------------------------------------------------------- 1 | import aiogram_timepicker.panel.min_sec.adapter.minute as minute 2 | import aiogram_timepicker.panel.min_sec.adapter.second as second 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .backup/ 2 | .vscode/ 3 | .idea/ 4 | env/ 5 | .venv/ 6 | .env 7 | __pycache__/ 8 | *.pyc 9 | config.py 10 | 11 | dist/ 12 | MANIFEST 13 | MANIFEST.in 14 | 15 | aiogram_timepicker.egg-info -------------------------------------------------------------------------------- /aiogram_timepicker/__init__.py: -------------------------------------------------------------------------------- 1 | import aiogram_timepicker.result as result 2 | 3 | from aiogram_timepicker import panel 4 | from aiogram_timepicker import carousel 5 | from aiogram_timepicker import clock 6 | -------------------------------------------------------------------------------- /aiogram_timepicker/carousel/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram_timepicker.carousel.full import \ 2 | timepicker_callback as full_timep_callback, \ 3 | TimePicker as FullTimePicker, \ 4 | default as full_timep_default 5 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/__init__.py: -------------------------------------------------------------------------------- 1 | import aiogram_timepicker.clock.single.c24 as c24 2 | import aiogram_timepicker.clock.single.c24_ts3 as c24_ts3 3 | import aiogram_timepicker.clock.single.c60_ts5 as c60_ts5 4 | import aiogram_timepicker.clock.single.c60_ts3 as c60_ts3 5 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c24/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('c24_tmpck', 'act', 'time') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'cancel': 'Cancel', 10 | 'empty': ' ', 11 | 'center': '•', 12 | 'time_format': '{0:02}', 13 | 'time_current_format': '({0:02})', 14 | } 15 | 16 | from .timepicker import TimePicker 17 | from .utils import default 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c24_ts3/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('c24_3_tmpck', 'act', 'time') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'cancel': 'Cancel', 10 | 'empty': ' ', 11 | 'center': '•', 12 | 'time_format': '{0:02}', 13 | 'time_current_format': '({0:02})', 14 | } 15 | 16 | from .timepicker import TimePicker 17 | from .utils import default 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c60_ts3/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('c60_ts3_tmpck', 'act', 'time') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'cancel': 'Cancel', 10 | 'empty': ' ', 11 | 'center': '•', 12 | 'time_format': '{0:02}', 13 | 'time_current_format': '({0:02})', 14 | } 15 | 16 | from .timepicker import TimePicker 17 | from .utils import default 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c60_ts5/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('c60_ts5_tmpck', 'act', 'time') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'cancel': 'Cancel', 10 | 'empty': ' ', 11 | 'center': '•', 12 | 'time_format': '{0:02}', 13 | 'time_current_format': '({0:02})', 14 | } 15 | 16 | from .timepicker import TimePicker 17 | from .utils import default 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/min_sec/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | # setting timepicker_callback prefix and parts 4 | timepicker_callback = CallbackData('ms_tmpck', 'act', 'minute', 'second') 5 | 6 | _default = { 7 | 'up': '⇪', 8 | 'down': '⇓', 9 | 'select': 'Select', 10 | 'cancel': 'Cancel', 11 | 'empty': ' ', 12 | 'minute_format': '{0:02}', 13 | 'second_format': '{0:02}', 14 | } 15 | 16 | from .timepicker import TimePicker 17 | from .utils import default 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/hour/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('h_tmpck', 'act', 'hour') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'cancel': 'Cancel', 10 | 'empty': ' ', 11 | 'hour_format': '{0:02}', 12 | 'hour_unavailable_format': '{0:02}', 13 | 'hour_current_format': '({0:02})', 14 | } 15 | 16 | from .timepicker import TimePicker 17 | from .utils import default 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/hour2/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('h2_tmpck', 'act', 'hour') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'cancel': 'Cancel', 10 | 'empty': ' ', 11 | 'hour_format': '{0:02}', 12 | 'hour_unavailable_format': '{0:02}', 13 | 'hour_current_format': '({0:02})', 14 | } 15 | 16 | from .timepicker import TimePicker 17 | from .utils import default 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/full/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | # setting timepicker_callback prefix and parts 4 | timepicker_callback = CallbackData('full_timepicker', 'act', 'hour', 'minute', 'second') 5 | 6 | _default = { 7 | 'up': '⇪', 8 | 'down': '⇓', 9 | 'select': 'Select', 10 | 'cancel': 'Cancel', 11 | 'empty': ' ', 12 | 'hour_format': '{0:02}', 13 | 'minute_format': '{0:02}', 14 | 'second_format': '{0:02}', 15 | } 16 | 17 | from .timepicker import TimePicker 18 | from .utils import default 19 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/minute/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('minute_timepicker', 'act', 'minute') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'back': 'Back', 10 | 'cancel': 'Cancel', 11 | 'empty': ' ', 12 | 'minute_format': '{0:02}', 13 | 'minute_unavailable_format': '{0:02}', 14 | 'minute_current_format': '({0:02})', 15 | } 16 | 17 | from .timepicker import TimePicker 18 | from .utils import default 19 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/second/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | 4 | # setting timepicker_callback prefix and parts 5 | timepicker_callback = CallbackData('minute_timepicker', 'act', 'minute') 6 | 7 | _default = { 8 | 'select': 'Select', 9 | 'back': 'Back', 10 | 'cancel': 'Cancel', 11 | 'empty': ' ', 12 | 'second_format': '{0:02}', 13 | 'second_unavailable_format': '{0:02}', 14 | 'second_current_format': '({0:02})', 15 | } 16 | 17 | from .timepicker import TimePicker 18 | from .utils import default 19 | -------------------------------------------------------------------------------- /aiogram_timepicker/carousel/full/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram.utils.callback_data import CallbackData 2 | 3 | # setting timepicker_callback prefix and parts 4 | timepicker_callback = CallbackData('f2_tmpck', 'act', 'hours', 'minutes', 'seconds') 5 | 6 | _default = { 7 | 'up': '⇪', 8 | 'down': '⇓', 9 | 'select': 'Select', 10 | 'cancel': 'Cancel', 11 | 'empty': ' ', 12 | 'hour_format': '{0:02}', 13 | 'minute_format': '{0:02}', 14 | 'second_format': '{0:02}', 15 | 'hour_current_format': '{0:02}', 16 | 'minute_current_format': '{0:02}', 17 | 'second_current_format': '{0:02}', 18 | } 19 | 20 | from .timepicker import TimePicker 21 | from .utils import default 22 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/hour/utils.py: -------------------------------------------------------------------------------- 1 | from . import _default 2 | 3 | 4 | def default(**kwargs): 5 | if 'label_empty' in kwargs: 6 | _default['empty'] = kwargs.get('label_empty') 7 | if 'label_select' in kwargs: 8 | _default['select'] = kwargs.get('label_select') 9 | if 'label_cancel' in kwargs: 10 | _default['cancel'] = kwargs.get('label_cancel') 11 | if 'hour_format' in kwargs: 12 | _default['hour_format'] = kwargs.get('hour_format') 13 | if 'hour_unavailable_format' in kwargs: 14 | _default['hour_unavailable_format'] = kwargs.get('hour_unavailable_format') 15 | if 'hour_current_format' in kwargs: 16 | _default['hour_current_format'] = kwargs.get('hour_current_format') 17 | 18 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/min_sec/utils.py: -------------------------------------------------------------------------------- 1 | from . import _default 2 | 3 | 4 | def default(**kwargs): 5 | if 'label_up' in kwargs: 6 | _default['up'] = kwargs.get('label_up') 7 | if 'label_down' in kwargs: 8 | _default['down'] = kwargs.get('label_down') 9 | if 'label_empty' in kwargs: 10 | _default['empty'] = kwargs.get('label_empty') 11 | if 'label_select' in kwargs: 12 | _default['select'] = kwargs.get('label_select') 13 | if 'label_cancel' in kwargs: 14 | _default['cancel'] = kwargs.get('label_cancel') 15 | if 'minute_format' in kwargs: 16 | _default['minute_format'] = kwargs.get('minute_format') 17 | if 'second_format' in kwargs: 18 | _default['second_format'] = kwargs.get('second_format') 19 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "aiogram_timepicker" 7 | version = "0.2.0" 8 | authors = [ 9 | { name="Marat Ashrafzianov", email="maratashdev@gmail.com" }, 10 | ] 11 | description = "Time Selection tool for Aiogram Telegram Bots" 12 | readme = "README.md" 13 | license = { file="LICENSE" } 14 | requires-python = ">=3.7" 15 | classifiers = [ 16 | "Programming Language :: Python :: 3", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | ] 20 | dependencies = [ 21 | "aiogram~=2.12", 22 | 'importlib-metadata; python_version<"3.7"', 23 | ] 24 | 25 | [project.urls] 26 | "Homepage" = "https://github.com/maratx86/aiogram-timepicker" 27 | "Bug Tracker" = "https://github.com/maratx86/aiogram-timepicker/issues" -------------------------------------------------------------------------------- /aiogram_timepicker/panel/full/utils.py: -------------------------------------------------------------------------------- 1 | from . import _default 2 | 3 | 4 | def default(**kwargs): 5 | if 'label_up' in kwargs: 6 | _default['up'] = kwargs.get('label_up') 7 | if 'label_down' in kwargs: 8 | _default['down'] = kwargs.get('label_down') 9 | if 'label_empty' in kwargs: 10 | _default['empty'] = kwargs.get('label_empty') 11 | if 'label_select' in kwargs: 12 | _default['select'] = kwargs.get('label_select') 13 | if 'label_cancel' in kwargs: 14 | _default['cancel'] = kwargs.get('label_cancel') 15 | if 'hour_format' in kwargs: 16 | _default['hour_format'] = kwargs.get('hour_format') 17 | if 'minute_format' in kwargs: 18 | _default['minute_format'] = kwargs.get('minute_format') 19 | if 'second_format' in kwargs: 20 | _default['second_format'] = kwargs.get('second_format') 21 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/__init__.py: -------------------------------------------------------------------------------- 1 | from aiogram_timepicker.panel.full import \ 2 | timepicker_callback as full_timep_callback, \ 3 | TimePicker as FullTimePicker, \ 4 | default as full_timep_default 5 | from aiogram_timepicker.panel.single.hour import \ 6 | timepicker_callback as hour_timep_callback, \ 7 | TimePicker as HourTimePicker, \ 8 | default as hour_timep_default 9 | from aiogram_timepicker.panel.single.minute import \ 10 | timepicker_callback as minute_timep_callback, \ 11 | TimePicker as MinuteTimePicker, \ 12 | default as minute_timep_default 13 | from aiogram_timepicker.panel.single.second import \ 14 | timepicker_callback as second_timep_callback, \ 15 | TimePicker as SecondTimePicker, \ 16 | default as second_timep_default 17 | from aiogram_timepicker.panel.min_sec import \ 18 | timepicker_callback as minsec_timep_callback, \ 19 | TimePicker as MinSecTimePicker, \ 20 | default as minsec_timep_default 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2021 maratx86 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /aiogram_timepicker/carousel/full/utils.py: -------------------------------------------------------------------------------- 1 | from . import _default 2 | 3 | 4 | def default(**kwargs): 5 | if 'label_up' in kwargs: 6 | _default['up'] = kwargs.get('label_up') 7 | if 'label_down' in kwargs: 8 | _default['down'] = kwargs.get('label_down') 9 | if 'label_empty' in kwargs: 10 | _default['empty'] = kwargs.get('label_empty') 11 | if 'label_select' in kwargs: 12 | _default['select'] = kwargs.get('label_select') 13 | if 'label_cancel' in kwargs: 14 | _default['cancel'] = kwargs.get('label_cancel') 15 | if 'hour_format' in kwargs: 16 | _default['hour_format'] = kwargs.get('hour_format') 17 | if 'minute_format' in kwargs: 18 | _default['minute_format'] = kwargs.get('minute_format') 19 | if 'second_format' in kwargs: 20 | _default['second_format'] = kwargs.get('second_format') 21 | if 'hour_current_format' in kwargs: 22 | _default['hour_current_format'] = kwargs.get('hour_current_format') 23 | if 'minute_current_format' in kwargs: 24 | _default['minute_current_format'] = kwargs.get('minute_current_format') 25 | if 'second_current_format' in kwargs: 26 | _default['second_current_format'] = kwargs.get('second_current_format') 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Time Selection tool for Aiogram Telegram Bots 2 | 3 | ## Description 4 | A simple inline time selection tool for [aiogram](https://github.com/aiogram/aiogram) telegram bots written in Python. 5 | Demo: [@aiogram_timepicker_bot](https://t.me/aiogram_timepicker_bot). 6 | 7 | Offers 9 types of panel time picker: 8 | * Panel Pickers (`aiogram_timepicker.panel` and `aiogram_timepicker.panel.single`): 9 | * * Full Time Picker - user can select a time with hours, minutes and seconds. 10 | * * Minute & Second Picker - user can select a time with minutes and seconds. 11 | * * Single Hour Picker - user can select a hour. 12 | * * Single Minute Picker - user can select a minute. 13 | * * Single Second Picker - user can select a second. 14 | * Carousel Pickers (`aiogram_timepicker.carousel`): 15 | * * Full Time Picker - user can select a time with hours, minutes and seconds with carousel. 16 | * Clock Pickers (`aiogram_timepicker.clock`): 17 | * * c24 - user can select a hour between 0 and 24. 18 | * * c24_ts3 - user can select a minute/second between 0 and 21 with 3 timestamp. 19 | * * c60_ts5 - user can select a minute/second between 0 and 55 with 5 timestamp. 20 | 21 | ## Usage 22 | Install package with pip 23 | 24 | pip install aiogram_timepicker 25 | 26 | A full working example on how to use aiogram-timepicker is provided in *bot_example.py*. 27 | You create a timepicker panel and add it to a message with a *reply_markup* parameter, and then you can process it in a `callbackqueyhandler` method using the *process_selection* method. 28 | 29 | ## Demo 30 | Code example is [**bot_example.py**](./bot_example.py) and demo use [@aiogram_timepicker_bot](https://t.me/aiogram_timepicker_bot). 31 | 32 | 33 | ## Licence 34 | Read more about licence [here](./LICENSE.txt). 35 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/min_sec/adapter/minute.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | 4 | def function_replace_default(callback, seconds, minutes): 5 | async def function_create_minute_time_button(timepicker, minute_curr, minute): 6 | label = timepicker.minute_format.format(minute) if minute_curr != minute else \ 7 | timepicker.minute_current_format.format(minute) 8 | callback_data = callback.new('CHANGE', minute, seconds) 9 | return InlineKeyboardButton( 10 | label, 11 | callback_data=callback_data 12 | ) 13 | 14 | async def function_create_minute_group_button(timepicker, minute_current, minute_from, minute_to): 15 | label = '{0:02} - {1:02}'.format(minute_from, minute_to) 16 | callback_data = callback.new('GRP_MIN', '{0}-{1}-{2}'.format( 17 | minute_current, minute_from, minute_to), seconds) 18 | return InlineKeyboardButton( 19 | label, 20 | callback_data=callback_data 21 | ) 22 | 23 | async def function_create_minute_back_button(timepicker): 24 | callback_data = callback.new('GRP_MIN_MENU', minutes, seconds) 25 | return InlineKeyboardButton( 26 | timepicker.label_back, 27 | callback_data=callback_data, 28 | ) 29 | 30 | async def function_create_minute_cancel_button(timepicker): 31 | callback_data = callback.new('CHANGE', minutes, seconds) 32 | return InlineKeyboardButton( 33 | timepicker.label_cancel, 34 | callback_data=callback_data, 35 | ) 36 | 37 | return { 38 | 'create_time': function_create_minute_time_button, 39 | 'create_group': function_create_minute_group_button, 40 | 'create_cancel': function_create_minute_cancel_button, 41 | 'create_back': function_create_minute_back_button, 42 | } 43 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/full/adapter/minute.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | 4 | def function_replace_default(callback, seconds, minutes, hours): 5 | async def function_create_minute_time_button(timepicker, minute_curr, minute): 6 | label = timepicker.minute_format.format(minute) if minute_curr != minute else \ 7 | timepicker.minute_current_format.format(minute) 8 | callback_data = callback.new('CHANGE', hours, minute, seconds) 9 | return InlineKeyboardButton( 10 | label, 11 | callback_data=callback_data 12 | ) 13 | 14 | async def function_create_minute_group_button(timepicker, minute_current, minute_from, minute_to): 15 | label = '{0:02} - {1:02}'.format(minute_from, minute_to) 16 | callback_data = callback.new('GRP_MIN', hours, '{0}-{1}-{2}'.format( 17 | minute_current, minute_from, minute_to), seconds) 18 | return InlineKeyboardButton( 19 | label, 20 | callback_data=callback_data 21 | ) 22 | 23 | async def function_create_minute_back_button(timepicker): 24 | callback_data = callback.new('GRP_MIN_MENU', hours, 0, seconds) 25 | return InlineKeyboardButton( 26 | timepicker.label_back, 27 | callback_data=callback_data, 28 | ) 29 | 30 | async def function_create_minute_cancel_button(timepicker): 31 | callback_data = callback.new('CHANGE', hours, minutes, seconds) 32 | return InlineKeyboardButton( 33 | timepicker.label_cancel, 34 | callback_data=callback_data, 35 | ) 36 | 37 | return { 38 | 'create_time': function_create_minute_time_button, 39 | 'create_group': function_create_minute_group_button, 40 | 'create_cancel': function_create_minute_cancel_button, 41 | 'create_back': function_create_minute_back_button, 42 | } 43 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/min_sec/adapter/second.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | 4 | def function_replace_default(callback, seconds, minutes): 5 | async def function_create_second_time_button(timepicker, second_curr, second): 6 | label = timepicker.second_format.format(second) if second_curr != second else \ 7 | timepicker.second_current_format.format(second) 8 | callback_data = callback.new('CHANGE', minutes, second) 9 | return InlineKeyboardButton( 10 | label, 11 | callback_data=callback_data 12 | ) 13 | 14 | async def function_create_second_group_button(timepicker, second_current, second_from, second_to): 15 | label = '{0:02} - {1:02}'.format(second_from, second_to) 16 | callback_data = callback.new('GRP_SEC', minutes, 17 | '{0}-{1}-{2}'.format(second_current, second_from, second_to)) 18 | return InlineKeyboardButton( 19 | label, 20 | callback_data=callback_data 21 | ) 22 | 23 | async def function_create_second_back_button(timepicker): 24 | callback_data = callback.new('GRP_SEC_MENU', minutes, seconds) 25 | return InlineKeyboardButton( 26 | timepicker.label_back, 27 | callback_data=callback_data, 28 | ) 29 | 30 | async def function_create_second_cancel_button(timepicker): 31 | callback_data = callback.new('CHANGE', minutes, seconds) 32 | return InlineKeyboardButton( 33 | timepicker.label_cancel, 34 | callback_data=callback_data, 35 | ) 36 | 37 | return { 38 | 'create_time': function_create_second_time_button, 39 | 'create_group': function_create_second_group_button, 40 | 'create_cancel': function_create_second_cancel_button, 41 | 'create_back': function_create_second_back_button, 42 | } 43 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/full/adapter/second.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | 4 | def function_replace_default(callback, seconds, minutes, hours): 5 | async def function_create_second_time_button(timepicker, second_curr, second): 6 | label = timepicker.second_format.format(second) if second_curr != second else \ 7 | timepicker.second_current_format.format(second) 8 | callback_data = callback.new('CHANGE', hours, minutes, second) 9 | return InlineKeyboardButton( 10 | label, 11 | callback_data=callback_data 12 | ) 13 | 14 | async def function_create_second_group_button(timepicker, second_current, second_from, second_to): 15 | label = '{0:02} - {1:02}'.format(second_from, second_to) 16 | callback_data = callback.new('GRP_SEC', hours, minutes, 17 | '{0}-{1}-{2}'.format(second_current, second_from, second_to)) 18 | return InlineKeyboardButton( 19 | label, 20 | callback_data=callback_data 21 | ) 22 | 23 | async def function_create_second_back_button(timepicker): 24 | callback_data = callback.new('GRP_SEC_MENU', hours, minutes, seconds) 25 | return InlineKeyboardButton( 26 | timepicker.label_back, 27 | callback_data=callback_data, 28 | ) 29 | 30 | async def function_create_second_cancel_button(timepicker): 31 | callback_data = callback.new('CHANGE', hours, minutes, seconds) 32 | return InlineKeyboardButton( 33 | timepicker.label_cancel, 34 | callback_data=callback_data, 35 | ) 36 | 37 | return { 38 | 'create_time': function_create_second_time_button, 39 | 'create_group': function_create_second_group_button, 40 | 'create_cancel': function_create_second_cancel_button, 41 | 'create_back': function_create_second_back_button, 42 | } 43 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/hour2/utils.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | from . import _default 4 | 5 | 6 | def default(**kwargs): 7 | if 'label_empty' in kwargs: 8 | _default['empty'] = kwargs.get('label_empty') 9 | if 'label_select' in kwargs: 10 | _default['select'] = kwargs.get('label_select') 11 | if 'label_cancel' in kwargs: 12 | _default['cancel'] = kwargs.get('label_cancel') 13 | if 'hour_format' in kwargs: 14 | _default['hour_format'] = kwargs.get('hour_format') 15 | if 'hour_unavailable_format' in kwargs: 16 | _default['hour_unavailable_format'] = kwargs.get('hour_unavailable_format') 17 | if 'hour_current_format' in kwargs: 18 | _default['hour_current_format'] = kwargs.get('hour_current_format') 19 | 20 | 21 | # default simple creation functions 22 | async def default_create_time_button(timepicker, minute_curr, minute): 23 | label = timepicker.minute_format.format(minute) if minute_curr != minute else \ 24 | timepicker.minute_current_format.format(minute) 25 | callback_data = timepicker.callback.new('SELECTED', minute) 26 | return InlineKeyboardButton( 27 | label, 28 | callback_data=callback_data 29 | ) 30 | 31 | 32 | async def default_create_group_button(timepicker, minute_current, minute_from, minute_to): 33 | label = '{0:02} - {1:02}'.format(minute_from, minute_to) 34 | callback_data = timepicker.callback.new('GROUP', '{0}-{1}-{2}'.format( 35 | minute_current, minute_from, minute_to)) 36 | return InlineKeyboardButton( 37 | label, 38 | callback_data=callback_data 39 | ) 40 | 41 | 42 | async def default_create_back_button(timepicker): 43 | callback_data = timepicker.callback.new('BACK', 0) 44 | return InlineKeyboardButton( 45 | timepicker.label_back, 46 | callback_data=callback_data, 47 | ) 48 | 49 | 50 | async def default_create_cancel_button(timepicker): 51 | callback_data = timepicker.callback.new('CANCEL', 0) 52 | return InlineKeyboardButton( 53 | timepicker.label_cancel, 54 | callback_data=callback_data, 55 | ) 56 | 57 | 58 | # default insert functions 59 | async def default_insert_time_button(timepicker, i, inline_kb, button): 60 | inline_kb.insert(button) 61 | inline_kb.row() 62 | 63 | 64 | async def default_insert_group_button(timepicker, minute_from, minute_to, inline_kb, button): 65 | inline_kb.insert(button) 66 | inline_kb.row() 67 | 68 | 69 | async def default_insert_back_button(timepicker, inline_kb, button): 70 | inline_kb.insert(button) 71 | 72 | 73 | async def default_insert_cancel_button(timepicker, inline_kb, button): 74 | inline_kb.insert(button) 75 | 76 | # 77 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/minute/utils.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | from . import _default 4 | 5 | 6 | def default(**kwargs): 7 | if 'label_empty' in kwargs: 8 | _default['empty'] = kwargs.get('label_empty') 9 | if 'label_select' in kwargs: 10 | _default['select'] = kwargs.get('label_select') 11 | if 'label_cancel' in kwargs: 12 | _default['cancel'] = kwargs.get('label_cancel') 13 | if 'label_back' in kwargs: 14 | _default['back'] = kwargs.get('label_back') 15 | if 'minute_format' in kwargs: 16 | _default['minute_format'] = kwargs.get('minute_format') 17 | if 'minute_unavailable_format' in kwargs: 18 | _default['minute_unavailable_format'] = kwargs.get('minute_unavailable_format') 19 | if 'minute_current_format' in kwargs: 20 | _default['minute_current_format'] = kwargs.get('minute_current_format') 21 | 22 | 23 | async def default_create_time_button(timepicker, minute_curr, minute): 24 | label = timepicker.minute_format.format(minute) if minute_curr != minute else \ 25 | timepicker.minute_current_format.format(minute) 26 | callback_data = timepicker.callback.new('SELECTED', minute) 27 | return InlineKeyboardButton( 28 | label, 29 | callback_data=callback_data 30 | ) 31 | 32 | 33 | async def default_insert_time_button(timepicker, i, inline_kb, button): 34 | inline_kb.insert(button) 35 | inline_kb.row() 36 | 37 | 38 | async def default_create_group_button(timepicker, minute_current, minute_from, minute_to): 39 | label = '{0:02} - {1:02}'.format(minute_from, minute_to) 40 | callback_data = timepicker.callback.new('GROUP', '{0}-{1}-{2}'.format( 41 | minute_current, minute_from, minute_to)) 42 | return InlineKeyboardButton( 43 | label, 44 | callback_data=callback_data 45 | ) 46 | 47 | 48 | async def default_insert_group_button(timepicker, minute_from, minute_to, inline_kb, button): 49 | inline_kb.insert(button) 50 | inline_kb.row() 51 | 52 | 53 | async def default_create_back_button(timepicker): 54 | callback_data = timepicker.callback.new('BACK', 0) 55 | return InlineKeyboardButton( 56 | timepicker.label_back, 57 | callback_data=callback_data, 58 | ) 59 | 60 | 61 | async def default_insert_back_button(timepicker, inline_kb, button): 62 | inline_kb.insert(button) 63 | 64 | 65 | async def default_create_cancel_button(timepicker): 66 | callback_data = timepicker.callback.new('CANCEL', 0) 67 | return InlineKeyboardButton( 68 | timepicker.label_cancel, 69 | callback_data=callback_data, 70 | ) 71 | 72 | 73 | async def default_insert_cancel_button(timepicker, inline_kb, button): 74 | inline_kb.insert(button) 75 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/second/utils.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | from . import _default 4 | 5 | 6 | def default(**kwargs): 7 | if 'label_empty' in kwargs: 8 | _default['empty'] = kwargs.get('label_empty') 9 | if 'label_select' in kwargs: 10 | _default['select'] = kwargs.get('label_select') 11 | if 'label_cancel' in kwargs: 12 | _default['cancel'] = kwargs.get('label_cancel') 13 | if 'label_back' in kwargs: 14 | _default['back'] = kwargs.get('label_back') 15 | if 'second_format' in kwargs: 16 | _default['second_format'] = kwargs.get('second_format') 17 | if 'minute_unavailable_format' in kwargs: 18 | _default['second_unavailable_format'] = kwargs.get('second_unavailable_format') 19 | if 'second_current_format' in kwargs: 20 | _default['second_current_format'] = kwargs.get('second_current_format') 21 | 22 | 23 | async def default_create_time_button(timepicker, second_curr, second): 24 | label = timepicker.second_format.format(second) if second_curr != second else \ 25 | timepicker.second_current_format.format(second) 26 | callback_data = timepicker.callback.new('SELECTED', second) 27 | return InlineKeyboardButton( 28 | label, 29 | callback_data=callback_data 30 | ) 31 | 32 | 33 | async def default_insert_time_button(timepicker, i, inline_kb, button): 34 | inline_kb.insert(button) 35 | inline_kb.row() 36 | 37 | 38 | async def default_create_group_button(timepicker, second_current, second_from, second_to): 39 | label = '{0:02} - {1:02}'.format(second_from, second_to) 40 | callback_data = timepicker.callback.new('GROUP', '{0}-{1}-{2}'.format( 41 | second_current, second_from, second_to)) 42 | return InlineKeyboardButton( 43 | label, 44 | callback_data=callback_data 45 | ) 46 | 47 | 48 | async def default_insert_group_button(timepicker, second_from, second_to, inline_kb, button): 49 | inline_kb.insert(button) 50 | inline_kb.row() 51 | 52 | 53 | async def default_create_back_button(timepicker): 54 | callback_data = timepicker.callback.new('BACK', 0) 55 | return InlineKeyboardButton( 56 | timepicker.label_back, 57 | callback_data=callback_data, 58 | ) 59 | 60 | 61 | async def default_insert_back_button(timepicker, inline_kb, button): 62 | inline_kb.insert(button) 63 | 64 | 65 | async def default_create_cancel_button(timepicker): 66 | callback_data = timepicker.callback.new('CANCEL', 0) 67 | return InlineKeyboardButton( 68 | timepicker.label_cancel, 69 | callback_data=callback_data, 70 | ) 71 | 72 | 73 | async def default_insert_cancel_button(timepicker, inline_kb, button): 74 | inline_kb.insert(button) 75 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c24/timepicker.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import CallbackQuery 2 | from aiogram.utils.callback_data import CallbackData 3 | 4 | 5 | from aiogram_timepicker import utils as lib_utils 6 | from aiogram_timepicker.result import Result 7 | 8 | from ..base import BaseTimePicker 9 | from . import timepicker_callback, _default, utils 10 | 11 | 12 | class TimePicker(BaseTimePicker): 13 | def __init__(self, callback: CallbackData = timepicker_callback, **kwargs): 14 | super().__init__(callback, **kwargs) 15 | self.label_empty = _default['empty'] 16 | self.label_center = _default['center'] 17 | self.label_select = _default['select'] 18 | self.label_cancel = _default['cancel'] 19 | self.time_format = _default['time_format'] 20 | self.time_current_format = _default['time_current_format'] 21 | self.functions = lib_utils.Functions( 22 | utils.default_create_time_button, 23 | utils.default_insert_time_button, 24 | None, 25 | None, 26 | utils.default_create_cancel_button, 27 | utils.default_insert_cancel_button, 28 | utils.default_create_back_button, 29 | utils.default_insert_back_button, 30 | create_select=utils.default_create_select_button, 31 | insert_select=utils.default_insert_select_button, 32 | ) 33 | self.kwargs_params(**kwargs) 34 | 35 | def kwargs_params(self, **kwargs): 36 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 37 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 38 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 39 | if 'time_format' in kwargs: 40 | self.time_format = kwargs.get('time_format') or _default.get('time_format') 41 | if 'time_current_format' in kwargs: 42 | self.time_current_format = kwargs.get('time_current_format') or _default.get('time_current_format') 43 | 44 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 45 | """ 46 | Process the callback_query. This method generates a new time picker if forward or 47 | backward is pressed. This method should be called inside a CallbackQueryHandler. 48 | :param query: callback_query, as provided by the CallbackQueryHandler 49 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 50 | :return: Returns an aiogram_timepicker.result.Result object. 51 | """ 52 | r = await super().process_selection(query, data) 53 | r.hours = r.seconds 54 | r.seconds = 0 55 | r.editable = False 56 | return r 57 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c24_ts3/timepicker.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import CallbackQuery 2 | from aiogram.utils.callback_data import CallbackData 3 | 4 | 5 | from aiogram_timepicker import utils as lib_utils 6 | from aiogram_timepicker.result import Result 7 | 8 | from ..base import BaseTimePicker 9 | from . import timepicker_callback, _default, utils 10 | 11 | 12 | class TimePicker(BaseTimePicker): 13 | def __init__(self, callback: CallbackData = timepicker_callback, **kwargs): 14 | super().__init__(callback, **kwargs) 15 | self.label_empty = _default['empty'] 16 | self.label_center = _default['center'] 17 | self.label_select = _default['select'] 18 | self.label_cancel = _default['cancel'] 19 | self.time_format = _default['time_format'] 20 | self.time_current_format = _default['time_current_format'] 21 | self.functions = lib_utils.Functions( 22 | utils.default_create_time_button, 23 | utils.default_insert_time_button, 24 | None, 25 | None, 26 | utils.default_create_cancel_button, 27 | utils.default_insert_cancel_button, 28 | utils.default_create_back_button, 29 | utils.default_insert_back_button, 30 | create_select=utils.default_create_select_button, 31 | insert_select=utils.default_insert_select_button, 32 | ) 33 | self.kwargs_params(**kwargs) 34 | 35 | def kwargs_params(self, **kwargs): 36 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 37 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 38 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 39 | if 'time_format' in kwargs: 40 | self.time_format = kwargs.get('time_format') or _default.get('time_format') 41 | if 'time_current_format' in kwargs: 42 | self.time_current_format = kwargs.get('time_current_format') or _default.get('time_current_format') 43 | 44 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 45 | """ 46 | Process the callback_query. This method generates a new time picker if forward or 47 | backward is pressed. This method should be called inside a CallbackQueryHandler. 48 | :param query: callback_query, as provided by the CallbackQueryHandler 49 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 50 | :return: Returns an aiogram_timepicker.result.Result object. 51 | """ 52 | r = await super().process_selection(query, data) 53 | r.hours = r.seconds 54 | r.seconds = 0 55 | r.editable = False 56 | return r 57 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c60_ts5/timepicker.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import CallbackQuery 2 | from aiogram.utils.callback_data import CallbackData 3 | 4 | 5 | from aiogram_timepicker import utils as lib_utils 6 | from aiogram_timepicker.result import Result 7 | 8 | from ..base import BaseTimePicker 9 | from . import timepicker_callback, _default, utils 10 | 11 | 12 | class TimePicker(BaseTimePicker): 13 | def __init__(self, callback: CallbackData = timepicker_callback, **kwargs): 14 | super().__init__(callback, **kwargs) 15 | self.label_empty = _default['empty'] 16 | self.label_center = _default['center'] 17 | self.label_select = _default['select'] 18 | self.label_cancel = _default['cancel'] 19 | self.time_format = _default['time_format'] 20 | self.time_current_format = _default['time_current_format'] 21 | self.functions = lib_utils.Functions( 22 | utils.default_create_time_button, 23 | utils.default_insert_time_button, 24 | None, 25 | None, 26 | utils.default_create_cancel_button, 27 | utils.default_insert_cancel_button, 28 | utils.default_create_back_button, 29 | utils.default_insert_back_button, 30 | create_select=utils.default_create_select_button, 31 | insert_select=utils.default_insert_select_button, 32 | ) 33 | self.kwargs_params(**kwargs) 34 | 35 | def kwargs_params(self, **kwargs): 36 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 37 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 38 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 39 | if 'time_format' in kwargs: 40 | self.time_format = kwargs.get('time_format') or _default.get('time_format') 41 | if 'time_current_format' in kwargs: 42 | self.time_current_format = kwargs.get('time_current_format') or _default.get('time_current_format') 43 | 44 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 45 | """ 46 | Process the callback_query. This method generates a new time picker if forward or 47 | backward is pressed. This method should be called inside a CallbackQueryHandler. 48 | :param query: callback_query, as provided by the CallbackQueryHandler 49 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 50 | :return: Returns an aiogram_timepicker.result.Result object. 51 | """ 52 | r = await super().process_selection(query, data) 53 | r.minutes = r.seconds 54 | r.seconds = 0 55 | r.editable = False 56 | return r 57 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c60_ts3/timepicker.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import CallbackQuery 2 | from aiogram.utils.callback_data import CallbackData 3 | 4 | 5 | from aiogram_timepicker import utils as lib_utils 6 | from aiogram_timepicker.result import Result 7 | 8 | from ..base import BaseTimePicker 9 | from . import timepicker_callback, _default, utils 10 | 11 | 12 | class TimePicker(BaseTimePicker): 13 | def __init__(self, callback: CallbackData = timepicker_callback, **kwargs): 14 | super().__init__(callback, **kwargs) 15 | self._rows = 9 16 | self._row_center = 4 17 | self.label_empty = _default['empty'] 18 | self.label_center = _default['center'] 19 | self.label_select = _default['select'] 20 | self.label_cancel = _default['cancel'] 21 | self.time_format = _default['time_format'] 22 | self.time_current_format = _default['time_current_format'] 23 | self.functions = lib_utils.Functions( 24 | utils.default_create_time_button, 25 | utils.default_insert_time_button, 26 | None, 27 | None, 28 | utils.default_create_cancel_button, 29 | utils.default_insert_cancel_button, 30 | utils.default_create_back_button, 31 | utils.default_insert_back_button, 32 | create_select=utils.default_create_select_button, 33 | insert_select=utils.default_insert_select_button, 34 | ) 35 | self.kwargs_params(**kwargs) 36 | 37 | def kwargs_params(self, **kwargs): 38 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 39 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 40 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 41 | if 'time_format' in kwargs: 42 | self.time_format = kwargs.get('time_format') or _default.get('time_format') 43 | if 'time_current_format' in kwargs: 44 | self.time_current_format = kwargs.get('time_current_format') or _default.get('time_current_format') 45 | 46 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 47 | """ 48 | Process the callback_query. This method generates a new time picker if forward or 49 | backward is pressed. This method should be called inside a CallbackQueryHandler. 50 | :param query: callback_query, as provided by the CallbackQueryHandler 51 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 52 | :return: Returns an aiogram_timepicker.result.Result object. 53 | """ 54 | r = await super().process_selection(query, data) 55 | r.minutes = r.seconds 56 | r.seconds = 0 57 | r.editable = False 58 | return r 59 | -------------------------------------------------------------------------------- /aiogram_timepicker/utils.py: -------------------------------------------------------------------------------- 1 | class Function: 2 | _default_action = None 3 | _custom_action = None 4 | 5 | def __init__(self, default_acton, custom_action=None): 6 | self._default_action = default_acton 7 | self._custom_action = custom_action 8 | 9 | @property 10 | def action(self): 11 | if self._custom_action: 12 | return self._custom_action 13 | return self._default_action 14 | 15 | @action.setter 16 | def action(self, value): 17 | self._custom_action = value 18 | 19 | 20 | class Functions: 21 | create_time = None 22 | insert_time = None 23 | create_group = None 24 | insert_group = None 25 | create_cancel = None 26 | insert_cancel = None 27 | create_back = None 28 | insert_back = None 29 | create_select = None 30 | insert_select = None 31 | 32 | def __init__(self, 33 | create_button, insert_button, 34 | create_group, insert_group, 35 | create_cancel, insert_cancel, 36 | create_back, insert_back, 37 | **kwargs, 38 | ): 39 | self.create_time = Function(create_button) 40 | self.insert_time = Function(insert_button) 41 | self.create_group = Function(create_group) 42 | self.insert_group = Function(insert_group) 43 | self.create_cancel = Function(create_cancel) 44 | self.insert_cancel = Function(insert_cancel) 45 | self.create_back = Function(create_back) 46 | self.insert_back = Function(insert_back) 47 | if 'create_select' in kwargs: 48 | self.create_select = Function(kwargs.get('create_select')) 49 | if 'insert_select' in kwargs: 50 | self.insert_select = Function(kwargs.get('insert_select')) 51 | 52 | def change_actions(self, **kwargs): 53 | if 'create_time' in kwargs: 54 | self.create_time.action = kwargs.get('create_time') 55 | if 'insert_time' in kwargs: 56 | self.insert_time.action = kwargs.get('insert_time') 57 | if 'create_group' in kwargs: 58 | self.create_group.action = kwargs.get('create_group') 59 | if 'insert_group' in kwargs: 60 | self.insert_group.action = kwargs.get('insert_group') 61 | if 'create_cancel' in kwargs: 62 | self.create_cancel.action = kwargs.get('create_cancel') 63 | if 'insert_cancel' in kwargs: 64 | self.insert_cancel.action = kwargs.get('insert_cancel') 65 | if 'create_back' in kwargs: 66 | self.create_back.action = kwargs.get('create_back') 67 | if 'insert_back' in kwargs: 68 | self.insert_back.action = kwargs.get('insert_back') 69 | if 'create_select' in kwargs: 70 | self.create_select.action = Function(kwargs.get('create_select')) 71 | if 'insert_select' in kwargs: 72 | self.insert_select.action = Function(kwargs.get('insert_select')) 73 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c24_ts3/utils.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | from . import _default 4 | 5 | 6 | def default(**kwargs): 7 | if 'label_empty' in kwargs: 8 | _default['empty'] = kwargs.get('label_empty') 9 | if 'label_center' in kwargs: 10 | _default['center'] = kwargs.get('label_center') 11 | if 'label_select' in kwargs: 12 | _default['select'] = kwargs.get('label_select') 13 | if 'label_cancel' in kwargs: 14 | _default['cancel'] = kwargs.get('label_cancel') 15 | if 'time_format' in kwargs: 16 | _default['time_format'] = kwargs.get('time_format') 17 | if 'time_current_format' in kwargs: 18 | _default['time_current_format'] = kwargs.get('time_current_format') 19 | 20 | 21 | def _time_button_or_not(row, column): 22 | if row == 0 and column == 3: 23 | return True, 0 24 | if row == 1 and column == 1: 25 | return True, 21 26 | if row == 1 and column == 5: 27 | return True, 3 28 | if row == 3 and column == 0: 29 | return True, 18 30 | if row == 3 and column == 6: 31 | return True, 6 32 | if row == 5 and column == 1: 33 | return True, 15 34 | if row == 5 and column == 5: 35 | return True, 9 36 | if row == 6 and column == 3: 37 | return True, 12 38 | 39 | return False, None 40 | 41 | 42 | async def default_create_time_button(timepicker, time_curr, row, column): 43 | t_b, t = _time_button_or_not(row, column) 44 | if t_b: 45 | label = timepicker.time_format.format(t) if time_curr != t else \ 46 | timepicker.time_current_format.format(t) 47 | if timepicker.select_button_needed: 48 | callback_data = timepicker.callback.new('CHANGED', t) 49 | else: 50 | callback_data = timepicker.callback.new('SELECTED', t) 51 | else: 52 | if row == column == 3: 53 | label = timepicker.label_center 54 | else: 55 | label = timepicker.label_empty 56 | callback_data = timepicker.callback.new('IGNORE', row * 7 + column) 57 | return InlineKeyboardButton( 58 | label, 59 | callback_data=callback_data 60 | ) 61 | 62 | 63 | async def default_insert_time_button(timepicker, row, column, inline_kb, button): 64 | inline_kb.insert(button) 65 | if column >= 6: 66 | inline_kb.row() 67 | 68 | 69 | async def default_create_back_button(timepicker): 70 | callback_data = timepicker.callback.new('BACK', 0) 71 | return InlineKeyboardButton( 72 | timepicker.label_back, 73 | callback_data=callback_data, 74 | ) 75 | 76 | 77 | async def default_insert_back_button(timepicker, inline_kb, button): 78 | inline_kb.insert(button) 79 | 80 | 81 | async def default_create_cancel_button(timepicker): 82 | callback_data = timepicker.callback.new('CANCEL', 0) 83 | return InlineKeyboardButton( 84 | timepicker.label_cancel, 85 | callback_data=callback_data, 86 | ) 87 | 88 | 89 | async def default_insert_cancel_button(timepicker, inline_kb, button): 90 | inline_kb.insert(button) 91 | 92 | 93 | async def default_create_select_button(timepicker, time): 94 | callback_data = timepicker.callback.new('SELECTED', time) 95 | return InlineKeyboardButton( 96 | timepicker.label_select, 97 | callback_data=callback_data, 98 | ) 99 | 100 | 101 | async def default_insert_select_button(timepicker, inline_kb, button): 102 | inline_kb.insert(button) 103 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c60_ts5/utils.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | from . import _default 4 | 5 | 6 | def default(**kwargs): 7 | if 'label_empty' in kwargs: 8 | _default['empty'] = kwargs.get('label_empty') 9 | if 'label_center' in kwargs: 10 | _default['center'] = kwargs.get('label_center') 11 | if 'label_select' in kwargs: 12 | _default['select'] = kwargs.get('label_select') 13 | if 'label_cancel' in kwargs: 14 | _default['cancel'] = kwargs.get('label_cancel') 15 | if 'time_format' in kwargs: 16 | _default['time_format'] = kwargs.get('time_format') 17 | if 'time_current_format' in kwargs: 18 | _default['time_current_format'] = kwargs.get('time_current_format') 19 | 20 | 21 | def _time_button_or_not(row, column): 22 | if row == 0: 23 | if column == 3: 24 | return True, 0 25 | return False, None 26 | if row == 6: 27 | if column == 3: 28 | return True, 30 29 | return False, None 30 | if row == 1: 31 | if column == 2: 32 | return True, 55 33 | if column == 4: 34 | return True, 5 35 | return False, None 36 | if row == 2: 37 | if column == 1: 38 | return True, 50 39 | if column == 5: 40 | return True, 10 41 | return False, None 42 | if row == 3: 43 | if column == 0: 44 | return True, 45 45 | if column == 6: 46 | return True, 15 47 | return False, None 48 | if row == 4: 49 | if column == 1: 50 | return True, 40 51 | if column == 5: 52 | return True, 20 53 | return False, None 54 | if row == 5: 55 | if column == 2: 56 | return True, 35 57 | if column == 4: 58 | return True, 25 59 | return False, None 60 | 61 | 62 | async def default_create_time_button(timepicker, time_curr, row, column): 63 | t_b, t = _time_button_or_not(row, column) 64 | if t_b: 65 | label = timepicker.time_format.format(t) if time_curr != t else \ 66 | timepicker.time_current_format.format(t) 67 | if timepicker.select_button_needed: 68 | callback_data = timepicker.callback.new('CHANGED', t) 69 | else: 70 | callback_data = timepicker.callback.new('SELECTED', t) 71 | else: 72 | if row == column == 3: 73 | label = timepicker.label_center 74 | else: 75 | label = timepicker.label_empty 76 | callback_data = timepicker.callback.new('IGNORE', row * 7 + column) 77 | return InlineKeyboardButton( 78 | label, 79 | callback_data=callback_data 80 | ) 81 | 82 | 83 | async def default_insert_time_button(timepicker, row, column, inline_kb, button): 84 | inline_kb.insert(button) 85 | if column >= 6: 86 | inline_kb.row() 87 | 88 | 89 | async def default_create_back_button(timepicker): 90 | callback_data = timepicker.callback.new('BACK', 0) 91 | return InlineKeyboardButton( 92 | timepicker.label_back, 93 | callback_data=callback_data, 94 | ) 95 | 96 | 97 | async def default_insert_back_button(timepicker, inline_kb, button): 98 | inline_kb.insert(button) 99 | 100 | 101 | async def default_create_cancel_button(timepicker): 102 | callback_data = timepicker.callback.new('CANCEL', 0) 103 | return InlineKeyboardButton( 104 | timepicker.label_cancel, 105 | callback_data=callback_data, 106 | ) 107 | 108 | 109 | async def default_insert_cancel_button(timepicker, inline_kb, button): 110 | inline_kb.insert(button) 111 | 112 | 113 | async def default_create_select_button(timepicker, time): 114 | callback_data = timepicker.callback.new('SELECTED', time) 115 | return InlineKeyboardButton( 116 | timepicker.label_select, 117 | callback_data=callback_data, 118 | ) 119 | 120 | 121 | async def default_insert_select_button(timepicker, inline_kb, button): 122 | inline_kb.insert(button) 123 | -------------------------------------------------------------------------------- /aiogram_timepicker/result.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta, time 2 | from enum import Enum 3 | 4 | 5 | class Status(Enum): 6 | UNSET = -1 7 | IGNORE = 0b100000000 8 | ERROR = 0b1000000000 9 | CANCELED = 0b00000000 10 | SELECTED = 0b11000111 11 | SELECTED_SECOND = 0b11000001 12 | SELECTED_MINUTE = 0b11000010 13 | SELECTED_HOUR = 0b11000100 14 | 15 | CHANGED = 0b10000111 16 | CHANGED_SECOND = 0b10000001 17 | CHANGED_MINUTE = 0b10000010 18 | CHANGED_HOUR = 0b10000100 19 | 20 | CHANGE_SECOND = 0b01000001 21 | CHANGE_MINUTE = 0b01000010 22 | CHANGE_HOUR = 0b01000100 23 | 24 | SELECT_GROUP_SECOND = 0b100000001 25 | SELECT_GROUP_MINUTE = 0b100000010 26 | SELECT_GROUP_HOUR = 0b100000100 27 | 28 | BACK_TO_GROUP = 0b100000000000000 29 | 30 | 31 | class Result: 32 | selected: bool 33 | status: Status 34 | 35 | def __init__(self, status: Status = Status.UNSET, **kwargs): 36 | self.status = status 37 | self.selected = kwargs.get('selected', self.status == Status.SELECTED) is True 38 | self._hours = kwargs.get('hours', 0) 39 | self._minutes = kwargs.get('minutes', 0) 40 | self._seconds = kwargs.get('seconds', 0) 41 | self._editable = kwargs.get('editable', False) is True 42 | self._datetime = None 43 | self._timedelta = None 44 | self._time = None 45 | 46 | def _clear(self): 47 | self._datetime = None 48 | self._timedelta = None 49 | self._time = None 50 | 51 | def __str__(self): 52 | if self._time: 53 | return "Result<{status} {time}>".format( 54 | status=self.status, 55 | time=self._time.strftime("%H:%M:%S") 56 | ) 57 | return "Result({status})".format( 58 | status=self.status, 59 | ) 60 | 61 | def __repr__(self): 62 | return "aiogram_timepicker.result.Result(" \ 63 | "{status}, " \ 64 | "hours={hours}, " \ 65 | "minutes={minutes}, " \ 66 | "seconds={seconds}" \ 67 | ")".format( 68 | status=self.status, hours=self._hours, minutes=self._minutes, seconds=self._seconds, 69 | ) 70 | 71 | @property 72 | def editable(self): 73 | return self._editable 74 | 75 | @editable.setter 76 | def editable(self, value: bool): 77 | if not self._editable: 78 | raise AttributeError() 79 | self._editable = value is True 80 | 81 | @property 82 | def hours(self): 83 | return self._hours 84 | 85 | @hours.setter 86 | def hours(self, value: int): 87 | if not self._editable: 88 | raise AttributeError() 89 | self._hours = value 90 | self._clear() 91 | 92 | @property 93 | def minutes(self): 94 | return self._minutes 95 | 96 | @minutes.setter 97 | def minutes(self, value: int): 98 | if not self._editable: 99 | raise AttributeError() 100 | self._minutes = value 101 | self._clear() 102 | 103 | @property 104 | def seconds(self): 105 | return self._seconds 106 | 107 | @seconds.setter 108 | def seconds(self, value: int): 109 | if not self._editable: 110 | raise AttributeError() 111 | self._seconds = value 112 | self._clear() 113 | 114 | @property 115 | def datetime(self): 116 | if not self._datetime: 117 | self._datetime = datetime(1970, 1, 1) + self.timedelta 118 | return self._datetime 119 | 120 | @property 121 | def time(self): 122 | if not self._time: 123 | self._time = time(self._hours, self._minutes, self._seconds) 124 | return self._time 125 | 126 | @property 127 | def timedelta(self): 128 | if not self._timedelta: 129 | self._timedelta = timedelta( 130 | hours=self._hours, 131 | minutes=self._minutes, 132 | seconds=self._seconds 133 | ) 134 | return self._timedelta 135 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/base/timepicker.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardMarkup 2 | from aiogram.utils.callback_data import CallbackData 3 | from aiogram.types import CallbackQuery 4 | 5 | from aiogram_timepicker.result import Result, Status 6 | from aiogram_timepicker import utils as lib_utils 7 | 8 | 9 | class TimePicker: 10 | functions: lib_utils.Functions 11 | 12 | def __init__(self, callback, **kwargs): 13 | self.callback = callback 14 | self.cancel_button_needed = kwargs.get('cancel_button_needed', True) is True 15 | self.select_button_needed = kwargs.get('select_button_needed', False) is True 16 | self._rows = 7 17 | self._columns = 7 18 | self._row_center = 3 19 | self._column_center = 3 20 | self._row_end = 6 21 | 22 | def kwargs_params(self, **kwargs): 23 | pass 24 | 25 | def change_default_action(self, **kwargs): 26 | if self.functions: 27 | self.functions.change_actions(**kwargs) 28 | return self 29 | 30 | async def start_picker( 31 | self, 32 | time=-1, 33 | **kwargs 34 | ) -> InlineKeyboardMarkup: 35 | """ 36 | Creates an inline keyboard with the list of minutes for selection 37 | :param int time: Time to use in the picker, if None the -1 is used. (0...24 or -1) 38 | :return: Returns InlineKeyboardMarkup object with the keyboard. 39 | """ 40 | if len(kwargs): 41 | self.kwargs_params(**kwargs) 42 | inline_kb = InlineKeyboardMarkup(row_width=self._rows) 43 | for row in range(self._rows): 44 | for column in range(self._columns): 45 | await self.functions.insert_time.action( 46 | self, 47 | row, 48 | column, 49 | inline_kb, 50 | await self.functions.create_time.action( 51 | self, 52 | time, 53 | row, 54 | column, 55 | ), 56 | ) 57 | if self.cancel_button_needed: 58 | await self.functions.insert_cancel.action( 59 | self, 60 | inline_kb, 61 | await self.functions.create_cancel.action(self) 62 | ) 63 | if self.select_button_needed: 64 | await self.functions.insert_select.action( 65 | self, 66 | inline_kb, 67 | await self.functions.create_select.action(self, time) 68 | ) 69 | return inline_kb 70 | 71 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 72 | """ 73 | Process the callback_query. This method generates a new time picker if forward or 74 | backward is pressed. This method should be called inside a CallbackQueryHandler. 75 | :param query: callback_query, as provided by the CallbackQueryHandler 76 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 77 | :return: Returns an aiogram_timepicker.result.Result object. 78 | """ 79 | time = int(data['time']) if data['time'] and data['time'].isdigit() else 0 80 | 81 | if data['act'] == "IGNORE": 82 | await query.answer(cache_time=60) 83 | return Result( 84 | Status.IGNORE, 85 | seconds=time, 86 | editable=True, 87 | ) 88 | if data['act'] == "CANCEL": 89 | return Result( 90 | Status.CANCELED, 91 | seconds=time, 92 | editable=True, 93 | ) 94 | if data['act'] == "CHANGED": 95 | kb = await self.start_picker(time) 96 | await query.message.edit_reply_markup(kb) 97 | await query.answer(cache_time=60) 98 | return Result( 99 | Status.CHANGED_HOUR, 100 | seconds=time, 101 | editable=True, 102 | ) 103 | if data['act'] == "SELECTED": 104 | return Result( 105 | Status.SELECTED_HOUR, 106 | selected=True, 107 | seconds=time, 108 | editable=True, 109 | ) 110 | return Result( 111 | Status.UNSET, 112 | seconds=time, 113 | editable=True, 114 | ) 115 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c24/utils.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | from . import _default 4 | 5 | 6 | def default(**kwargs): 7 | if 'label_empty' in kwargs: 8 | _default['empty'] = kwargs.get('label_empty') 9 | if 'label_center' in kwargs: 10 | _default['center'] = kwargs.get('label_center') 11 | if 'label_select' in kwargs: 12 | _default['select'] = kwargs.get('label_select') 13 | if 'label_cancel' in kwargs: 14 | _default['cancel'] = kwargs.get('label_cancel') 15 | if 'time_format' in kwargs: 16 | _default['time_format'] = kwargs.get('time_format') 17 | if 'time_current_format' in kwargs: 18 | _default['time_current_format'] = kwargs.get('time_current_format') 19 | 20 | 21 | def _time_button_or_not(row, column): 22 | if row == 0: 23 | if column == 1: 24 | return True, 22 25 | elif column == 2: 26 | return True, 23 27 | elif column == 3: 28 | return True, 0 29 | elif column == 4: 30 | return True, 1 31 | elif column == 5: 32 | return True, 2 33 | elif row == 1: 34 | if column == 0: 35 | return True, 20 36 | elif column == 1: 37 | return True, 21 38 | elif column == 5: 39 | return True, 3 40 | elif column == 6: 41 | return True, 4 42 | elif row == 5: 43 | if column == 0: 44 | return True, 16 45 | elif column == 1: 46 | return True, 15 47 | elif column == 5: 48 | return True, 9 49 | elif column == 6: 50 | return True, 8 51 | elif row == 6: 52 | if column == 1: 53 | return True, 14 54 | elif column == 2: 55 | return True, 13 56 | elif column == 3: 57 | return True, 12 58 | elif column == 4: 59 | return True, 11 60 | elif column == 5: 61 | return True, 10 62 | elif row == 2: 63 | if column == 0: 64 | return True, 19 65 | elif column == 6: 66 | return True, 5 67 | elif row == 3: 68 | if column == 0: 69 | return True, 18 70 | elif column == 6: 71 | return True, 6 72 | elif row == 4: 73 | if column == 0: 74 | return True, 17 75 | elif column == 6: 76 | return True, 7 77 | return False, None 78 | 79 | 80 | async def default_create_time_button(timepicker, time_curr, row, column): 81 | t_b, t = _time_button_or_not(row, column) 82 | if t_b: 83 | label = timepicker.time_format.format(t) if time_curr != t else \ 84 | timepicker.time_current_format.format(t) 85 | if timepicker.select_button_needed: 86 | callback_data = timepicker.callback.new('CHANGED', t) 87 | else: 88 | callback_data = timepicker.callback.new('SELECTED', t) 89 | else: 90 | if row == column == 3: 91 | label = timepicker.label_center 92 | else: 93 | label = timepicker.label_empty 94 | callback_data = timepicker.callback.new('IGNORE', row * 7 + column) 95 | return InlineKeyboardButton( 96 | label, 97 | callback_data=callback_data 98 | ) 99 | 100 | 101 | async def default_insert_time_button(timepicker, row, column, inline_kb, button): 102 | inline_kb.insert(button) 103 | if column >= 6: 104 | inline_kb.row() 105 | 106 | 107 | async def default_create_back_button(timepicker): 108 | callback_data = timepicker.callback.new('BACK', 0) 109 | return InlineKeyboardButton( 110 | timepicker.label_back, 111 | callback_data=callback_data, 112 | ) 113 | 114 | 115 | async def default_insert_back_button(timepicker, inline_kb, button): 116 | inline_kb.insert(button) 117 | 118 | 119 | async def default_create_cancel_button(timepicker): 120 | callback_data = timepicker.callback.new('CANCEL', 0) 121 | return InlineKeyboardButton( 122 | timepicker.label_cancel, 123 | callback_data=callback_data, 124 | ) 125 | 126 | 127 | async def default_insert_cancel_button(timepicker, inline_kb, button): 128 | inline_kb.insert(button) 129 | 130 | 131 | async def default_create_select_button(timepicker, time): 132 | callback_data = timepicker.callback.new('SELECTED', time) 133 | return InlineKeyboardButton( 134 | timepicker.label_select, 135 | callback_data=callback_data, 136 | ) 137 | 138 | 139 | async def default_insert_select_button(timepicker, inline_kb, button): 140 | inline_kb.insert(button) 141 | -------------------------------------------------------------------------------- /aiogram_timepicker/clock/single/c60_ts3/utils.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import InlineKeyboardButton 2 | 3 | from . import _default 4 | 5 | 6 | def default(**kwargs): 7 | if 'label_empty' in kwargs: 8 | _default['empty'] = kwargs.get('label_empty') 9 | if 'label_center' in kwargs: 10 | _default['center'] = kwargs.get('label_center') 11 | if 'label_select' in kwargs: 12 | _default['select'] = kwargs.get('label_select') 13 | if 'label_cancel' in kwargs: 14 | _default['cancel'] = kwargs.get('label_cancel') 15 | if 'time_format' in kwargs: 16 | _default['time_format'] = kwargs.get('time_format') 17 | if 'time_current_format' in kwargs: 18 | _default['time_current_format'] = kwargs.get('time_current_format') 19 | 20 | 21 | def _time_button_or_not(row, column): 22 | if row == 0: 23 | if column == 3: 24 | return True, 0 25 | elif column == 2: 26 | return True, 57 27 | elif column == 4: 28 | return True, 3 29 | return False, None 30 | if row == 8: 31 | if column == 3: 32 | return True, 30 33 | elif column == 2: 34 | return True, 33 35 | elif column == 4: 36 | return True, 27 37 | return False, None 38 | if row == 1: 39 | if column == 1: 40 | return True, 54 41 | if column == 5: 42 | return True, 6 43 | return False, None 44 | if row == 2: 45 | if column == 0: 46 | return True, 51 47 | if column == 6: 48 | return True, 9 49 | return False, None 50 | if row == 3: 51 | if column == 0: 52 | return True, 48 53 | if column == 6: 54 | return True, 12 55 | return False, None 56 | if row == 4: 57 | if column == 0: 58 | return True, 45 59 | if column == 6: 60 | return True, 15 61 | return False, None 62 | if row == 5: 63 | if column == 0: 64 | return True, 42 65 | if column == 6: 66 | return True, 18 67 | return False, None 68 | if row == 6: 69 | if column == 0: 70 | return True, 39 71 | if column == 6: 72 | return True, 21 73 | return False, None 74 | if row == 7: 75 | if column == 1: 76 | return True, 36 77 | if column == 5: 78 | return True, 24 79 | return False, None 80 | 81 | 82 | async def default_create_time_button(timepicker, time_curr, row, column): 83 | t_b, t = _time_button_or_not(row, column) 84 | if t_b: 85 | label = timepicker.time_format.format(t) if time_curr != t else \ 86 | timepicker.time_current_format.format(t) 87 | if timepicker.select_button_needed: 88 | callback_data = timepicker.callback.new('CHANGED', t) 89 | else: 90 | callback_data = timepicker.callback.new('SELECTED', t) 91 | else: 92 | if row == timepicker._row_center and column == timepicker._column_center: 93 | label = timepicker.label_center 94 | else: 95 | label = timepicker.label_empty 96 | callback_data = timepicker.callback.new('IGNORE', row * timepicker._rows + column) 97 | return InlineKeyboardButton( 98 | label, 99 | callback_data=callback_data 100 | ) 101 | 102 | 103 | async def default_insert_time_button(timepicker, row, column, inline_kb, button): 104 | inline_kb.insert(button) 105 | if column >= timepicker._row_end: 106 | inline_kb.row() 107 | 108 | 109 | async def default_create_back_button(timepicker): 110 | callback_data = timepicker.callback.new('BACK', 0) 111 | return InlineKeyboardButton( 112 | timepicker.label_back, 113 | callback_data=callback_data, 114 | ) 115 | 116 | 117 | async def default_insert_back_button(timepicker, inline_kb, button): 118 | inline_kb.insert(button) 119 | 120 | 121 | async def default_create_cancel_button(timepicker): 122 | callback_data = timepicker.callback.new('CANCEL', 0) 123 | return InlineKeyboardButton( 124 | timepicker.label_cancel, 125 | callback_data=callback_data, 126 | ) 127 | 128 | 129 | async def default_insert_cancel_button(timepicker, inline_kb, button): 130 | inline_kb.insert(button) 131 | 132 | 133 | async def default_create_select_button(timepicker, time): 134 | callback_data = timepicker.callback.new('SELECTED', time) 135 | return InlineKeyboardButton( 136 | timepicker.label_select, 137 | callback_data=callback_data, 138 | ) 139 | 140 | 141 | async def default_insert_select_button(timepicker, inline_kb, button): 142 | inline_kb.insert(button) 143 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/hour/timepicker.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 4 | from aiogram.utils.callback_data import CallbackData 5 | 6 | from aiogram_timepicker.result import Result, Status 7 | 8 | from . import timepicker_callback, _default 9 | 10 | 11 | class TimePicker: 12 | def __init__(self, interval: int = 1, callback: CallbackData = timepicker_callback, **kwargs): 13 | self.cancel = None 14 | self.interval = interval 15 | self.callback = callback 16 | self.label_empty = _default['empty'] 17 | self.label_select = _default['select'] 18 | self.label_cancel = _default['cancel'] 19 | self.hour_format = _default['hour_format'] 20 | self.hour_current_format = _default['hour_current_format'] 21 | self.hour_unavailable_format = _default['hour_unavailable_format'] 22 | self.str_callback = kwargs.get('str_callback', None) 23 | self.is_available = None 24 | self.check_available = False 25 | self.kwargs_params(**kwargs) 26 | 27 | def kwargs_params(self, **kwargs): 28 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 29 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 30 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 31 | self.hour_format = kwargs.get('hour_format', self.hour_format or _default['hour_format']) 32 | self.hour_current_format = kwargs.get('hour_current_format', self.hour_current_format or _default['hour_current_format']) 33 | self.hour_unavailable_format = kwargs.get('hour_unavailable_format', self.hour_unavailable_format or _default['hour_unavailable_format']) 34 | self.is_available = kwargs.get('is_available', self.is_available) 35 | self.check_available = kwargs.get('check_available', self.check_available) is True 36 | 37 | async def start_picker( 38 | self, 39 | hour: int = 12, 40 | cancel: typing.Any = None, 41 | **kwargs 42 | ) -> InlineKeyboardMarkup: 43 | """ 44 | Creates an inline keyboard with the list of hours for selection 45 | :param int hour: Hour to use in the picker, if None the 12 is used. (0...23) 46 | :param int cancel: Action to perform after canceling, if None the keyboard is closed. 47 | :return: Returns InlineKeyboardMarkup object with the keyboard. 48 | """ 49 | if len(kwargs): 50 | self.kwargs_params(**kwargs) 51 | self.cancel = cancel 52 | inline_kb = InlineKeyboardMarkup(row_width=6) 53 | for row in range(4): 54 | for column in range(6): 55 | t = row * 6 + column 56 | if self.check_available and not await self.is_available(t): 57 | label = self.hour_unavailable_format.format(t) 58 | callback_data = self.callback.new('IGNORE', t) 59 | else: 60 | label = self.hour_format.format(t) if t != hour else self.hour_current_format.format(t) 61 | callback_data = self.callback.new('SELECTED', t) if self.callback else \ 62 | self.str_callback.format(hour=t) 63 | inline_kb.insert(InlineKeyboardButton(label, callback_data=callback_data)) 64 | inline_kb.row() 65 | inline_kb.row() 66 | inline_kb.insert(InlineKeyboardButton( 67 | self.label_cancel, 68 | callback_data=self.callback.new('CANCEL', t) if self.callback else \ 69 | self.str_callback.format(hour=t), 70 | )) 71 | return inline_kb 72 | 73 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 74 | """ 75 | Process the callback_query. This method generates a new time picker if forward or 76 | backward is pressed. This method should be called inside a CallbackQueryHandler. 77 | :param query: callback_query, as provided by the CallbackQueryHandler 78 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 79 | :return: Returns an aiogram_timepicker.result.Result object. 80 | """ 81 | hours = int(data['hour']) if data['hour'] and data['hour'].isdigit() else 0 82 | if data['act'] == "CANCEL": 83 | if self.cancel: 84 | await self.cancel(query, data) 85 | else: 86 | await query.message.delete() 87 | return Result( 88 | Status.CANCELED, 89 | hours=hours 90 | ) 91 | if not data['hour'].isdigit(): 92 | return Result(Status.ERROR) 93 | # processing empty buttons, answering with no action 94 | if data['act'] == "IGNORE": 95 | await query.answer(cache_time=60) 96 | return Result(Status.IGNORE) 97 | if data['act'] == "SELECTED": 98 | return Result( 99 | Status.SELECTED_HOUR, 100 | selected=True, hours=hours, 101 | ) 102 | return Result(hours=hours) 103 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/second/timepicker.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import CallbackQuery 2 | from aiogram.utils.callback_data import CallbackData 3 | 4 | from aiogram_timepicker.panel.single.minute import timepicker 5 | from aiogram_timepicker.result import Result, Status 6 | from aiogram_timepicker import utils as lib_utils 7 | 8 | from . import timepicker_callback, _default, utils 9 | 10 | 11 | class TimePicker(timepicker.TimePicker): 12 | def __init__(self, interval: int = 1, callback: CallbackData = timepicker_callback, **kwargs): 13 | self.cancel = None 14 | if not 60 % interval: 15 | ValueError('MinuteTimepicker: interval must be an integer value from 0 to 59.') 16 | self.interval = interval 17 | self.callback = callback 18 | self.label_empty = _default['empty'] 19 | self.label_select = _default['select'] 20 | self.label_cancel = _default['cancel'] 21 | self.label_back = _default['back'] 22 | self.second_format = _default['second_format'] 23 | self.second_current_format = _default['second_current_format'] 24 | self.second_unavailable_format = _default['second_unavailable_format'] 25 | self.str_callback = None 26 | self.is_available = None 27 | self.check_available = False 28 | self.group_inside_count = 10 29 | self.group_count = 6 30 | self.functions = lib_utils.Functions( 31 | utils.default_create_time_button, 32 | utils.default_insert_time_button, 33 | utils.default_create_group_button, 34 | utils.default_insert_group_button, 35 | utils.default_create_cancel_button, 36 | utils.default_insert_cancel_button, 37 | utils.default_create_back_button, 38 | utils.default_insert_back_button, 39 | ) 40 | self.kwargs_params(**kwargs) 41 | 42 | def kwargs_params(self, **kwargs): 43 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 44 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 45 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 46 | self.label_back = kwargs.get('label_back', self.label_back or _default['back']) 47 | self.second_format = kwargs.get('second_format', 48 | self.second_format or _default['second_format']) 49 | self.second_current_format = kwargs.get('second_current_format', 50 | self.second_current_format or _default['second_current_format']) 51 | self.second_unavailable_format = kwargs.get('second_unavailable_format', 52 | self.second_unavailable_format or 53 | _default['second_unavailable_format']) 54 | self.str_callback = kwargs.get('str_callback', self.str_callback or None) 55 | self.is_available = kwargs.get('is_available', self.is_available) 56 | self.check_available = kwargs.get('check_available', self.check_available) is True 57 | self.group_inside_count = kwargs.get('group_inside_count', self.group_inside_count or 10) 58 | self.group_count = kwargs.get('group_count', self.group_count or 6) 59 | 60 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 61 | """ 62 | Process the callback_query. This method generates a new time picker if forward or 63 | backward is pressed. This method should be called inside a CallbackQueryHandler. 64 | :param query: callback_query, as provided by the CallbackQueryHandler 65 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 66 | :return: Returns an aiogram_timepicker.result.Result object. 67 | """ 68 | seconds = int(data['second']) if data['second'].isdigit() else 0 69 | if data['act'] == "IGNORE": 70 | await query.answer(cache_time=60) 71 | return Result( 72 | Status.IGNORE, 73 | seconds=seconds, 74 | ) 75 | if data['act'] == "CANCEL": 76 | if self.cancel: 77 | await self.cancel(query, data) 78 | else: 79 | await query.message.delete() 80 | return Result( 81 | Status.CANCELED, 82 | seconds=seconds, 83 | ) 84 | if data['act'] == "BACK": 85 | await query.message.edit_reply_markup( 86 | await self.start_picker( 87 | seconds, 88 | ) 89 | ) 90 | return Result( 91 | Status.BACK_TO_GROUP, 92 | seconds=seconds 93 | ) 94 | if data['act'] == "GROUP": 95 | await self.process_group_selection(query, data['second']) 96 | return Result( 97 | Status.SELECT_GROUP_MINUTE, 98 | seconds=seconds 99 | ) 100 | if data['act'] == "SELECTED": 101 | return Result( 102 | Status.SELECTED_SECOND, 103 | selected=True, 104 | seconds=seconds, 105 | ) 106 | return Result( 107 | Status.UNSET, 108 | seconds=seconds, 109 | ) 110 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/hour2/timepicker.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 4 | from aiogram.utils.callback_data import CallbackData 5 | 6 | from aiogram_timepicker.result import Result, Status 7 | from aiogram_timepicker import utils as lib_utils 8 | 9 | from . import timepicker_callback, _default, utils 10 | 11 | 12 | class TimePicker: 13 | def __init__(self, interval: int = 1, callback: CallbackData = timepicker_callback, **kwargs): 14 | self.cancel = None 15 | self.interval = interval 16 | self.callback = callback 17 | self.label_empty = _default['empty'] 18 | self.label_select = _default['select'] 19 | self.label_cancel = _default['cancel'] 20 | self.hour_format = _default['hour_format'] 21 | self.hour_current_format = _default['hour_current_format'] 22 | self.hour_unavailable_format = _default['hour_unavailable_format'] 23 | self.str_callback = kwargs.get('str_callback', None) 24 | self.is_available = None 25 | self.check_available = False 26 | self.kwargs_params(**kwargs) 27 | self.functions = lib_utils.Functions( 28 | utils.default_create_time_button, 29 | utils.default_insert_time_button, 30 | utils.default_create_group_button, 31 | utils.default_insert_group_button, 32 | utils.default_create_cancel_button, 33 | utils.default_insert_cancel_button, 34 | utils.default_create_back_button, 35 | utils.default_insert_back_button, 36 | ) 37 | 38 | def kwargs_params(self, **kwargs): 39 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 40 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 41 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 42 | self.hour_format = kwargs.get('hour_format', self.hour_format or _default['hour_format']) 43 | self.hour_current_format = kwargs.get('hour_current_format', self.hour_current_format or _default['hour_current_format']) 44 | self.hour_unavailable_format = kwargs.get('hour_unavailable_format', self.hour_unavailable_format or _default['hour_unavailable_format']) 45 | self.is_available = kwargs.get('is_available', self.is_available) 46 | self.check_available = kwargs.get('check_available', self.check_available) is True 47 | 48 | async def start_picker( 49 | self, 50 | hour: int = 12, 51 | cancel: typing.Any = None, 52 | **kwargs 53 | ) -> InlineKeyboardMarkup: 54 | """ 55 | Creates an inline keyboard with the list of hours for selection 56 | :param int hour: Hour to use in the picker, if None the 12 is used. (0...23) 57 | :param int cancel: Action to perform after canceling, if None the keyboard is closed. 58 | :return: Returns InlineKeyboardMarkup object with the keyboard. 59 | """ 60 | if len(kwargs): 61 | self.kwargs_params(**kwargs) 62 | self.cancel = cancel 63 | inline_kb = InlineKeyboardMarkup(row_width=6) 64 | for row in range(4): 65 | for column in range(6): 66 | t = row * 6 + column 67 | if self.check_available and not await self.is_available(t): 68 | label = self.hour_unavailable_format.format(t) 69 | callback_data = self.callback.new('IGNORE', t) 70 | else: 71 | label = self.hour_format.format(t) if t != hour else self.hour_current_format.format(t) 72 | callback_data = self.callback.new('SELECTED', t) if self.callback else \ 73 | self.str_callback.format(hour=t) 74 | inline_kb.insert(InlineKeyboardButton(label, callback_data=callback_data)) 75 | inline_kb.row() 76 | inline_kb.row() 77 | inline_kb.insert(InlineKeyboardButton( 78 | self.label_cancel, 79 | callback_data=self.callback.new('CANCEL', t) if self.callback else \ 80 | self.str_callback.format(hour=t), 81 | )) 82 | return inline_kb 83 | 84 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 85 | """ 86 | Process the callback_query. This method generates a new time picker if forward or 87 | backward is pressed. This method should be called inside a CallbackQueryHandler. 88 | :param query: callback_query, as provided by the CallbackQueryHandler 89 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 90 | :return: Returns an aiogram_timepicker.result.Result object. 91 | """ 92 | hours = int(data['hour']) if data['hour'] and data['hour'].isdigit() else 0 93 | if data['act'] == "CANCEL": 94 | if self.cancel: 95 | await self.cancel(query, data) 96 | else: 97 | await query.message.delete() 98 | return Result( 99 | Status.CANCELED, 100 | hours=hours 101 | ) 102 | if not data['hour'].isdigit(): 103 | return Result(Status.ERROR) 104 | # processing empty buttons, answering with no action 105 | if data['act'] == "IGNORE": 106 | await query.answer(cache_time=60) 107 | return Result(Status.IGNORE) 108 | if data['act'] == "SELECTED": 109 | return Result( 110 | Status.SELECTED_HOUR, 111 | selected=True, hours=hours, 112 | ) 113 | return Result(hours=hours) 114 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/single/minute/timepicker.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from aiogram.types import InlineKeyboardMarkup 4 | from aiogram.utils.callback_data import CallbackData 5 | from aiogram.types import CallbackQuery 6 | 7 | from aiogram_timepicker.result import Result, Status 8 | from aiogram_timepicker import utils as lib_utils 9 | from . import utils, timepicker_callback 10 | from .utils import _default 11 | 12 | 13 | class TimePicker: 14 | def __init__(self, interval: int = 1, callback: CallbackData = timepicker_callback, **kwargs): 15 | self.cancel = None 16 | if not 60 % interval: 17 | ValueError('MinuteTimepicker: interval must be an integer value from 0 to 59.') 18 | self.interval = interval 19 | self.callback = callback 20 | self.label_empty = _default['empty'] 21 | self.label_select = _default['select'] 22 | self.label_cancel = _default['cancel'] 23 | self.label_back = _default['back'] 24 | self.minute_format = _default['minute_format'] 25 | self.minute_current_format = _default['minute_current_format'] 26 | self.minute_unavailable_format = _default['minute_unavailable_format'] 27 | self.str_callback = None 28 | self.is_available = None 29 | self.check_available = False 30 | self.group_inside_count = 10 31 | self.group_count = 6 32 | self.functions = lib_utils.Functions( 33 | utils.default_create_time_button, 34 | utils.default_insert_time_button, 35 | utils.default_create_group_button, 36 | utils.default_insert_group_button, 37 | utils.default_create_cancel_button, 38 | utils.default_insert_cancel_button, 39 | utils.default_create_back_button, 40 | utils.default_insert_back_button, 41 | ) 42 | self.kwargs_params(**kwargs) 43 | 44 | def change_default_action(self, **kwargs): 45 | self.functions.change_actions(**kwargs) 46 | return self 47 | 48 | def kwargs_params(self, **kwargs): 49 | self.label_empty = kwargs.get('label_empty', self.label_empty or _default['empty']) 50 | self.label_select = kwargs.get('label_select', self.label_select or _default['select']) 51 | self.label_cancel = kwargs.get('label_cancel', self.label_cancel or _default['cancel']) 52 | self.label_back = kwargs.get('label_back', self.label_back or _default['back']) 53 | self.minute_format = kwargs.get('minute_format', self.minute_format or _default['minute_format']) 54 | self.minute_current_format = kwargs.get('minute_current_format', 55 | self.minute_current_format or _default['minute_current_format']) 56 | self.minute_unavailable_format = kwargs.get('minute_unavailable_format', 57 | self.minute_unavailable_format or _default[ 58 | 'minute_unavailable_format']) 59 | self.str_callback = kwargs.get('str_callback', self.str_callback or None) 60 | self.is_available = kwargs.get('is_available', self.is_available) 61 | self.check_available = kwargs.get('check_available', self.check_available) is True 62 | self.group_inside_count = kwargs.get('group_inside_count', self.group_inside_count or 10) 63 | self.group_count = kwargs.get('group_count', self.group_count or 6) 64 | 65 | async def start_picker( 66 | self, 67 | minute: int = 0, 68 | cancel: typing.Any = None, 69 | **kwargs 70 | ) -> InlineKeyboardMarkup: 71 | """ 72 | Creates an inline keyboard with the list of minutes for selection 73 | :param int minute: Minute to use in the picker, if None the -1 is used. (0...59) 74 | :param int cancel: Action to perform after canceling, if None the keyboard is closed. 75 | :return: Returns InlineKeyboardMarkup object with the keyboard. 76 | """ 77 | if len(kwargs): 78 | self.kwargs_params(**kwargs) 79 | self.cancel = cancel 80 | inline_kb = InlineKeyboardMarkup(row_width=2) 81 | for group_i in range(self.group_count): 82 | minute_from = group_i * self.group_inside_count 83 | minute_to = minute_from + self.group_inside_count - 1 84 | await self.functions.insert_group.action( 85 | self, 86 | minute_from, 87 | minute_to, 88 | inline_kb, 89 | await self.functions.create_group.action(self, minute, minute_from, minute_to) 90 | ) 91 | await self.functions.insert_cancel.action( 92 | self, 93 | inline_kb, 94 | await self.functions.create_cancel.action(self) 95 | ) 96 | return inline_kb 97 | 98 | async def time_group_picker( 99 | self, 100 | minute_curr, 101 | minute_from, 102 | minute_to, 103 | ): 104 | inline_kb = InlineKeyboardMarkup(row_width=2) 105 | for minute in range(minute_from, minute_to + 1, self.interval): 106 | await self.functions.insert_time.action( 107 | self, 108 | minute, 109 | inline_kb, 110 | await self.functions.create_time.action(self, minute_curr, minute) 111 | ) 112 | await self.functions.insert_back.action( 113 | self, 114 | inline_kb, 115 | await self.functions.create_back.action(self) 116 | ) 117 | await self.functions.insert_cancel.action( 118 | self, 119 | inline_kb, 120 | await self.functions.create_cancel.action(self) 121 | ) 122 | return inline_kb 123 | 124 | async def process_group_selection(self, query: CallbackQuery, minute_data: str) -> tuple: 125 | group = minute_data.split('-') 126 | 127 | if len(group) != 3 or any(map(lambda elem: not elem.isdigit(), group)): 128 | await query.message.delete_reply_markup() 129 | return Result( 130 | Status.ERROR, 131 | message="GROUP selection numbers should be an integer" 132 | ) 133 | minute_curr = int(group[0]) 134 | minute_from = int(group[1]) 135 | minute_to = int(group[2]) 136 | inline_kb = await self.time_group_picker(minute_curr, minute_from, minute_to) 137 | await query.message.edit_reply_markup(inline_kb) 138 | 139 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> Result: 140 | """ 141 | Process the callback_query. This method generates a new time picker if forward or 142 | backward is pressed. This method should be called inside a CallbackQueryHandler. 143 | :param query: callback_query, as provided by the CallbackQueryHandler 144 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 145 | :return: Returns an aiogram_timepicker.result.Result object. 146 | """ 147 | minutes = int(data['minute']) if data['minute'].isdigit() else 0 148 | if data['act'] == "IGNORE": 149 | await query.answer(cache_time=60) 150 | return Result( 151 | Status.IGNORE, 152 | minutes=minutes, 153 | ) 154 | if data['act'] == "CANCEL": 155 | if self.cancel: 156 | await self.cancel(query, data) 157 | else: 158 | await query.message.delete() 159 | return Result( 160 | Status.CANCELED, 161 | minutes=minutes, 162 | ) 163 | if data['act'] == "BACK": 164 | await query.message.edit_reply_markup( 165 | await self.start_picker( 166 | minutes, 167 | ) 168 | ) 169 | return Result( 170 | Status.BACK_TO_GROUP, 171 | minutes=minutes 172 | ) 173 | if data['act'] == "GROUP": 174 | await self.process_group_selection(query, data['minute']) 175 | return Result( 176 | Status.SELECT_GROUP_MINUTE, 177 | minutes=int(data['minute']) if data['minute'].isdigit() else 0 178 | ) 179 | if data['act'] == "SELECTED": 180 | return Result( 181 | Status.SELECTED_MINUTE, 182 | selected=True, 183 | minutes=minutes, 184 | ) 185 | return Result( 186 | Status.UNSET, 187 | minutes=minutes, 188 | ) 189 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/min_sec/timepicker.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | import typing 3 | 4 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton 5 | from aiogram.utils.callback_data import CallbackData 6 | from aiogram.types import CallbackQuery 7 | 8 | from aiogram_timepicker import result 9 | from aiogram_timepicker.panel.single import minute, second 10 | from . import _default, timepicker_callback, adapter 11 | 12 | 13 | class TimePicker: 14 | def __init__(self, interval: int = 1, callback: CallbackData = timepicker_callback, **kwargs): 15 | self.cancel = None 16 | if not 3600 % interval: 17 | ValueError('FullTimepicker: 3600 must be a multiple of the interval and integer value.') 18 | self.interval = interval 19 | self.callback = callback 20 | self.is_atomic = kwargs.get('is_atomic') is True 21 | self.label_up = kwargs.get('label_up', _default['up']) 22 | self.label_down = kwargs.get('label_down', _default['down']) 23 | self.label_empty = kwargs.get('label_empty', _default['empty']) 24 | self.label_select = kwargs.get('label_select', _default['select']) 25 | self.label_cancel = kwargs.get('label_cancel', _default['cancel']) 26 | self.minute_format = kwargs.get('minute_format', _default['minute_format']) 27 | self.second_format = kwargs.get('second_format', _default['second_format']) 28 | self.timezone = kwargs.get('timezone', 0) 29 | 30 | async def _atomic_picker(self, inline_kb: InlineKeyboardMarkup, _time: datetime): 31 | _tds = ( 32 | timedelta(minutes=1 if _time.minute + 1 < 60 else - (60 - 1)), 33 | timedelta(minutes=-1 if _time.minute - 1 >= 0 else 60 + -1), 34 | timedelta(seconds=self.interval if _time.second + self.interval < 60 else - (60 - self.interval)), 35 | timedelta(seconds=-self.interval if _time.second - self.interval >= 0 else 60 + -self.interval), 36 | ) 37 | 38 | for _td_i in range(len(_tds)): 39 | _td = _tds[_td_i] 40 | t = _time + _td 41 | inline_kb.insert(InlineKeyboardButton( 42 | self.label_up if _td_i % 2 == 0 else self.label_down, 43 | callback_data=self.callback.new( 44 | "CHANGE", t.strftime("%M"), t.strftime("%S")), 45 | )) 46 | inline_kb.row() 47 | 48 | async def _not_atomic_picker(self, inline_kb: InlineKeyboardMarkup, _time: datetime): 49 | _tds = ( 50 | timedelta(minutes=1), 51 | timedelta(seconds=self.interval), 52 | ) 53 | for _td in _tds: 54 | t = _time + _td 55 | inline_kb.insert(InlineKeyboardButton( 56 | self.label_up, 57 | callback_data=self.callback.new( 58 | "CHANGE", t.strftime("%M"), t.strftime("%S")), 59 | )) 60 | t = _time - _td 61 | inline_kb.insert(InlineKeyboardButton( 62 | self.label_down, 63 | callback_data=self.callback.new( 64 | "CHANGE", t.strftime("%M"), t.strftime("%S")), 65 | )) 66 | inline_kb.row() 67 | 68 | async def start_picker( 69 | self, 70 | minute: int = 00, 71 | second: int = 00, 72 | cancel: typing.Any = None, 73 | ) -> InlineKeyboardMarkup: 74 | """ 75 | Creates an inline keyboard with the provided minute and second 76 | :param int minute: Minute to use in the picker, if None the 00 is used. (0...59) 77 | :param int second: Second to use in the picker, if None the 00 is used. (0...59) 78 | :param int cancel: Action to perform after canceling, if None the keyboard is closed. 79 | :return: Returns InlineKeyboardMarkup object with the timepicker keyboard. 80 | """ 81 | self.cancel = cancel 82 | inline_kb = InlineKeyboardMarkup(row_width=6) 83 | ignore_callback = self.callback.new("IGNORE", minute, second) 84 | _time = datetime(1970, 1, 1, 0, minute, second) 85 | if self.is_atomic: 86 | await self._atomic_picker(inline_kb, _time) 87 | else: 88 | await self._not_atomic_picker(inline_kb, _time) 89 | inline_kb.insert(InlineKeyboardButton( 90 | self.minute_format.format(minute % 60), 91 | callback_data=self.callback.new('CHOOSE_M', minute, second), 92 | )) 93 | inline_kb.insert(InlineKeyboardButton( 94 | self.second_format.format(second % 60), 95 | callback_data=self.callback.new('CHOOSE_S', minute, second), 96 | )) 97 | inline_kb.row() 98 | inline_kb.insert(InlineKeyboardButton( 99 | self.label_cancel, 100 | callback_data=self.callback.new('CANCEL', -1, -1), 101 | )) 102 | inline_kb.insert(InlineKeyboardButton( 103 | self.label_select, 104 | callback_data=self.callback.new('SELECTED', minute, second), 105 | )) 106 | return inline_kb 107 | 108 | async def _process_selection_all_int(self, query: CallbackQuery, act: str, seconds: int, minutes: int): 109 | if act == "CHANGE": 110 | await query.message.edit_reply_markup( 111 | await self.start_picker(minutes, seconds, cancel=self.cancel) 112 | ) 113 | return result.Result( 114 | result.Status.CHANGED, 115 | minutes=minutes, seconds=seconds, 116 | ) 117 | elif act == "SELECTED": 118 | return result.Result( 119 | result.Status.SELECTED, 120 | minutes=minutes, seconds=seconds, 121 | ) 122 | elif act == "CHOOSE_M" or act == "GRP_MIN_MENU": 123 | kb = await minute.TimePicker(self.interval // 60 if self.interval >= 60 else 1, self.callback)\ 124 | .change_default_action( 125 | **adapter.minute.function_replace_default( 126 | self.callback, seconds, minutes) 127 | ).start_picker(minutes) 128 | await query.message.edit_reply_markup(kb) 129 | return result.Result( 130 | result.Status.CHANGE_MINUTE, 131 | minutes=minutes, seconds=seconds, 132 | ) 133 | elif act == "CHOOSE_S" or act == "GRP_SEC_MENU": 134 | kb = await second.TimePicker(self.interval, None)\ 135 | .change_default_action( 136 | **adapter.second.function_replace_default( 137 | self.callback, seconds, minutes) 138 | ).start_picker(seconds) 139 | await query.message.edit_reply_markup(kb) 140 | return result.Result( 141 | result.Status.CHANGE_SECOND, 142 | minutes=minutes, seconds=seconds, 143 | ) 144 | return result.Result() 145 | 146 | async def _process_selection_not_int(self, query: CallbackQuery, act: str, seconds: str, minutes: str): 147 | # processing empty buttons, answering with no action 148 | if act == "GRP_MIN": 149 | m = minutes.split('-')[0] 150 | m = int(m) if m.isdigit() else 0 151 | await minute.TimePicker( 152 | self.interval // 60 if self.interval >= 60 else 1, self.callback 153 | ).change_default_action( 154 | **adapter.minute.function_replace_default( 155 | self.callback, seconds, m) 156 | ).process_group_selection(query, minutes) 157 | return result.Result( 158 | result.Status.SELECT_GROUP_MINUTE, 159 | seconds=int(seconds) if seconds.isdigit() else 0, 160 | ) 161 | elif act == "GRP_SEC": 162 | s = seconds.split('-')[0] 163 | s = int(s) if s.isdigit() else 0 164 | await second.TimePicker(self.interval, self.callback).change_default_action( 165 | **adapter.second.function_replace_default( 166 | self.callback, s, minutes) 167 | ).process_group_selection(query, seconds) 168 | return result.Result( 169 | result.Status.SELECT_GROUP_SECOND, 170 | minutes=int(minutes) if minutes.isdigit() else 0, 171 | ) 172 | return result.Result() 173 | 174 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> result.Result: 175 | """ 176 | Process the callback_query. This method generates a new time picker if forward or 177 | backward is pressed. This method should be called inside a CallbackQueryHandler. 178 | :param query: callback_query, as provided by the CallbackQueryHandler 179 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 180 | :return: Returns an aiogram_timepicker.result.Result object. 181 | """ 182 | if data['act'] == "CANCEL": 183 | if self.cancel: 184 | await self.cancel(query, data) 185 | else: 186 | await query.message.delete() 187 | return result.Result(result.Status.CANCELED) 188 | elif data['act'] == "IGNORE": 189 | await query.answer(cache_time=60) 190 | return result.Result() 191 | if not (data['minute'].isdigit() and data['second'].isdigit()): 192 | return await self._process_selection_not_int( 193 | query, data['act'], data['second'], data['minute'] 194 | ) 195 | return await self._process_selection_all_int( 196 | query, data['act'], int(data['second']), int(data['minute']) 197 | ) 198 | -------------------------------------------------------------------------------- /bot_example.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | 4 | from aiogram import Bot, Dispatcher 5 | from aiogram.types import Message, CallbackQuery, ReplyKeyboardMarkup 6 | from aiogram.utils import executor 7 | from aiogram.dispatcher.filters import Text 8 | from aiogram_timepicker.panel import FullTimePicker, full_timep_callback, full_timep_default, \ 9 | HourTimePicker, hour_timep_callback, MinuteTimePicker, minute_timep_callback, \ 10 | SecondTimePicker, second_timep_callback, \ 11 | MinSecTimePicker, minsec_timep_callback, minsec_timep_default 12 | from aiogram_timepicker import result, carousel, clock 13 | 14 | 15 | # insert your telegram bot API key here 16 | API_TOKEN = '' or os.getenv('token') 17 | 18 | # Configure logging 19 | logging.basicConfig(level=logging.INFO) 20 | 21 | # Initialize bot and dispatcher 22 | bot = Bot(token=API_TOKEN) 23 | dp = Dispatcher(bot) 24 | 25 | start_kb = ReplyKeyboardMarkup(resize_keyboard=True, ) 26 | start_kb.row('Full TimePicker', 'Full Carousel TimePicker') 27 | start_kb.row('Hour TimePicker', 'Minute Timepicker', 'Second Timepicker') 28 | start_kb.row('Minute & Second') 29 | start_kb.row('Clock Hour 1', 'Clock Hour 2', 'Clock Minutes', 'Clock Minutes 2') 30 | 31 | 32 | # starting bot when user sends `/start` command, answering with inline timepicker 33 | @dp.message_handler(commands=['start']) 34 | async def cmd_start(message: Message): 35 | await message.reply('Pick a timepicker', reply_markup=start_kb) 36 | 37 | 38 | @dp.message_handler(Text(equals=['Full TimePicker'], ignore_case=True)) 39 | async def full_picker_handler(message: Message): 40 | await message.answer( 41 | "Please select a time: ", 42 | reply_markup=await FullTimePicker().start_picker() 43 | ) 44 | 45 | 46 | # full timepicker usage 47 | @dp.callback_query_handler(full_timep_callback.filter()) 48 | async def process_full_timepicker(callback_query: CallbackQuery, callback_data: dict): 49 | r = await FullTimePicker().process_selection(callback_query, callback_data) 50 | if r.selected: 51 | await callback_query.message.answer( 52 | f'You selected {r.time.strftime("%H:%M:%S")}', 53 | reply_markup=start_kb 54 | ) 55 | await callback_query.message.delete_reply_markup() 56 | 57 | 58 | @dp.message_handler(Text(equals=['Hour TimePicker'], ignore_case=True)) 59 | async def hour_picker_handler(message: Message): 60 | await message.answer( 61 | "Please select a hour: ", 62 | reply_markup=await HourTimePicker().start_picker() 63 | ) 64 | 65 | 66 | @dp.callback_query_handler(hour_timep_callback.filter()) 67 | async def process_hour_timepicker(callback_query: CallbackQuery, callback_data: dict): 68 | r = await HourTimePicker().process_selection(callback_query, callback_data) 69 | if r.selected: 70 | await callback_query.message.edit_text( 71 | f'You selected {r.hours}h.', 72 | ) 73 | 74 | 75 | @dp.message_handler(Text(equals=['Minute TimePicker'], ignore_case=True)) 76 | async def minute_picker_handler(message: Message): 77 | await message.answer( 78 | "Please select a minute: ", 79 | reply_markup=await MinuteTimePicker(5, group_inside_count=10).start_picker() 80 | ) 81 | 82 | 83 | @dp.callback_query_handler(minute_timep_callback.filter()) 84 | async def process_minute_timepicker(callback_query: CallbackQuery, callback_data: dict): 85 | r = await MinuteTimePicker(2, group_inside_count=10).process_selection(callback_query, callback_data) 86 | if r.selected: 87 | await callback_query.message.edit_text( 88 | f'You selected {r.minutes}m.', 89 | ) 90 | 91 | 92 | @dp.message_handler(Text(equals=['Second TimePicker'], ignore_case=True)) 93 | async def second_picker_handler(message: Message): 94 | await message.answer( 95 | "Please select a second: ", 96 | reply_markup=await SecondTimePicker(5, group_inside_count=10).start_picker() 97 | ) 98 | 99 | 100 | @dp.callback_query_handler(second_timep_callback.filter()) 101 | async def process_second_timepicker(callback_query: CallbackQuery, callback_data: dict): 102 | r = await SecondTimePicker(5, group_inside_count=10).process_selection(callback_query, callback_data) 103 | if r.selected: 104 | await callback_query.message.edit_text( 105 | f'You selected {r.seconds}s.', 106 | ) 107 | 108 | 109 | @dp.message_handler(Text(equals=['Minute & Second'], ignore_case=True)) 110 | async def second_picker_handler(message: Message): 111 | await message.answer( 112 | "Please select a time: ", 113 | reply_markup=await MinSecTimePicker(5).start_picker() 114 | ) 115 | 116 | 117 | @dp.callback_query_handler(minsec_timep_callback.filter()) 118 | async def process_second_timepicker(callback_query: CallbackQuery, callback_data: dict): 119 | r = await MinSecTimePicker(5).process_selection(callback_query, callback_data) 120 | if r.selected: 121 | await callback_query.message.edit_text( 122 | 'You selected {time}.'.format(time=r.time.strftime('%M:%S')), 123 | ) 124 | 125 | 126 | @dp.message_handler(Text(equals=['Full Carousel TimePicker'], ignore_case=True)) 127 | async def full2_picker_handler(message: Message): 128 | await message.answer( 129 | "Please select a time: ", 130 | reply_markup=await carousel.FullTimePicker().start_picker() 131 | ) 132 | 133 | 134 | # carousel full timepicker usage 135 | @dp.callback_query_handler(carousel.full_timep_callback.filter()) 136 | async def process_full2_timepicker(callback_query: CallbackQuery, callback_data: dict): 137 | r = await carousel.FullTimePicker().process_selection(callback_query, callback_data) 138 | if r.selected: 139 | await callback_query.message.answer( 140 | f'You selected {r.time.strftime("%H:%M:%S")}', 141 | reply_markup=start_kb 142 | ) 143 | await callback_query.message.delete_reply_markup() 144 | 145 | 146 | @dp.message_handler(Text(equals=['Clock Hour 1'], ignore_case=True)) 147 | async def clock_hour_1_picker_handler(message: Message): 148 | await message.answer( 149 | "Please select a time: ", 150 | reply_markup=await clock.single.c24.TimePicker(select_button_needed=True).start_picker(6) 151 | ) 152 | 153 | 154 | # clock hour 1st timepicker usage 155 | @dp.callback_query_handler(clock.single.c24.timepicker_callback.filter()) 156 | async def process_clock_hour_1_timepicker(callback_query: CallbackQuery, callback_data: dict): 157 | r = await clock.single.c24.TimePicker( 158 | select_button_needed=True).process_selection(callback_query, callback_data) 159 | if r.selected: 160 | await callback_query.message.answer( 161 | f'You selected {r.time.strftime("%H:%M:%S")}', 162 | reply_markup=start_kb 163 | ) 164 | await callback_query.message.delete_reply_markup() 165 | elif r.status == result.Status.CANCELED: 166 | await callback_query.message.delete() 167 | 168 | 169 | @dp.message_handler(Text(equals=['Clock Hour 2'], ignore_case=True)) 170 | async def clock_hour_2_picker_handler(message: Message): 171 | await message.answer( 172 | "Please select a time: ", 173 | reply_markup=await clock.single.c24_ts3.TimePicker(select_button_needed=True).start_picker(6) 174 | ) 175 | 176 | 177 | # clock hour 2nd timepicker usage 178 | @dp.callback_query_handler(clock.single.c24_ts3.timepicker_callback.filter()) 179 | async def process_clock_hour_2_timepicker(callback_query: CallbackQuery, callback_data: dict): 180 | r = await clock.single.c24_ts3.TimePicker(select_button_needed=True).process_selection(callback_query, callback_data) 181 | if r.selected: 182 | await callback_query.message.answer( 183 | f'You selected {r.time.strftime("%H:%M:%S")}', 184 | reply_markup=start_kb 185 | ) 186 | await callback_query.message.delete_reply_markup() 187 | elif r.status == result.Status.CANCELED: 188 | await callback_query.message.delete() 189 | 190 | 191 | @dp.message_handler(Text(equals=['Clock Minutes'], ignore_case=True)) 192 | async def clock_minute_picker_handler(message: Message): 193 | await message.answer( 194 | "Please select a time: ", 195 | reply_markup=await clock.single.c60_ts5.TimePicker( 196 | select_button_needed=True, 197 | cancel_button_needed=True, 198 | ).start_picker() 199 | ) 200 | 201 | 202 | # clock minute timepicker usage 203 | @dp.callback_query_handler(clock.single.c60_ts5.timepicker_callback.filter()) 204 | async def process_clock_minute_timepicker(callback_query: CallbackQuery, callback_data: dict): 205 | r = await clock.single.c60_ts5.TimePicker( 206 | select_button_needed=True, 207 | cancel_button_needed=True, 208 | ).process_selection(callback_query, callback_data) 209 | if r.selected: 210 | await callback_query.message.answer( 211 | f'You selected {r.time.strftime("%H:%M:%S")}', 212 | reply_markup=start_kb 213 | ) 214 | await callback_query.message.delete_reply_markup() 215 | elif r.status == result.Status.CANCELED: 216 | await callback_query.message.delete() 217 | 218 | @dp.message_handler(Text(equals=['Clock Minutes 2'], ignore_case=True)) 219 | async def clock_minute_2_picker_handler(message: Message): 220 | await message.answer( 221 | "Please select a time: ", 222 | reply_markup=await clock.single.c60_ts3.TimePicker( 223 | select_button_needed=True, 224 | cancel_button_needed=True, 225 | ).start_picker() 226 | ) 227 | 228 | 229 | # clock minute 2nd timepicker usage 230 | @dp.callback_query_handler(clock.single.c60_ts3.timepicker_callback.filter()) 231 | async def process_clock_minute_2_timepicker(callback_query: CallbackQuery, callback_data: dict): 232 | r = await clock.single.c60_ts3.TimePicker( 233 | select_button_needed=True, 234 | cancel_button_needed=True, 235 | ).process_selection(callback_query, callback_data) 236 | if r.selected: 237 | await callback_query.message.answer( 238 | f'You selected {r.time.strftime("%H:%M:%S")}', 239 | reply_markup=start_kb 240 | ) 241 | await callback_query.message.delete_reply_markup() 242 | elif r.status == result.Status.CANCELED: 243 | await callback_query.message.delete() 244 | 245 | 246 | if __name__ == '__main__': 247 | full_timep_default( 248 | # default labels 249 | label_up='⇪', label_down='⇓', 250 | hour_format='{0:02}h', minute_format='{0:02}m', second_format='{0:02}s' 251 | ) 252 | minsec_timep_default( 253 | hour_format='{0:02}h', minute_format='{0:02}m', second_format='{0:02}s' 254 | ) 255 | carousel.full_timep_default( 256 | hour_current_format='{0:02}h', 257 | minute_current_format='{0:02}m', 258 | second_current_format='{0:02}s', 259 | ) 260 | clock.single.c24.utils.default( 261 | time_current_format='⦾', 262 | ) 263 | executor.start_polling(dp, skip_updates=True) 264 | -------------------------------------------------------------------------------- /aiogram_timepicker/panel/full/timepicker.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | import typing 3 | 4 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton 5 | from aiogram.utils.callback_data import CallbackData 6 | from aiogram.types import CallbackQuery 7 | 8 | from aiogram_timepicker import result 9 | from aiogram_timepicker.panel.single import minute, second, hour 10 | from . import _default, timepicker_callback, adapter 11 | 12 | 13 | class TimePicker: 14 | def __init__(self, interval: int = 1, callback: CallbackData = timepicker_callback, **kwargs): 15 | self.cancel = None 16 | if not 3600 % interval: 17 | ValueError('FullTimepicker: 3600 must be a multiple of the interval and integer value.') 18 | self.interval = interval 19 | self.callback = callback 20 | self.is_atomic = kwargs.get('is_atomic') is True 21 | self.label_up = kwargs.get('label_up', _default['up']) 22 | self.label_down = kwargs.get('label_down', _default['down']) 23 | self.label_empty = kwargs.get('label_empty', _default['empty']) 24 | self.label_select = kwargs.get('label_select', _default['select']) 25 | self.label_cancel = kwargs.get('label_cancel', _default['cancel']) 26 | self.hour_format = kwargs.get('hour_format', _default['hour_format']) 27 | self.minute_format = kwargs.get('minute_format', _default['minute_format']) 28 | self.second_format = kwargs.get('second_format', _default['second_format']) 29 | self.timezone = kwargs.get('timezone', 0) 30 | 31 | async def _atomic_picker(self, inline_kb: InlineKeyboardMarkup, _time: datetime): 32 | _tds = ( 33 | timedelta(hours=1), 34 | timedelta(hours=-1), 35 | timedelta(minutes=1 if _time.minute + 1 < 60 else - (60 - 1)), 36 | timedelta(minutes=-1 if _time.minute - 1 >= 0 else 60 + -1), 37 | timedelta(seconds=self.interval if _time.second + self.interval < 60 else - (60 - self.interval)), 38 | timedelta(seconds=-self.interval if _time.second - self.interval >= 0 else 60 + -self.interval), 39 | ) 40 | 41 | for _td_i in range(len(_tds)): 42 | _td = _tds[_td_i] 43 | t = _time + _td 44 | inline_kb.insert(InlineKeyboardButton( 45 | self.label_up if _td_i % 2 == 0 else self.label_down, 46 | callback_data=self.callback.new( 47 | "CHANGE", t.strftime('%H'), t.strftime("%M"), t.strftime("%S")), 48 | )) 49 | inline_kb.row() 50 | 51 | async def _not_atomic_picker(self, inline_kb: InlineKeyboardMarkup, _time: datetime): 52 | _tds = { 53 | timedelta(hours=1), 54 | timedelta(minutes=1), 55 | timedelta(seconds=self.interval), 56 | } 57 | for _td in _tds: 58 | t = _time + _td 59 | inline_kb.insert(InlineKeyboardButton( 60 | self.label_up, 61 | callback_data=self.callback.new( 62 | "CHANGE", t.strftime('%H'), t.strftime("%M"), t.strftime("%S")), 63 | )) 64 | t = _time - _td 65 | inline_kb.insert(InlineKeyboardButton( 66 | self.label_down, 67 | callback_data=self.callback.new( 68 | "CHANGE", t.strftime('%H'), t.strftime("%M"), t.strftime("%S")), 69 | )) 70 | inline_kb.row() 71 | 72 | async def start_picker( 73 | self, 74 | hour: int = 12, 75 | minute: int = 00, 76 | second: int = 00, 77 | cancel: typing.Any = None, 78 | ) -> InlineKeyboardMarkup: 79 | """ 80 | Creates an inline keyboard with the provided hour, minute and second 81 | :param int hour: Hour to use in the picker, if None the 12 is used. (0...23) 82 | :param int minute: Minute to use in the picker, if None the 00 is used. (0...59) 83 | :param int second: Second to use in the picker, if None the 00 is used. (0...59) 84 | :param int cancel: Action to perform after canceling, if None the keyboard is closed. 85 | :return: Returns InlineKeyboardMarkup object with the timepicker keyboard. 86 | """ 87 | self.cancel = cancel 88 | inline_kb = InlineKeyboardMarkup(row_width=6) 89 | ignore_callback = self.callback.new("IGNORE", hour, minute, second) 90 | _time = datetime(1970, 1, 1, hour, minute, second) 91 | if self.is_atomic: 92 | await self._atomic_picker(inline_kb, _time) 93 | else: 94 | await self._not_atomic_picker(inline_kb, _time) 95 | inline_kb.insert(InlineKeyboardButton( 96 | self.hour_format.format(hour % 24), 97 | callback_data=self.callback.new('CHOOSE_H', hour, minute, second), 98 | )) 99 | inline_kb.insert(InlineKeyboardButton( 100 | self.minute_format.format(minute % 60), 101 | callback_data=self.callback.new('CHOOSE_M', hour, minute, second), 102 | )) 103 | inline_kb.insert(InlineKeyboardButton( 104 | self.second_format.format(second % 60), 105 | callback_data=self.callback.new('CHOOSE_S', hour, minute, second), 106 | )) 107 | inline_kb.row() 108 | inline_kb.insert(InlineKeyboardButton( 109 | self.label_cancel, 110 | callback_data=self.callback.new('CANCEL', -1, -1, -1), 111 | )) 112 | inline_kb.insert(InlineKeyboardButton( 113 | self.label_select, 114 | callback_data=self.callback.new('SELECTED', hour, minute, second), 115 | )) 116 | return inline_kb 117 | 118 | async def _process_selection_all_int(self, query: CallbackQuery, act: str, seconds: int, minutes: int, hours: int): 119 | if act == "CHANGE": 120 | await query.message.edit_reply_markup( 121 | await self.start_picker(hours, minutes, seconds, cancel=self.cancel) 122 | ) 123 | return result.Result( 124 | result.Status.CHANGED, 125 | hours=hours, minutes=minutes, seconds=seconds, 126 | ) 127 | elif act == "SELECTED": 128 | return result.Result( 129 | result.Status.SELECTED, 130 | hours=hours, minutes=minutes, seconds=seconds, 131 | ) 132 | elif act == "CHOOSE_H": 133 | kb = await hour.TimePicker( 134 | 1, None, 135 | str_callback=self.callback.new('CHANGE', '{hour}', minutes, seconds))\ 136 | .start_picker(hours) 137 | await query.message.edit_reply_markup(kb) 138 | return result.Result( 139 | result.Status.CHANGE_HOUR, 140 | hours=hours, minutes=minutes, seconds=seconds, 141 | ) 142 | elif act == "CHOOSE_M" or act == "GRP_MIN_MENU": 143 | kb = await minute.TimePicker(self.interval // 60 if self.interval >= 60 else 1, self.callback)\ 144 | .change_default_action( 145 | **adapter.minute.function_replace_default( 146 | self.callback, seconds, minutes, hours) 147 | ).start_picker(minutes) 148 | await query.message.edit_reply_markup(kb) 149 | return result.Result( 150 | result.Status.CHANGE_MINUTE, 151 | hours=hours, minutes=minutes, seconds=seconds, 152 | ) 153 | elif act == "CHOOSE_S" or act == "GRP_SEC_MENU": 154 | kb = await second.TimePicker(self.interval, None)\ 155 | .change_default_action( 156 | **adapter.second.function_replace_default( 157 | self.callback, seconds, minutes, hours) 158 | ).start_picker(seconds) 159 | await query.message.edit_reply_markup(kb) 160 | return result.Result( 161 | result.Status.CHANGE_SECOND, 162 | hours=hours, minutes=minutes, seconds=seconds, 163 | ) 164 | return result.Result() 165 | 166 | async def _process_selection_not_int(self, query: CallbackQuery, act: str, seconds: str, minutes: str, hours: str): 167 | # processing empty buttons, answering with no action 168 | if act == "GRP_MIN": 169 | m = minutes.split('-')[0] 170 | m = int(m) if m.isdigit() else 0 171 | await minute.TimePicker( 172 | self.interval // 60 if self.interval >= 60 else 1, self.callback 173 | ).change_default_action( 174 | **adapter.minute.function_replace_default( 175 | self.callback, seconds, m, hours) 176 | ).process_group_selection(query, minutes) 177 | return result.Result( 178 | result.Status.SELECT_GROUP_MINUTE, 179 | **{ 180 | hours: int(hours) if hours.isdigit() else 0, 181 | seconds: int(seconds) if seconds.isdigit() else 0, 182 | } 183 | ) 184 | elif act == "GRP_SEC": 185 | s = seconds.split('-')[0] 186 | s = int(s) if s.isdigit() else 0 187 | await second.TimePicker(self.interval, self.callback).change_default_action( 188 | **adapter.second.function_replace_default( 189 | self.callback, s, minutes, hours) 190 | ).process_group_selection(query, seconds) 191 | return result.Result( 192 | result.Status.SELECT_GROUP_SECOND, 193 | **{ 194 | hours: int(hours) if hours.isdigit() else 0, 195 | minutes: int(minutes) if minutes.isdigit() else 0, 196 | } 197 | ) 198 | return result.Result() 199 | 200 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> result.Result: 201 | """ 202 | Process the callback_query. This method generates a new time picker if forward or 203 | backward is pressed. This method should be called inside a CallbackQueryHandler. 204 | :param query: callback_query, as provided by the CallbackQueryHandler 205 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 206 | :return: Returns an aiogram_timepicker.result.Result object. 207 | """ 208 | if data['act'] == "CANCEL": 209 | if self.cancel: 210 | await self.cancel(query, data) 211 | else: 212 | await query.message.delete() 213 | return result.Result(result.Status.CANCELED) 214 | elif data['act'] == "IGNORE": 215 | await query.answer(cache_time=60) 216 | return result.Result() 217 | if not (data['hour'].isdigit() and data['minute'].isdigit() and data['second'].isdigit()): 218 | return await self._process_selection_not_int( 219 | query, data['act'], data['second'], data['minute'], data['hour'] 220 | ) 221 | return await self._process_selection_all_int( 222 | query, data['act'], int(data['second']), int(data['minute']), int(data['hour']) 223 | ) 224 | -------------------------------------------------------------------------------- /aiogram_timepicker/carousel/full/timepicker.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | import typing 3 | 4 | from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton 5 | from aiogram.utils.callback_data import CallbackData 6 | from aiogram.types import CallbackQuery 7 | 8 | from aiogram_timepicker import result 9 | from aiogram_timepicker.panel.single import minute, second, hour 10 | from . import _default, timepicker_callback 11 | 12 | 13 | class TimePicker: 14 | def __init__(self, interval: int = 1, callback: CallbackData = timepicker_callback, **kwargs): 15 | self.cancel = None 16 | if not 3600 % interval: 17 | ValueError('FullTimepicker: 3600 must be a multiple of the interval and integer value.') 18 | self.interval = interval 19 | self.callback = callback 20 | self.is_atomic = kwargs.get('is_atomic') is True 21 | self.label_up = kwargs.get('label_up', _default['up']) 22 | self.label_down = kwargs.get('label_down', _default['down']) 23 | self.label_empty = kwargs.get('label_empty', _default['empty']) 24 | self.label_select = kwargs.get('label_select', _default['select']) 25 | self.label_cancel = kwargs.get('label_cancel', _default['cancel']) 26 | self.hour_format = kwargs.get('hour_format', _default['hour_format']) 27 | self.minute_format = kwargs.get('minute_format', _default['minute_format']) 28 | self.second_format = kwargs.get('second_format', _default['second_format']) 29 | self.hour_current_format = kwargs.get('hour_current_format', _default['hour_current_format']) 30 | self.minute_current_format = kwargs.get('minute_current_format', _default['minute_current_format']) 31 | self.second_current_format = kwargs.get('second_current_format', _default['second_current_format']) 32 | self.count_top = kwargs.get('count_top', 2) 33 | self.count_bottom = kwargs.get('count_bottom', 2) 34 | self.timezone = kwargs.get('timezone', 0) 35 | 36 | async def _atomic_picker(self, inline_kb: InlineKeyboardMarkup, _time: datetime): 37 | _tds = ( 38 | timedelta(hours=1), 39 | timedelta(hours=-1), 40 | timedelta(minutes=1 if _time.minute + 1 < 60 else - (60 - 1)), 41 | timedelta(minutes=-1 if _time.minute - 1 >= 0 else 60 + -1), 42 | timedelta(seconds=self.interval if _time.second + self.interval < 60 else - (60 - self.interval)), 43 | timedelta(seconds=-self.interval if _time.second - self.interval >= 0 else 60 + -self.interval), 44 | ) 45 | 46 | for _td_i in range(len(_tds)): 47 | _td = _tds[_td_i] 48 | t = _time + _td 49 | inline_kb.insert(InlineKeyboardButton( 50 | self.label_up if _td_i % 2 == 0 else self.label_down, 51 | callback_data=self.callback.new( 52 | "CHANGE", t.strftime('%H'), t.strftime("%M"), t.strftime("%S")), 53 | )) 54 | inline_kb.row() 55 | 56 | async def _not_atomic_picker(self, inline_kb: InlineKeyboardMarkup, _time: datetime): 57 | _tds = { 58 | timedelta(hours=1), 59 | timedelta(minutes=1), 60 | timedelta(seconds=self.interval), 61 | } 62 | for _td in _tds: 63 | t = _time + _td 64 | inline_kb.insert(InlineKeyboardButton( 65 | self.label_up, 66 | callback_data=self.callback.new( 67 | "CHANGE", t.strftime('%H'), t.strftime("%M"), t.strftime("%S")), 68 | )) 69 | t = _time - _td 70 | inline_kb.insert(InlineKeyboardButton( 71 | self.label_down, 72 | callback_data=self.callback.new( 73 | "CHANGE", t.strftime('%H'), t.strftime("%M"), t.strftime("%S")), 74 | )) 75 | inline_kb.row() 76 | 77 | async def start_picker( 78 | self, 79 | hour: int = 12, 80 | minute: int = 00, 81 | second: int = 00, 82 | cancel: typing.Any = None, 83 | ) -> InlineKeyboardMarkup: 84 | """ 85 | Creates an inline keyboard with the provided hour, minute and second 86 | :param int hour: Hour to use in the picker, if None the 12 is used. (0...23) 87 | :param int minute: Minute to use in the picker, if None the 00 is used. (0...59) 88 | :param int second: Second to use in the picker, if None the 00 is used. (0...59) 89 | :param int cancel: Action to perform after canceling, if None the keyboard is closed. 90 | :return: Returns InlineKeyboardMarkup object with the timepicker keyboard. 91 | """ 92 | self.cancel = cancel 93 | inline_kb = InlineKeyboardMarkup(row_width=7) 94 | _time = datetime(1971, 1, 1, hour, minute, second) 95 | _tds = ( 96 | timedelta(hours=1), 97 | timedelta(minutes=1 if _time.minute + 1 < 60 else - (60 - 1)), 98 | timedelta(seconds=self.interval if _time.second + self.interval < 60 else - (60 - self.interval)), 99 | ) 100 | for i in range(self.count_top, 0, -1): 101 | inline_kb.insert(InlineKeyboardButton( 102 | self.label_empty, 103 | callback_data=self.callback.new('IGNORE', hour, minute, second) 104 | )) 105 | t = _time + _tds[0] * i 106 | inline_kb.insert(InlineKeyboardButton( 107 | self.hour_format.format(t.hour), 108 | callback_data=self.callback.new('CHANGE', t.hour, minute, second) 109 | )) 110 | inline_kb.insert(InlineKeyboardButton( 111 | self.label_empty, 112 | callback_data=self.callback.new('IGNORE', hour, minute, second) 113 | )) 114 | t = _time + _tds[1] * i 115 | inline_kb.insert(InlineKeyboardButton( 116 | self.minute_format.format(t.minute), 117 | callback_data=self.callback.new('CHANGE', hour, t.minute, second) 118 | )) 119 | inline_kb.insert(InlineKeyboardButton( 120 | self.label_empty, 121 | callback_data=self.callback.new('IGNORE', hour, minute, second) 122 | )) 123 | t = _time + _tds[2] * i 124 | inline_kb.insert(InlineKeyboardButton( 125 | self.minute_format.format(t.second), 126 | callback_data=self.callback.new('CHANGE', hour, minute, t.second) 127 | )) 128 | inline_kb.insert(InlineKeyboardButton( 129 | self.label_empty, 130 | callback_data=self.callback.new('IGNORE', hour, minute, second) 131 | )) 132 | inline_kb.row() 133 | 134 | inline_kb.insert(InlineKeyboardButton( 135 | self.hour_current_format.format(hour), 136 | callback_data=self.callback.new('IGNORE', hour, minute, second) 137 | )) 138 | inline_kb.insert(InlineKeyboardButton( 139 | self.minute_current_format.format(minute), 140 | callback_data=self.callback.new('IGNORE', hour, minute, second) 141 | )) 142 | inline_kb.insert(InlineKeyboardButton( 143 | self.second_current_format.format(second), 144 | callback_data=self.callback.new('IGNORE', hour, minute, second) 145 | )) 146 | inline_kb.row() 147 | 148 | _tds = ( 149 | timedelta(hours=-1), 150 | timedelta(minutes=-1 if _time.minute - 1 >= 0 else 60 + -1), 151 | timedelta(seconds=-self.interval if _time.second - self.interval >= 0 else 60 + -self.interval), 152 | ) 153 | for i in range(1, self.count_bottom + 1): 154 | inline_kb.insert(InlineKeyboardButton( 155 | self.label_empty, 156 | callback_data=self.callback.new('IGNORE', hour, minute, second) 157 | )) 158 | t = _time + _tds[0] * i 159 | inline_kb.insert(InlineKeyboardButton( 160 | self.hour_format.format(t.hour), 161 | callback_data=self.callback.new('CHANGE', t.hour, minute, second) 162 | )) 163 | inline_kb.insert(InlineKeyboardButton( 164 | self.label_empty, 165 | callback_data=self.callback.new('IGNORE', hour, minute, second) 166 | )) 167 | t = _time + _tds[1] * i 168 | inline_kb.insert(InlineKeyboardButton( 169 | self.minute_format.format(t.minute), 170 | callback_data=self.callback.new('CHANGE', hour, t.minute, second) 171 | )) 172 | inline_kb.insert(InlineKeyboardButton( 173 | self.label_empty, 174 | callback_data=self.callback.new('IGNORE', hour, minute, second) 175 | )) 176 | t = _time + _tds[2] * i 177 | inline_kb.insert(InlineKeyboardButton( 178 | self.second_format.format(t.second), 179 | callback_data=self.callback.new('CHANGE', hour, minute, t.second) 180 | )) 181 | inline_kb.insert(InlineKeyboardButton( 182 | self.label_empty, 183 | callback_data=self.callback.new('IGNORE', hour, minute, second) 184 | )) 185 | inline_kb.row() 186 | inline_kb.insert(InlineKeyboardButton( 187 | self.label_cancel, 188 | callback_data=self.callback.new('CANCEL', -1, -1, -1), 189 | )) 190 | inline_kb.insert(InlineKeyboardButton( 191 | self.label_select, 192 | callback_data=self.callback.new('SELECTED', hour, minute, second), 193 | )) 194 | return inline_kb 195 | 196 | async def _process_selection_all_int(self, query: CallbackQuery, act: str, seconds: int, minutes: int, hours: int): 197 | if act == "CHANGE": 198 | await query.message.edit_reply_markup( 199 | await self.start_picker(hours, minutes, seconds) 200 | ) 201 | return result.Result( 202 | result.Status.CHANGED, 203 | hours=hours, minutes=minutes, seconds=seconds, 204 | ) 205 | elif act == "SELECTED": 206 | return result.Result( 207 | result.Status.SELECTED, 208 | hours=hours, minutes=minutes, seconds=seconds, 209 | ) 210 | # elif act == "CHOOSE_H": 211 | # kb = await hour.TimePicker( 212 | # 1, None, 213 | # str_callback=self.callback.new('CHANGE', '{hour}', minutes, seconds))\ 214 | # .start_picker(hours) 215 | # await query.message.edit_reply_markup(kb) 216 | # return result.Result( 217 | # result.Status.CHANGE_HOUR, 218 | # hours=hours, minutes=minutes, seconds=seconds, 219 | # ) 220 | # elif act == "CHOOSE_M" or act == "GRP_MIN_MENU": 221 | # kb = await minute.TimePicker(self.interval // 60 if self.interval >= 60 else 1, self.callback)\ 222 | # .change_default_action( 223 | # **adapter.minute.function_replace_default( 224 | # self.callback, seconds, minutes, hours) 225 | # ).start_picker(minutes) 226 | # await query.message.edit_reply_markup(kb) 227 | # return result.Result( 228 | # result.Status.CHANGE_MINUTE, 229 | # hours=hours, minutes=minutes, seconds=seconds, 230 | # ) 231 | # elif act == "CHOOSE_S" or act == "GRP_SEC_MENU": 232 | # kb = await second.TimePicker(self.interval, None)\ 233 | # .change_default_action( 234 | # **adapter.second.function_replace_default( 235 | # self.callback, seconds, minutes, hours) 236 | # ).start_picker(seconds) 237 | # await query.message.edit_reply_markup(kb) 238 | # return result.Result( 239 | # result.Status.CHANGE_SECOND, 240 | # hours=hours, minutes=minutes, seconds=seconds, 241 | # ) 242 | return result.Result() 243 | 244 | async def _process_selection_not_int(self, query: CallbackQuery, act: str, seconds: str, minutes: str, hours: str): 245 | # processing empty buttons, answering with no action 246 | # if act == "GRP_MIN": 247 | # m = minutes.split('-')[0] 248 | # m = int(m) if m.isdigit() else 0 249 | # await minute.TimePicker( 250 | # self.interval // 60 if self.interval >= 60 else 1, self.callback 251 | # ).change_default_action( 252 | # **adapter.minute.function_replace_default( 253 | # self.callback, seconds, m, hours) 254 | # ).process_group_selection(query, minutes) 255 | # return result.Result( 256 | # result.Status.SELECT_GROUP_MINUTE, 257 | # **{ 258 | # hours: int(hours) if hours.isdigit() else 0, 259 | # seconds: int(seconds) if seconds.isdigit() else 0, 260 | # } 261 | # ) 262 | # elif act == "GRP_SEC": 263 | # s = seconds.split('-')[0] 264 | # s = int(s) if s.isdigit() else 0 265 | # await second.TimePicker(self.interval, self.callback).change_default_action( 266 | # **adapter.second.function_replace_default( 267 | # self.callback, s, minutes, hours) 268 | # ).process_group_selection(query, seconds) 269 | # return result.Result( 270 | # result.Status.SELECT_GROUP_SECOND, 271 | # **{ 272 | # hours: int(hours) if hours.isdigit() else 0, 273 | # minutes: int(minutes) if minutes.isdigit() else 0, 274 | # } 275 | # ) 276 | return result.Result() 277 | 278 | async def process_selection(self, query: CallbackQuery, data: CallbackData) -> result.Result: 279 | """ 280 | Process the callback_query. This method generates a new time picker if forward or 281 | backward is pressed. This method should be called inside a CallbackQueryHandler. 282 | :param query: callback_query, as provided by the CallbackQueryHandler 283 | :param data: callback_data, dictionary, set by `self.callback` (default timepicker_callback) 284 | :return: Returns an aiogram_timepicker.result.Result object. 285 | """ 286 | if data['act'] == "CANCEL": 287 | if self.cancel: 288 | await self.cancel(query, data) 289 | else: 290 | await query.message.delete() 291 | return result.Result(result.Status.CANCELED) 292 | elif data['act'] == "IGNORE": 293 | await query.answer(cache_time=60) 294 | return result.Result() 295 | if not (data['hours'].isdigit() and data['minutes'].isdigit() and data['seconds'].isdigit()): 296 | return await self._process_selection_not_int( 297 | query, data['act'], data['seconds'], data['minutes'], data['hours'] 298 | ) 299 | return await self._process_selection_all_int( 300 | query, data['act'], int(data['seconds']), int(data['minutes']), int(data['hours']) 301 | ) 302 | --------------------------------------------------------------------------------