mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-11 18:01:04 +00:00
Merge branch 'dev-2.x' into dev-2.x
This commit is contained in:
commit
23ceb1445e
47 changed files with 952 additions and 848 deletions
|
|
@ -97,15 +97,13 @@ class I18nMiddleware(BaseMiddleware):
|
||||||
if locale not in self.locales:
|
if locale not in self.locales:
|
||||||
if n is 1:
|
if n is 1:
|
||||||
return singular
|
return singular
|
||||||
else:
|
return plural
|
||||||
return plural
|
|
||||||
|
|
||||||
translator = self.locales[locale]
|
translator = self.locales[locale]
|
||||||
|
|
||||||
if plural is None:
|
if plural is None:
|
||||||
return translator.gettext(singular)
|
return translator.gettext(singular)
|
||||||
else:
|
return translator.ngettext(singular, plural, n)
|
||||||
return translator.ngettext(singular, plural, n)
|
|
||||||
|
|
||||||
def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=True) -> LazyProxy:
|
def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=True) -> LazyProxy:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -85,44 +85,64 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
|
||||||
|
|
||||||
filters_factory.bind(StateFilter, exclude_event_handlers=[
|
filters_factory.bind(StateFilter, exclude_event_handlers=[
|
||||||
self.errors_handlers,
|
self.errors_handlers,
|
||||||
self.poll_handlers
|
self.poll_handlers,
|
||||||
])
|
])
|
||||||
filters_factory.bind(ContentTypeFilter, event_handlers=[
|
filters_factory.bind(ContentTypeFilter, event_handlers=[
|
||||||
self.message_handlers, self.edited_message_handlers,
|
self.message_handlers,
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
self.edited_message_handlers,
|
||||||
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
]),
|
]),
|
||||||
filters_factory.bind(Command, event_handlers=[
|
filters_factory.bind(Command, event_handlers=[
|
||||||
self.message_handlers, self.edited_message_handlers
|
self.message_handlers,
|
||||||
|
self.edited_message_handlers
|
||||||
])
|
])
|
||||||
filters_factory.bind(Text, event_handlers=[
|
filters_factory.bind(Text, event_handlers=[
|
||||||
self.message_handlers, self.edited_message_handlers,
|
self.message_handlers,
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
self.edited_message_handlers,
|
||||||
self.callback_query_handlers, self.poll_handlers, self.inline_query_handlers
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
|
self.callback_query_handlers,
|
||||||
|
self.poll_handlers,
|
||||||
|
self.inline_query_handlers,
|
||||||
])
|
])
|
||||||
filters_factory.bind(HashTag, event_handlers=[
|
filters_factory.bind(HashTag, event_handlers=[
|
||||||
self.message_handlers, self.edited_message_handlers,
|
self.message_handlers,
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers
|
self.edited_message_handlers,
|
||||||
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
])
|
])
|
||||||
filters_factory.bind(Regexp, event_handlers=[
|
filters_factory.bind(Regexp, event_handlers=[
|
||||||
self.message_handlers, self.edited_message_handlers,
|
self.message_handlers,
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
self.edited_message_handlers,
|
||||||
self.callback_query_handlers, self.poll_handlers, self.inline_query_handlers
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
|
self.callback_query_handlers,
|
||||||
|
self.poll_handlers,
|
||||||
|
self.inline_query_handlers,
|
||||||
])
|
])
|
||||||
filters_factory.bind(RegexpCommandsFilter, event_handlers=[
|
filters_factory.bind(RegexpCommandsFilter, event_handlers=[
|
||||||
self.message_handlers, self.edited_message_handlers
|
self.message_handlers,
|
||||||
|
self.edited_message_handlers,
|
||||||
])
|
])
|
||||||
filters_factory.bind(ExceptionsFilter, event_handlers=[
|
filters_factory.bind(ExceptionsFilter, event_handlers=[
|
||||||
self.errors_handlers
|
self.errors_handlers,
|
||||||
])
|
|
||||||
filters_factory.bind(IDFilter, event_handlers=[
|
|
||||||
self.message_handlers, self.edited_message_handlers,
|
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
|
||||||
self.callback_query_handlers, self.inline_query_handlers
|
|
||||||
])
|
])
|
||||||
filters_factory.bind(AdminFilter, event_handlers=[
|
filters_factory.bind(AdminFilter, event_handlers=[
|
||||||
self.message_handlers, self.edited_message_handlers,
|
self.message_handlers,
|
||||||
self.channel_post_handlers, self.edited_channel_post_handlers,
|
self.edited_message_handlers,
|
||||||
self.callback_query_handlers, self.inline_query_handlers
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
|
self.callback_query_handlers,
|
||||||
|
self.inline_query_handlers,
|
||||||
|
])
|
||||||
|
filters_factory.bind(IDFilter, event_handlers=[
|
||||||
|
self.message_handlers,
|
||||||
|
self.edited_message_handlers,
|
||||||
|
self.channel_post_handlers,
|
||||||
|
self.edited_channel_post_handlers,
|
||||||
|
self.callback_query_handlers,
|
||||||
|
self.inline_query_handlers,
|
||||||
])
|
])
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,5 @@ __all__ = [
|
||||||
'get_filter_spec',
|
'get_filter_spec',
|
||||||
'get_filters_spec',
|
'get_filters_spec',
|
||||||
'execute_filter',
|
'execute_filter',
|
||||||
'check_filters'
|
'check_filters',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,9 @@ class Command(Filter):
|
||||||
|
|
||||||
if not ignore_mention and mention and (await message.bot.me).username.lower() != mention.lower():
|
if not ignore_mention and mention and (await message.bot.me).username.lower() != mention.lower():
|
||||||
return False
|
return False
|
||||||
elif prefix not in prefixes:
|
if prefix not in prefixes:
|
||||||
return False
|
return False
|
||||||
elif (command.lower() if ignore_case else command) not in commands:
|
if (command.lower() if ignore_case else command) not in commands:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return {'command': Command.CommandObj(command=command, prefix=prefix, mention=mention)}
|
return {'command': Command.CommandObj(command=command, prefix=prefix, mention=mention)}
|
||||||
|
|
@ -149,7 +149,7 @@ class CommandStart(Command):
|
||||||
|
|
||||||
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
|
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
|
||||||
"""
|
"""
|
||||||
super(CommandStart, self).__init__(['start'])
|
super().__init__(['start'])
|
||||||
self.deep_link = deep_link
|
self.deep_link = deep_link
|
||||||
|
|
||||||
async def check(self, message: types.Message):
|
async def check(self, message: types.Message):
|
||||||
|
|
@ -159,7 +159,7 @@ class CommandStart(Command):
|
||||||
:param message:
|
:param message:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
check = await super(CommandStart, self).check(message)
|
check = await super().check(message)
|
||||||
|
|
||||||
if check and self.deep_link is not None:
|
if check and self.deep_link is not None:
|
||||||
if not isinstance(self.deep_link, re.Pattern):
|
if not isinstance(self.deep_link, re.Pattern):
|
||||||
|
|
@ -179,7 +179,7 @@ class CommandHelp(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CommandHelp, self).__init__(['help'])
|
super().__init__(['help'])
|
||||||
|
|
||||||
|
|
||||||
class CommandSettings(Command):
|
class CommandSettings(Command):
|
||||||
|
|
@ -188,7 +188,7 @@ class CommandSettings(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CommandSettings, self).__init__(['settings'])
|
super().__init__(['settings'])
|
||||||
|
|
||||||
|
|
||||||
class CommandPrivacy(Command):
|
class CommandPrivacy(Command):
|
||||||
|
|
@ -197,7 +197,7 @@ class CommandPrivacy(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(CommandPrivacy, self).__init__(['privacy'])
|
super().__init__(['privacy'])
|
||||||
|
|
||||||
|
|
||||||
class Text(Filter):
|
class Text(Filter):
|
||||||
|
|
@ -205,6 +205,13 @@ class Text(Filter):
|
||||||
Simple text filter
|
Simple text filter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_default_params = (
|
||||||
|
('text', 'equals'),
|
||||||
|
('text_contains', 'contains'),
|
||||||
|
('text_startswith', 'startswith'),
|
||||||
|
('text_endswith', 'endswith'),
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
equals: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
equals: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||||
contains: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
contains: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
|
||||||
|
|
@ -244,14 +251,9 @@ class Text(Filter):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: Dict[str, Any]):
|
def validate(cls, full_config: Dict[str, Any]):
|
||||||
if 'text' in full_config:
|
for param, key in cls._default_params:
|
||||||
return {'equals': full_config.pop('text')}
|
if param in full_config:
|
||||||
elif 'text_contains' in full_config:
|
return {key: full_config.pop(param)}
|
||||||
return {'contains': full_config.pop('text_contains')}
|
|
||||||
elif 'text_startswith' in full_config:
|
|
||||||
return {'startswith': full_config.pop('text_startswith')}
|
|
||||||
elif 'text_endswith' in full_config:
|
|
||||||
return {'endswith': full_config.pop('text_endswith')}
|
|
||||||
|
|
||||||
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]):
|
async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]):
|
||||||
if isinstance(obj, Message):
|
if isinstance(obj, Message):
|
||||||
|
|
@ -269,19 +271,26 @@ class Text(Filter):
|
||||||
|
|
||||||
if self.ignore_case:
|
if self.ignore_case:
|
||||||
text = text.lower()
|
text = text.lower()
|
||||||
|
_pre_process_func = lambda s: str(s).lower()
|
||||||
|
else:
|
||||||
|
_pre_process_func = str
|
||||||
|
|
||||||
|
# now check
|
||||||
if self.equals is not None:
|
if self.equals is not None:
|
||||||
self.equals = list(map(lambda s: str(s).lower() if self.ignore_case else str(s), self.equals))
|
equals = list(map(_pre_process_func, self.equals))
|
||||||
return text in self.equals
|
return text in equals
|
||||||
elif self.contains is not None:
|
|
||||||
self.contains = list(map(lambda s: str(s).lower() if self.ignore_case else str(s), self.contains))
|
if self.contains is not None:
|
||||||
return all(map(text.__contains__, self.contains))
|
contains = list(map(_pre_process_func, self.contains))
|
||||||
elif self.startswith is not None:
|
return all(map(text.__contains__, contains))
|
||||||
self.startswith = list(map(lambda s: str(s).lower() if self.ignore_case else str(s), self.startswith))
|
|
||||||
return any(map(text.startswith, self.startswith))
|
if self.startswith is not None:
|
||||||
elif self.endswith is not None:
|
startswith = list(map(_pre_process_func, self.startswith))
|
||||||
self.endswith = list(map(lambda s: str(s).lower() if self.ignore_case else str(s), self.endswith))
|
return any(map(text.startswith, startswith))
|
||||||
return any(map(text.endswith, self.endswith))
|
|
||||||
|
if self.endswith is not None:
|
||||||
|
endswith = list(map(_pre_process_func, self.endswith))
|
||||||
|
return any(map(text.endswith, endswith))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -556,9 +565,9 @@ class IDFilter(Filter):
|
||||||
|
|
||||||
if self.user_id and self.chat_id:
|
if self.user_id and self.chat_id:
|
||||||
return user_id in self.user_id and chat_id in self.chat_id
|
return user_id in self.user_id and chat_id in self.chat_id
|
||||||
elif self.user_id:
|
if self.user_id:
|
||||||
return user_id in self.user_id
|
return user_id in self.user_id
|
||||||
elif self.chat_id:
|
if self.chat_id:
|
||||||
return chat_id in self.chat_id
|
return chat_id in self.chat_id
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
|
|
@ -70,4 +70,4 @@ class FiltersFactory:
|
||||||
yield filter_
|
yield filter_
|
||||||
|
|
||||||
if full_config:
|
if full_config:
|
||||||
raise NameError('Invalid filter name(s): \'' + '\', '.join(full_config.keys()) + '\'')
|
raise NameError("Invalid filter name(s): '" + "', ".join(full_config.keys()) + "'")
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class FilterRecord:
|
||||||
Filters record for factory
|
Filters record for factory
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, callback: typing.Callable,
|
def __init__(self, callback: typing.Union[typing.Callable, 'AbstractFilter'],
|
||||||
validator: typing.Optional[typing.Callable] = None,
|
validator: typing.Optional[typing.Callable] = None,
|
||||||
event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
|
||||||
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None):
|
exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None):
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,17 @@ class State:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
if self._state is None:
|
if self._state is None or self._state == '*':
|
||||||
return None
|
|
||||||
elif self._state == '*':
|
|
||||||
return self._state
|
return self._state
|
||||||
elif self._group_name is None and self._group:
|
|
||||||
|
if self._group_name is None and self._group:
|
||||||
group = self._group.__full_group_name__
|
group = self._group.__full_group_name__
|
||||||
elif self._group_name:
|
elif self._group_name:
|
||||||
group = self._group_name
|
group = self._group_name
|
||||||
else:
|
else:
|
||||||
group = '@'
|
group = '@'
|
||||||
return f"{group}:{self._state}"
|
|
||||||
|
return f'{group}:{self._state}'
|
||||||
|
|
||||||
def set_parent(self, group):
|
def set_parent(self, group):
|
||||||
if not issubclass(group, StatesGroup):
|
if not issubclass(group, StatesGroup):
|
||||||
|
|
@ -73,7 +73,6 @@ class StatesGroupMeta(type):
|
||||||
elif inspect.isclass(prop) and issubclass(prop, StatesGroup):
|
elif inspect.isclass(prop) and issubclass(prop, StatesGroup):
|
||||||
childs.append(prop)
|
childs.append(prop)
|
||||||
prop._parent = cls
|
prop._parent = cls
|
||||||
# continue
|
|
||||||
|
|
||||||
cls._parent = None
|
cls._parent = None
|
||||||
cls._childs = tuple(childs)
|
cls._childs = tuple(childs)
|
||||||
|
|
@ -83,13 +82,13 @@ class StatesGroupMeta(type):
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __group_name__(cls):
|
def __group_name__(cls) -> str:
|
||||||
return cls._group_name
|
return cls._group_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __full_group_name__(cls):
|
def __full_group_name__(cls) -> str:
|
||||||
if cls._parent:
|
if cls._parent:
|
||||||
return cls._parent.__full_group_name__ + '.' + cls._group_name
|
return '.'.join((cls._parent.__full_group_name__, cls._group_name))
|
||||||
return cls._group_name
|
return cls._group_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -97,7 +96,7 @@ class StatesGroupMeta(type):
|
||||||
return cls._states
|
return cls._states
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def childs(cls):
|
def childs(cls) -> tuple:
|
||||||
return cls._childs
|
return cls._childs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -130,9 +129,9 @@ class StatesGroupMeta(type):
|
||||||
def __contains__(cls, item):
|
def __contains__(cls, item):
|
||||||
if isinstance(item, str):
|
if isinstance(item, str):
|
||||||
return item in cls.all_states_names
|
return item in cls.all_states_names
|
||||||
elif isinstance(item, State):
|
if isinstance(item, State):
|
||||||
return item in cls.all_states
|
return item in cls.all_states
|
||||||
elif isinstance(item, StatesGroup):
|
if isinstance(item, StatesGroup):
|
||||||
return item in cls.all_childs
|
return item in cls.all_childs
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import inspect
|
import inspect
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Iterable
|
from typing import Optional, Iterable, List
|
||||||
|
|
||||||
ctx_data = ContextVar('ctx_handler_data')
|
ctx_data = ContextVar('ctx_handler_data')
|
||||||
current_handler = ContextVar('current_handler')
|
current_handler = ContextVar('current_handler')
|
||||||
|
|
@ -41,11 +41,10 @@ class Handler:
|
||||||
self.dispatcher = dispatcher
|
self.dispatcher = dispatcher
|
||||||
self.once = once
|
self.once = once
|
||||||
|
|
||||||
self.handlers = []
|
self.handlers: List[Handler.HandlerObj] = []
|
||||||
self.middleware_key = middleware_key
|
self.middleware_key = middleware_key
|
||||||
|
|
||||||
def register(self, handler, filters=None, index=None):
|
def register(self, handler, filters=None, index=None):
|
||||||
from .filters import get_filters_spec
|
|
||||||
"""
|
"""
|
||||||
Register callback
|
Register callback
|
||||||
|
|
||||||
|
|
@ -55,6 +54,8 @@ class Handler:
|
||||||
:param filters: list of filters
|
:param filters: list of filters
|
||||||
:param index: you can reorder handlers
|
:param index: you can reorder handlers
|
||||||
"""
|
"""
|
||||||
|
from .filters import get_filters_spec
|
||||||
|
|
||||||
spec = _get_spec(handler)
|
spec = _get_spec(handler)
|
||||||
|
|
||||||
if filters and not isinstance(filters, (list, tuple, set)):
|
if filters and not isinstance(filters, (list, tuple, set)):
|
||||||
|
|
|
||||||
|
|
@ -523,7 +523,7 @@ class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificatio
|
||||||
'disable_web_page_preview': self.disable_web_page_preview,
|
'disable_web_page_preview': self.disable_web_page_preview,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
def write(self, *text, sep=' '):
|
def write(self, *text, sep=' '):
|
||||||
|
|
@ -642,7 +642,7 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'caption': self.caption,
|
'caption': self.caption,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -704,7 +704,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -817,7 +817,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'caption': self.caption,
|
'caption': self.caption,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -871,7 +871,7 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'duration': self.duration,
|
'duration': self.duration,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -924,7 +924,7 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'length': self.length,
|
'length': self.length,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1050,7 +1050,7 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'longitude': self.longitude,
|
'longitude': self.longitude,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1109,7 +1109,7 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'foursquare_id': self.foursquare_id,
|
'foursquare_id': self.foursquare_id,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1160,7 +1160,7 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'last_name': self.last_name,
|
'last_name': self.last_name,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1220,7 +1220,7 @@ class KickChatMember(BaseResponse):
|
||||||
return {
|
return {
|
||||||
'chat_id': self.chat_id,
|
'chat_id': self.chat_id,
|
||||||
'user_id': self.user_id,
|
'user_id': self.user_id,
|
||||||
'until_date': prepare_arg(self.until_date)
|
'until_date': prepare_arg(self.until_date),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1608,7 +1608,7 @@ class EditMessageText(BaseResponse, ParseModeMixin, DisableWebPagePreviewMixin):
|
||||||
'text': self.text,
|
'text': self.text,
|
||||||
'parse_mode': self.parse_mode,
|
'parse_mode': self.parse_mode,
|
||||||
'disable_web_page_preview': self.disable_web_page_preview,
|
'disable_web_page_preview': self.disable_web_page_preview,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1649,7 +1649,7 @@ class EditMessageCaption(BaseResponse):
|
||||||
'message_id': self.message_id,
|
'message_id': self.message_id,
|
||||||
'inline_message_id': self.inline_message_id,
|
'inline_message_id': self.inline_message_id,
|
||||||
'caption': self.caption,
|
'caption': self.caption,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1685,7 +1685,7 @@ class EditMessageReplyMarkup(BaseResponse):
|
||||||
'chat_id': self.chat_id,
|
'chat_id': self.chat_id,
|
||||||
'message_id': self.message_id,
|
'message_id': self.message_id,
|
||||||
'inline_message_id': self.inline_message_id,
|
'inline_message_id': self.inline_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1756,7 +1756,7 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'sticker': self.sticker,
|
'sticker': self.sticker,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1848,7 +1848,7 @@ class AddStickerToSet(BaseResponse):
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'png_sticker': self.png_sticker,
|
'png_sticker': self.png_sticker,
|
||||||
'emojis': self.emojis,
|
'emojis': self.emojis,
|
||||||
'mask_position': prepare_arg(self.mask_position)
|
'mask_position': prepare_arg(self.mask_position),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2177,5 +2177,5 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
|
||||||
'game_short_name': self.game_short_name,
|
'game_short_name': self.game_short_name,
|
||||||
'disable_notification': self.disable_notification,
|
'disable_notification': self.disable_notification,
|
||||||
'reply_to_message_id': self.reply_to_message_id,
|
'reply_to_message_id': self.reply_to_message_id,
|
||||||
'reply_markup': prepare_arg(self.reply_markup)
|
'reply_markup': prepare_arg(self.reply_markup),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,60 +94,60 @@ class Message(base.TelegramObject):
|
||||||
def content_type(self):
|
def content_type(self):
|
||||||
if self.text:
|
if self.text:
|
||||||
return ContentType.TEXT
|
return ContentType.TEXT
|
||||||
elif self.audio:
|
if self.audio:
|
||||||
return ContentType.AUDIO
|
return ContentType.AUDIO
|
||||||
elif self.animation:
|
if self.animation:
|
||||||
return ContentType.ANIMATION
|
return ContentType.ANIMATION
|
||||||
elif self.document:
|
if self.document:
|
||||||
return ContentType.DOCUMENT
|
return ContentType.DOCUMENT
|
||||||
elif self.game:
|
if self.game:
|
||||||
return ContentType.GAME
|
return ContentType.GAME
|
||||||
elif self.photo:
|
if self.photo:
|
||||||
return ContentType.PHOTO
|
return ContentType.PHOTO
|
||||||
elif self.sticker:
|
if self.sticker:
|
||||||
return ContentType.STICKER
|
return ContentType.STICKER
|
||||||
elif self.video:
|
if self.video:
|
||||||
return ContentType.VIDEO
|
return ContentType.VIDEO
|
||||||
elif self.video_note:
|
if self.video_note:
|
||||||
return ContentType.VIDEO_NOTE
|
return ContentType.VIDEO_NOTE
|
||||||
elif self.voice:
|
if self.voice:
|
||||||
return ContentType.VOICE
|
return ContentType.VOICE
|
||||||
elif self.contact:
|
if self.contact:
|
||||||
return ContentType.CONTACT
|
return ContentType.CONTACT
|
||||||
elif self.venue:
|
if self.venue:
|
||||||
return ContentType.VENUE
|
return ContentType.VENUE
|
||||||
elif self.location:
|
if self.location:
|
||||||
return ContentType.LOCATION
|
return ContentType.LOCATION
|
||||||
elif self.new_chat_members:
|
if self.new_chat_members:
|
||||||
return ContentType.NEW_CHAT_MEMBERS
|
return ContentType.NEW_CHAT_MEMBERS
|
||||||
elif self.left_chat_member:
|
if self.left_chat_member:
|
||||||
return ContentType.LEFT_CHAT_MEMBER
|
return ContentType.LEFT_CHAT_MEMBER
|
||||||
elif self.invoice:
|
if self.invoice:
|
||||||
return ContentType.INVOICE
|
return ContentType.INVOICE
|
||||||
elif self.successful_payment:
|
if self.successful_payment:
|
||||||
return ContentType.SUCCESSFUL_PAYMENT
|
return ContentType.SUCCESSFUL_PAYMENT
|
||||||
elif self.connected_website:
|
if self.connected_website:
|
||||||
return ContentType.CONNECTED_WEBSITE
|
return ContentType.CONNECTED_WEBSITE
|
||||||
elif self.migrate_from_chat_id:
|
if self.migrate_from_chat_id:
|
||||||
return ContentType.MIGRATE_FROM_CHAT_ID
|
return ContentType.MIGRATE_FROM_CHAT_ID
|
||||||
elif self.migrate_to_chat_id:
|
if self.migrate_to_chat_id:
|
||||||
return ContentType.MIGRATE_TO_CHAT_ID
|
return ContentType.MIGRATE_TO_CHAT_ID
|
||||||
elif self.pinned_message:
|
if self.pinned_message:
|
||||||
return ContentType.PINNED_MESSAGE
|
return ContentType.PINNED_MESSAGE
|
||||||
elif self.new_chat_title:
|
if self.new_chat_title:
|
||||||
return ContentType.NEW_CHAT_TITLE
|
return ContentType.NEW_CHAT_TITLE
|
||||||
elif self.new_chat_photo:
|
if self.new_chat_photo:
|
||||||
return ContentType.NEW_CHAT_PHOTO
|
return ContentType.NEW_CHAT_PHOTO
|
||||||
elif self.delete_chat_photo:
|
if self.delete_chat_photo:
|
||||||
return ContentType.DELETE_CHAT_PHOTO
|
return ContentType.DELETE_CHAT_PHOTO
|
||||||
elif self.group_chat_created:
|
if self.group_chat_created:
|
||||||
return ContentType.GROUP_CHAT_CREATED
|
return ContentType.GROUP_CHAT_CREATED
|
||||||
elif self.passport_data:
|
if self.passport_data:
|
||||||
return ContentType.PASSPORT_DATA
|
return ContentType.PASSPORT_DATA
|
||||||
elif self.poll:
|
if self.poll:
|
||||||
return ContentType.POLL
|
return ContentType.POLL
|
||||||
else:
|
|
||||||
return ContentType.UNKNOWN
|
return ContentType.UNKNOWN
|
||||||
|
|
||||||
def is_command(self):
|
def is_command(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -49,30 +49,24 @@ class MessageEntity(base.TelegramObject):
|
||||||
entity_text = self.get_text(text)
|
entity_text = self.get_text(text)
|
||||||
|
|
||||||
if self.type == MessageEntityType.BOLD:
|
if self.type == MessageEntityType.BOLD:
|
||||||
if as_html:
|
method = markdown.hbold if as_html else markdown.bold
|
||||||
return markdown.hbold(entity_text)
|
return method(entity_text)
|
||||||
return markdown.bold(entity_text)
|
if self.type == MessageEntityType.ITALIC:
|
||||||
elif self.type == MessageEntityType.ITALIC:
|
method = markdown.hitalic if as_html else markdown.italic
|
||||||
if as_html:
|
return method(entity_text)
|
||||||
return markdown.hitalic(entity_text)
|
if self.type == MessageEntityType.PRE:
|
||||||
return markdown.italic(entity_text)
|
method = markdown.hpre if as_html else markdown.pre
|
||||||
elif self.type == MessageEntityType.PRE:
|
return method(entity_text)
|
||||||
if as_html:
|
if self.type == MessageEntityType.CODE:
|
||||||
return markdown.hpre(entity_text)
|
method = markdown.hcode if as_html else markdown.code
|
||||||
return markdown.pre(entity_text)
|
return method(entity_text)
|
||||||
elif self.type == MessageEntityType.CODE:
|
if self.type == MessageEntityType.URL:
|
||||||
if as_html:
|
method = markdown.hlink if as_html else markdown.link
|
||||||
return markdown.hcode(entity_text)
|
return method(entity_text, entity_text)
|
||||||
return markdown.code(entity_text)
|
if self.type == MessageEntityType.TEXT_LINK:
|
||||||
elif self.type == MessageEntityType.URL:
|
method = markdown.hlink if as_html else markdown.link
|
||||||
if as_html:
|
return method(entity_text, self.url)
|
||||||
return markdown.hlink(entity_text, entity_text)
|
if self.type == MessageEntityType.TEXT_MENTION and self.user:
|
||||||
return markdown.link(entity_text, entity_text)
|
|
||||||
elif self.type == MessageEntityType.TEXT_LINK:
|
|
||||||
if as_html:
|
|
||||||
return markdown.hlink(entity_text, self.url)
|
|
||||||
return markdown.link(entity_text, self.url)
|
|
||||||
elif self.type == MessageEntityType.TEXT_MENTION and self.user:
|
|
||||||
return self.user.get_mention(entity_text)
|
return self.user.get_mention(entity_text)
|
||||||
return entity_text
|
return entity_text
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import babel
|
import babel
|
||||||
|
|
||||||
from . import base
|
from . import base
|
||||||
|
|
@ -45,7 +47,7 @@ class User(base.TelegramObject):
|
||||||
return self.full_name
|
return self.full_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def locale(self) -> babel.core.Locale or None:
|
def locale(self) -> Optional[babel.core.Locale]:
|
||||||
"""
|
"""
|
||||||
Get user's locale
|
Get user's locale
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,13 @@ class CallbackData:
|
||||||
|
|
||||||
def __init__(self, prefix, *parts, sep=':'):
|
def __init__(self, prefix, *parts, sep=':'):
|
||||||
if not isinstance(prefix, str):
|
if not isinstance(prefix, str):
|
||||||
raise TypeError(f"Prefix must be instance of str not {type(prefix).__name__}")
|
raise TypeError(f'Prefix must be instance of str not {type(prefix).__name__}')
|
||||||
elif not prefix:
|
if not prefix:
|
||||||
raise ValueError('Prefix can\'t be empty')
|
raise ValueError("Prefix can't be empty")
|
||||||
elif sep in prefix:
|
if sep in prefix:
|
||||||
raise ValueError(f"Separator '{sep}' can't be used in prefix")
|
raise ValueError(f"Separator {sep!r} can't be used in prefix")
|
||||||
elif not parts:
|
if not parts:
|
||||||
raise TypeError('Parts is not passed!')
|
raise TypeError('Parts were not passed!')
|
||||||
|
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.sep = sep
|
self.sep = sep
|
||||||
|
|
@ -59,20 +59,20 @@ class CallbackData:
|
||||||
if args:
|
if args:
|
||||||
value = args.pop(0)
|
value = args.pop(0)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Value for '{part}' is not passed!")
|
raise ValueError(f'Value for {part!r} was not passed!')
|
||||||
|
|
||||||
if value is not None and not isinstance(value, str):
|
if value is not None and not isinstance(value, str):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
raise ValueError(f"Value for part {part} can't be empty!'")
|
raise ValueError(f"Value for part {part!r} can't be empty!'")
|
||||||
elif self.sep in value:
|
if self.sep in value:
|
||||||
raise ValueError(f"Symbol defined as separator can't be used in values of parts")
|
raise ValueError(f"Symbol {self.sep!r} is defined as the separator and can't be used in parts' values")
|
||||||
|
|
||||||
data.append(value)
|
data.append(value)
|
||||||
|
|
||||||
if args or kwargs:
|
if args or kwargs:
|
||||||
raise TypeError('Too many arguments is passed!')
|
raise TypeError('Too many arguments were passed!')
|
||||||
|
|
||||||
callback_data = self.sep.join(data)
|
callback_data = self.sep.join(data)
|
||||||
if len(callback_data) > 64:
|
if len(callback_data) > 64:
|
||||||
|
|
@ -106,30 +106,31 @@ class CallbackData:
|
||||||
"""
|
"""
|
||||||
for key in config.keys():
|
for key in config.keys():
|
||||||
if key not in self._part_names:
|
if key not in self._part_names:
|
||||||
raise ValueError(f"Invalid field name '{key}'")
|
raise ValueError(f'Invalid field name {key!r}')
|
||||||
return CallbackDataFilter(self, config)
|
return CallbackDataFilter(self, config)
|
||||||
|
|
||||||
|
|
||||||
class CallbackDataFilter(Filter):
|
class CallbackDataFilter(Filter):
|
||||||
|
|
||||||
def __init__(self, factory: CallbackData, config: typing.Dict[str, str]):
|
def __init__(self, factory: CallbackData, config: typing.Dict[str, str]):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.factory = factory
|
self.factory = factory
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, full_config: typing.Dict[str, typing.Any]):
|
def validate(cls, full_config: typing.Dict[str, typing.Any]):
|
||||||
raise ValueError('That filter can\'t be used in filters factory!')
|
raise ValueError("That filter can't be used in filters factory!")
|
||||||
|
|
||||||
async def check(self, query: types.CallbackQuery):
|
async def check(self, query: types.CallbackQuery):
|
||||||
try:
|
try:
|
||||||
data = self.factory.parse(query.data)
|
data = self.factory.parse(query.data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
for key, value in self.config.items():
|
for key, value in self.config.items():
|
||||||
if isinstance(value, (list, tuple, set)):
|
if isinstance(value, (list, tuple, set, frozenset)):
|
||||||
if data.get(key) not in value:
|
if data.get(key) not in value:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if value != data.get(key):
|
if data.get(key) != value:
|
||||||
return False
|
return False
|
||||||
return {'callback_data': data}
|
return {'callback_data': data}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
"""
|
|
||||||
Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
|
|
||||||
"""
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import warnings
|
import warnings
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
def deprecated(reason):
|
def deprecated(reason):
|
||||||
|
|
@ -12,6 +9,8 @@ def deprecated(reason):
|
||||||
This is a decorator which can be used to mark functions
|
This is a decorator which can be used to mark functions
|
||||||
as deprecated. It will result in a warning being emitted
|
as deprecated. It will result in a warning being emitted
|
||||||
when the function is used.
|
when the function is used.
|
||||||
|
|
||||||
|
Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(reason, str):
|
if isinstance(reason, str):
|
||||||
|
|
@ -41,7 +40,7 @@ def deprecated(reason):
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
elif inspect.isclass(reason) or inspect.isfunction(reason):
|
if inspect.isclass(reason) or inspect.isfunction(reason):
|
||||||
|
|
||||||
# The @deprecated is used without any 'reason'.
|
# The @deprecated is used without any 'reason'.
|
||||||
#
|
#
|
||||||
|
|
@ -65,11 +64,71 @@ def deprecated(reason):
|
||||||
|
|
||||||
return wrapper1
|
return wrapper1
|
||||||
|
|
||||||
else:
|
raise TypeError(repr(type(reason)))
|
||||||
raise TypeError(repr(type(reason)))
|
|
||||||
|
|
||||||
|
|
||||||
def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2):
|
def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2):
|
||||||
warnings.simplefilter('always', warning)
|
warnings.simplefilter('always', warning)
|
||||||
warnings.warn(message, category=warning, stacklevel=stacklevel)
|
warnings.warn(message, category=warning, stacklevel=stacklevel)
|
||||||
warnings.simplefilter('default', warning)
|
warnings.simplefilter('default', warning)
|
||||||
|
|
||||||
|
|
||||||
|
def renamed_argument(old_name: str, new_name: str, until_version: str, stacklevel: int = 3):
|
||||||
|
"""
|
||||||
|
A meta-decorator to mark an argument as deprecated.
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@renamed_argument("chat", "chat_id", "3.0") # stacklevel=3 by default
|
||||||
|
@renamed_argument("user", "user_id", "3.0", stacklevel=4)
|
||||||
|
def some_function(user_id, chat_id=None):
|
||||||
|
print(f"user_id={user_id}, chat_id={chat_id}")
|
||||||
|
|
||||||
|
some_function(user=123) # prints 'user_id=123, chat_id=None' with warning
|
||||||
|
some_function(123) # prints 'user_id=123, chat_id=None' without warning
|
||||||
|
some_function(user_id=123) # prints 'user_id=123, chat_id=None' without warning
|
||||||
|
|
||||||
|
|
||||||
|
:param old_name:
|
||||||
|
:param new_name:
|
||||||
|
:param until_version: the version in which the argument is scheduled to be removed
|
||||||
|
:param stacklevel: leave it to default if it's the first decorator used.
|
||||||
|
Increment with any new decorator used.
|
||||||
|
:return: decorator
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
if asyncio.iscoroutinefunction(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
async def wrapped(*args, **kwargs):
|
||||||
|
if old_name in kwargs:
|
||||||
|
warn_deprecated(f"In coroutine '{func.__name__}' argument '{old_name}' "
|
||||||
|
f"is renamed to '{new_name}' "
|
||||||
|
f"and will be removed in aiogram {until_version}",
|
||||||
|
stacklevel=stacklevel)
|
||||||
|
kwargs.update(
|
||||||
|
{
|
||||||
|
new_name: kwargs[old_name],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
kwargs.pop(old_name)
|
||||||
|
await func(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
if old_name in kwargs:
|
||||||
|
warn_deprecated(f"In function `{func.__name__}` argument `{old_name}` "
|
||||||
|
f"is renamed to `{new_name}` "
|
||||||
|
f"and will be removed in aiogram {until_version}",
|
||||||
|
stacklevel=stacklevel)
|
||||||
|
kwargs.update(
|
||||||
|
{
|
||||||
|
new_name: kwargs[old_name],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
kwargs.pop(old_name)
|
||||||
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from ..dispatcher.webhook import BOT_DISPATCHER_KEY, DEFAULT_ROUTE_NAME, Webhook
|
||||||
APP_EXECUTOR_KEY = 'APP_EXECUTOR'
|
APP_EXECUTOR_KEY = 'APP_EXECUTOR'
|
||||||
|
|
||||||
|
|
||||||
def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
|
def _setup_callbacks(executor: 'Executor', on_startup=None, on_shutdown=None):
|
||||||
if on_startup is not None:
|
if on_startup is not None:
|
||||||
executor.on_startup(on_startup)
|
executor.on_startup(on_startup)
|
||||||
if on_shutdown is not None:
|
if on_shutdown is not None:
|
||||||
|
|
@ -23,7 +23,7 @@ def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
|
||||||
|
|
||||||
|
|
||||||
def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=True,
|
def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=True,
|
||||||
on_startup=None, on_shutdown=None, timeout=20, fast=True):
|
on_startup=None, on_shutdown=None, timeout=20, relax=0.1, fast=True):
|
||||||
"""
|
"""
|
||||||
Start bot in long-polling mode
|
Start bot in long-polling mode
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=Tr
|
||||||
executor = Executor(dispatcher, skip_updates=skip_updates, loop=loop)
|
executor = Executor(dispatcher, skip_updates=skip_updates, loop=loop)
|
||||||
_setup_callbacks(executor, on_startup, on_shutdown)
|
_setup_callbacks(executor, on_startup, on_shutdown)
|
||||||
|
|
||||||
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast)
|
executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, relax=relax, fast=fast)
|
||||||
|
|
||||||
|
|
||||||
def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None,
|
def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None,
|
||||||
|
|
@ -291,7 +291,7 @@ class Executor:
|
||||||
self.set_webhook(webhook_path=webhook_path, request_handler=request_handler, route_name=route_name)
|
self.set_webhook(webhook_path=webhook_path, request_handler=request_handler, route_name=route_name)
|
||||||
self.run_app(**kwargs)
|
self.run_app(**kwargs)
|
||||||
|
|
||||||
def start_polling(self, reset_webhook=None, timeout=20, fast=True):
|
def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True):
|
||||||
"""
|
"""
|
||||||
Start bot in long-polling mode
|
Start bot in long-polling mode
|
||||||
|
|
||||||
|
|
@ -303,7 +303,8 @@ class Executor:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
loop.run_until_complete(self._startup_polling())
|
loop.run_until_complete(self._startup_polling())
|
||||||
loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout, fast=fast))
|
loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout,
|
||||||
|
relax=relax, fast=fast))
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
# loop.stop()
|
# loop.stop()
|
||||||
|
|
@ -339,7 +340,7 @@ class Executor:
|
||||||
async def _skip_updates(self):
|
async def _skip_updates(self):
|
||||||
await self.dispatcher.reset_webhook(True)
|
await self.dispatcher.reset_webhook(True)
|
||||||
await self.dispatcher.skip_updates()
|
await self.dispatcher.skip_updates()
|
||||||
log.warning(f"Updates are skipped successfully.")
|
log.warning(f'Updates were skipped successfully.')
|
||||||
|
|
||||||
async def _welcome(self):
|
async def _welcome(self):
|
||||||
user = await self.dispatcher.bot.me
|
user = await self.dispatcher.bot.me
|
||||||
|
|
|
||||||
|
|
@ -120,15 +120,15 @@ class HelperMode(Helper):
|
||||||
"""
|
"""
|
||||||
if mode == cls.SCREAMING_SNAKE_CASE:
|
if mode == cls.SCREAMING_SNAKE_CASE:
|
||||||
return cls._screaming_snake_case(text)
|
return cls._screaming_snake_case(text)
|
||||||
elif mode == cls.snake_case:
|
if mode == cls.snake_case:
|
||||||
return cls._snake_case(text)
|
return cls._snake_case(text)
|
||||||
elif mode == cls.lowercase:
|
if mode == cls.lowercase:
|
||||||
return cls._snake_case(text).replace('_', '')
|
return cls._snake_case(text).replace('_', '')
|
||||||
elif mode == cls.lowerCamelCase:
|
if mode == cls.lowerCamelCase:
|
||||||
return cls._camel_case(text)
|
return cls._camel_case(text)
|
||||||
elif mode == cls.CamelCase:
|
if mode == cls.CamelCase:
|
||||||
return cls._camel_case(text, True)
|
return cls._camel_case(text, True)
|
||||||
elif callable(mode):
|
if callable(mode):
|
||||||
return mode(text)
|
return mode(text)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ T = TypeVar('T')
|
||||||
|
|
||||||
class ContextInstanceMixin:
|
class ContextInstanceMixin:
|
||||||
def __init_subclass__(cls, **kwargs):
|
def __init_subclass__(cls, **kwargs):
|
||||||
cls.__context_instance = contextvars.ContextVar('instance_' + cls.__name__)
|
cls.__context_instance = contextvars.ContextVar(f'instance_{cls.__name__}')
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -43,5 +43,5 @@ class ContextInstanceMixin:
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_current(cls: Type[T], value: T):
|
def set_current(cls: Type[T], value: T):
|
||||||
if not isinstance(value, cls):
|
if not isinstance(value, cls):
|
||||||
raise TypeError(f"Value should be instance of '{cls.__name__}' not '{type(value).__name__}'")
|
raise TypeError(f'Value should be instance of {cls.__name__!r} not {type(value).__name__!r}')
|
||||||
cls.__context_instance.set(value)
|
cls.__context_instance.set(value)
|
||||||
|
|
|
||||||
|
|
@ -52,14 +52,14 @@ def prepare_arg(value):
|
||||||
"""
|
"""
|
||||||
if value is None:
|
if value is None:
|
||||||
return value
|
return value
|
||||||
elif isinstance(value, (list, dict)) or hasattr(value, 'to_python'):
|
if isinstance(value, (list, dict)) or hasattr(value, 'to_python'):
|
||||||
return json.dumps(_normalize(value))
|
return json.dumps(_normalize(value))
|
||||||
elif isinstance(value, datetime.timedelta):
|
if isinstance(value, datetime.timedelta):
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
return int((now + value).timestamp())
|
return int((now + value).timestamp())
|
||||||
elif isinstance(value, datetime.datetime):
|
if isinstance(value, datetime.datetime):
|
||||||
return round(value.timestamp())
|
return round(value.timestamp())
|
||||||
elif isinstance(value, LazyProxy):
|
if isinstance(value, LazyProxy):
|
||||||
return str(value)
|
return str(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,4 @@ sphinx-rtd-theme>=0.4.3
|
||||||
sphinxcontrib-programoutput>=0.14
|
sphinxcontrib-programoutput>=0.14
|
||||||
aiohttp-socks>=0.2.2
|
aiohttp-socks>=0.2.2
|
||||||
rethinkdb>=2.4.1
|
rethinkdb>=2.4.1
|
||||||
|
coverage==4.5.3
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
==========
|
==========
|
||||||
Deprecated
|
Deprecated
|
||||||
==========
|
==========
|
||||||
Coming soon...
|
.. automodule:: aiogram.utils.deprecated
|
||||||
|
:members:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import uuid
|
import uuid
|
||||||
|
|
@ -21,12 +20,12 @@ dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
POSTS = {
|
POSTS = {
|
||||||
str(uuid.uuid4()): {
|
str(uuid.uuid4()): {
|
||||||
'title': f"Post {index}",
|
'title': f'Post {index}',
|
||||||
'body': 'Lorem ipsum dolor sit amet, '
|
'body': 'Lorem ipsum dolor sit amet, '
|
||||||
'consectetur adipiscing elit, '
|
'consectetur adipiscing elit, '
|
||||||
'sed do eiusmod tempor incididunt ut '
|
'sed do eiusmod tempor incididunt ut '
|
||||||
'labore et dolore magna aliqua',
|
'labore et dolore magna aliqua',
|
||||||
'votes': random.randint(-2, 5)
|
'votes': random.randint(-2, 5),
|
||||||
} for index in range(1, 6)
|
} for index in range(1, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,21 +41,24 @@ def get_keyboard() -> types.InlineKeyboardMarkup:
|
||||||
markup.add(
|
markup.add(
|
||||||
types.InlineKeyboardButton(
|
types.InlineKeyboardButton(
|
||||||
post['title'],
|
post['title'],
|
||||||
callback_data=posts_cb.new(id=post_id, action='view'))
|
callback_data=posts_cb.new(id=post_id, action='view')),
|
||||||
)
|
)
|
||||||
return markup
|
return markup
|
||||||
|
|
||||||
|
|
||||||
def format_post(post_id: str, post: dict) -> (str, types.InlineKeyboardMarkup):
|
def format_post(post_id: str, post: dict) -> (str, types.InlineKeyboardMarkup):
|
||||||
text = f"{md.hbold(post['title'])}\n" \
|
text = md.text(
|
||||||
f"{md.quote_html(post['body'])}\n" \
|
md.hbold(post['title']),
|
||||||
f"\n" \
|
md.quote_html(post['body']),
|
||||||
f"Votes: {post['votes']}"
|
'', # just new empty line
|
||||||
|
f"Votes: {post['votes']}",
|
||||||
|
sep = '\n',
|
||||||
|
)
|
||||||
|
|
||||||
markup = types.InlineKeyboardMarkup()
|
markup = types.InlineKeyboardMarkup()
|
||||||
markup.row(
|
markup.row(
|
||||||
types.InlineKeyboardButton('👍', callback_data=posts_cb.new(id=post_id, action='like')),
|
types.InlineKeyboardButton('👍', callback_data=posts_cb.new(id=post_id, action='like')),
|
||||||
types.InlineKeyboardButton('👎', callback_data=posts_cb.new(id=post_id, action='unlike')),
|
types.InlineKeyboardButton('👎', callback_data=posts_cb.new(id=post_id, action='dislike')),
|
||||||
)
|
)
|
||||||
markup.add(types.InlineKeyboardButton('<< Back', callback_data=posts_cb.new(id='-', action='list')))
|
markup.add(types.InlineKeyboardButton('<< Back', callback_data=posts_cb.new(id='-', action='list')))
|
||||||
return text, markup
|
return text, markup
|
||||||
|
|
@ -84,7 +86,7 @@ async def query_view(query: types.CallbackQuery, callback_data: dict):
|
||||||
await query.message.edit_text(text, reply_markup=markup)
|
await query.message.edit_text(text, reply_markup=markup)
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query_handler(posts_cb.filter(action=['like', 'unlike']))
|
@dp.callback_query_handler(posts_cb.filter(action=['like', 'dislike']))
|
||||||
async def query_post_vote(query: types.CallbackQuery, callback_data: dict):
|
async def query_post_vote(query: types.CallbackQuery, callback_data: dict):
|
||||||
try:
|
try:
|
||||||
await dp.throttle('vote', rate=1)
|
await dp.throttle('vote', rate=1)
|
||||||
|
|
@ -100,10 +102,10 @@ async def query_post_vote(query: types.CallbackQuery, callback_data: dict):
|
||||||
|
|
||||||
if action == 'like':
|
if action == 'like':
|
||||||
post['votes'] += 1
|
post['votes'] += 1
|
||||||
elif action == 'unlike':
|
elif action == 'dislike':
|
||||||
post['votes'] -= 1
|
post['votes'] -= 1
|
||||||
|
|
||||||
await query.answer('Voted.')
|
await query.answer('Vote accepted')
|
||||||
text, markup = format_post(post_id, post)
|
text, markup = format_post(post_id, post)
|
||||||
await query.message.edit_text(text, reply_markup=markup)
|
await query.message.edit_text(text, reply_markup=markup)
|
||||||
|
|
||||||
|
|
@ -114,4 +116,4 @@ async def message_not_modified_handler(update, error):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -27,42 +27,40 @@ likes = {} # user_id: amount_of_likes
|
||||||
def get_keyboard():
|
def get_keyboard():
|
||||||
return types.InlineKeyboardMarkup().row(
|
return types.InlineKeyboardMarkup().row(
|
||||||
types.InlineKeyboardButton('👍', callback_data=vote_cb.new(action='up')),
|
types.InlineKeyboardButton('👍', callback_data=vote_cb.new(action='up')),
|
||||||
types.InlineKeyboardButton('👎', callback_data=vote_cb.new(action='down')))
|
types.InlineKeyboardButton('👎', callback_data=vote_cb.new(action='down')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
amount_of_likes = likes.get(message.from_user.id, 0) # get value if key exists else set to 0
|
amount_of_likes = likes.get(message.from_user.id, 0) # get value if key exists else set to 0
|
||||||
await message.reply(f'Vote! Now you have {amount_of_likes} votes.', reply_markup=get_keyboard())
|
await message.reply(f'Vote! You have {amount_of_likes} votes now.', reply_markup=get_keyboard())
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query_handler(vote_cb.filter(action='up'))
|
@dp.callback_query_handler(vote_cb.filter(action=['up', 'down']))
|
||||||
async def vote_up_cb_handler(query: types.CallbackQuery, callback_data: dict):
|
async def callback_vote_action(query: types.CallbackQuery, callback_data: dict):
|
||||||
logging.info(callback_data) # callback_data contains all info from callback data
|
logging.info('Got this callback data: %r', callback_data) # callback_data contains all info from callback data
|
||||||
likes[query.from_user.id] = likes.get(query.from_user.id, 0) + 1 # update amount of likes in storage
|
await query.answer() # don't forget to answer callback query as soon as possible
|
||||||
amount_of_likes = likes[query.from_user.id]
|
callback_data_action = callback_data['action']
|
||||||
|
likes_count = likes.get(query.from_user.id, 0)
|
||||||
|
|
||||||
await bot.edit_message_text(f'You voted up! Now you have {amount_of_likes} votes.',
|
if callback_data_action == 'up':
|
||||||
query.from_user.id,
|
likes_count += 1
|
||||||
query.message.message_id,
|
else:
|
||||||
reply_markup=get_keyboard())
|
likes_count -= 1
|
||||||
|
|
||||||
|
likes[query.from_user.id] = likes_count # update amount of likes in storage
|
||||||
|
|
||||||
@dp.callback_query_handler(vote_cb.filter(action='down'))
|
await bot.edit_message_text(
|
||||||
async def vote_down_cb_handler(query: types.CallbackQuery, callback_data: dict):
|
f'You voted {callback_data_action}! Now you have {likes_count} vote[s].',
|
||||||
logging.info(callback_data) # callback_data contains all info from callback data
|
query.from_user.id,
|
||||||
likes[query.from_user.id] = likes.get(query.from_user.id, 0) - 1 # update amount of likes in storage
|
query.message.message_id,
|
||||||
amount_of_likes = likes[query.from_user.id]
|
reply_markup=get_keyboard(),
|
||||||
|
)
|
||||||
await bot.edit_message_text(f'You voted down! Now you have {amount_of_likes} votes.',
|
|
||||||
query.from_user.id,
|
|
||||||
query.message.message_id,
|
|
||||||
reply_markup=get_keyboard())
|
|
||||||
|
|
||||||
|
|
||||||
@dp.errors_handler(exception=MessageNotModified) # handle the cases when this exception raises
|
@dp.errors_handler(exception=MessageNotModified) # handle the cases when this exception raises
|
||||||
async def message_not_modified_handler(update, error):
|
async def message_not_modified_handler(update, error):
|
||||||
# pass
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
Babel is required.
|
Babel is required.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, md, types
|
from aiogram import Bot, Dispatcher, executor, md, types
|
||||||
|
|
@ -22,12 +21,13 @@ async def check_language(message: types.Message):
|
||||||
|
|
||||||
await message.reply(md.text(
|
await message.reply(md.text(
|
||||||
md.bold('Info about your language:'),
|
md.bold('Info about your language:'),
|
||||||
md.text(' 🔸', md.bold('Code:'), md.italic(locale.locale)),
|
md.text('🔸', md.bold('Code:'), md.code(locale.language)),
|
||||||
md.text(' 🔸', md.bold('Territory:'), md.italic(locale.territory or 'Unknown')),
|
md.text('🔸', md.bold('Territory:'), md.code(locale.territory or 'Unknown')),
|
||||||
md.text(' 🔸', md.bold('Language name:'), md.italic(locale.language_name)),
|
md.text('🔸', md.bold('Language name:'), md.code(locale.language_name)),
|
||||||
md.text(' 🔸', md.bold('English language name:'), md.italic(locale.english_name)),
|
md.text('🔸', md.bold('English language name:'), md.code(locale.english_name)),
|
||||||
sep='\n'))
|
sep='\n',
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
executor.start_polling(dp, loop=loop, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ dp = Dispatcher(bot)
|
||||||
@dp.message_handler(commands=['start', 'help'])
|
@dp.message_handler(commands=['start', 'help'])
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
"""
|
"""
|
||||||
This handler will be called when client send `/start` or `/help` commands.
|
This handler will be called when user sends `/start` or `/help` command
|
||||||
"""
|
"""
|
||||||
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
|
||||||
|
|
||||||
|
|
@ -28,13 +28,25 @@ async def send_welcome(message: types.Message):
|
||||||
@dp.message_handler(regexp='(^cat[s]?$|puss)')
|
@dp.message_handler(regexp='(^cat[s]?$|puss)')
|
||||||
async def cats(message: types.Message):
|
async def cats(message: types.Message):
|
||||||
with open('data/cats.jpg', 'rb') as photo:
|
with open('data/cats.jpg', 'rb') as photo:
|
||||||
await bot.send_photo(message.chat.id, photo, caption='Cats is here 😺',
|
'''
|
||||||
reply_to_message_id=message.message_id)
|
# Old fashioned way:
|
||||||
|
await bot.send_photo(
|
||||||
|
message.chat.id,
|
||||||
|
photo,
|
||||||
|
caption='Cats are here 😺',
|
||||||
|
reply_to_message_id=message.message_id,
|
||||||
|
)
|
||||||
|
'''
|
||||||
|
|
||||||
|
await message.reply_photo(photo, caption='Cats are here 😺')
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler()
|
@dp.message_handler()
|
||||||
async def echo(message: types.Message):
|
async def echo(message: types.Message):
|
||||||
await bot.send_message(message.chat.id, message.text)
|
# old style:
|
||||||
|
# await bot.send_message(message.chat.id, message.text)
|
||||||
|
|
||||||
|
await message.reply(message.text, reply=False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import asyncio
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import aiogram.utils.markdown as md
|
import aiogram.utils.markdown as md
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||||
from aiogram.dispatcher import FSMContext
|
from aiogram.dispatcher import FSMContext
|
||||||
|
from aiogram.dispatcher.filters import Text
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.types import ParseMode
|
from aiogram.types import ParseMode
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = 'BOT TOKEN HERE'
|
||||||
|
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
|
|
||||||
# For example use simple MemoryStorage for Dispatcher.
|
# For example use simple MemoryStorage for Dispatcher.
|
||||||
|
|
@ -25,7 +28,7 @@ class Form(StatesGroup):
|
||||||
gender = State() # Will be represented in storage as 'Form:gender'
|
gender = State() # Will be represented in storage as 'Form:gender'
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands='start')
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
"""
|
"""
|
||||||
Conversation's entry point
|
Conversation's entry point
|
||||||
|
|
@ -37,19 +40,21 @@ async def cmd_start(message: types.Message):
|
||||||
|
|
||||||
|
|
||||||
# You can use state '*' if you need to handle all states
|
# You can use state '*' if you need to handle all states
|
||||||
@dp.message_handler(state='*', commands=['cancel'])
|
@dp.message_handler(state='*', commands='cancel')
|
||||||
@dp.message_handler(lambda message: message.text.lower() == 'cancel', state='*')
|
@dp.message_handler(Text(equals='cancel', ignore_case=True), state='*')
|
||||||
async def cancel_handler(message: types.Message, state: FSMContext, raw_state: Optional[str] = None):
|
async def cancel_handler(message: types.Message, state: FSMContext):
|
||||||
"""
|
"""
|
||||||
Allow user to cancel any action
|
Allow user to cancel any action
|
||||||
"""
|
"""
|
||||||
if raw_state is None:
|
current_state = await state.get_state()
|
||||||
|
if current_state is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logging.info('Cancelling state %r', current_state)
|
||||||
# Cancel state and inform user about it
|
# Cancel state and inform user about it
|
||||||
await state.finish()
|
await state.finish()
|
||||||
# And remove keyboard (just in case)
|
# And remove keyboard (just in case)
|
||||||
await message.reply('Canceled.', reply_markup=types.ReplyKeyboardRemove())
|
await message.reply('Cancelled.', reply_markup=types.ReplyKeyboardRemove())
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(state=Form.name)
|
@dp.message_handler(state=Form.name)
|
||||||
|
|
@ -66,7 +71,7 @@ async def process_name(message: types.Message, state: FSMContext):
|
||||||
|
|
||||||
# Check age. Age gotta be digit
|
# Check age. Age gotta be digit
|
||||||
@dp.message_handler(lambda message: not message.text.isdigit(), state=Form.age)
|
@dp.message_handler(lambda message: not message.text.isdigit(), state=Form.age)
|
||||||
async def failed_process_age(message: types.Message):
|
async def process_age_invalid(message: types.Message):
|
||||||
"""
|
"""
|
||||||
If age is invalid
|
If age is invalid
|
||||||
"""
|
"""
|
||||||
|
|
@ -88,11 +93,11 @@ async def process_age(message: types.Message, state: FSMContext):
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender)
|
@dp.message_handler(lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender)
|
||||||
async def failed_process_gender(message: types.Message):
|
async def process_gender_invalid(message: types.Message):
|
||||||
"""
|
"""
|
||||||
In this example gender has to be one of: Male, Female, Other.
|
In this example gender has to be one of: Male, Female, Other.
|
||||||
"""
|
"""
|
||||||
return await message.reply("Bad gender name. Choose you gender from keyboard.")
|
return await message.reply("Bad gender name. Choose your gender from the keyboard.")
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(state=Form.gender)
|
@dp.message_handler(state=Form.gender)
|
||||||
|
|
@ -104,11 +109,17 @@ async def process_gender(message: types.Message, state: FSMContext):
|
||||||
markup = types.ReplyKeyboardRemove()
|
markup = types.ReplyKeyboardRemove()
|
||||||
|
|
||||||
# And send message
|
# And send message
|
||||||
await bot.send_message(message.chat.id, md.text(
|
await bot.send_message(
|
||||||
md.text('Hi! Nice to meet you,', md.bold(data['name'])),
|
message.chat.id,
|
||||||
md.text('Age:', data['age']),
|
md.text(
|
||||||
md.text('Gender:', data['gender']),
|
md.text('Hi! Nice to meet you,', md.bold(data['name'])),
|
||||||
sep='\n'), reply_markup=markup, parse_mode=ParseMode.MARKDOWN)
|
md.text('Age:', md.code(data['age'])),
|
||||||
|
md.text('Gender:', data['gender']),
|
||||||
|
sep='\n',
|
||||||
|
),
|
||||||
|
reply_markup=markup,
|
||||||
|
parse_mode=ParseMode.MARKDOWN,
|
||||||
|
)
|
||||||
|
|
||||||
# Finish conversation
|
# Finish conversation
|
||||||
await state.finish()
|
await state.finish()
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ from pathlib import Path
|
||||||
from aiogram import Bot, Dispatcher, executor, types
|
from aiogram import Bot, Dispatcher, executor, types
|
||||||
from aiogram.contrib.middlewares.i18n import I18nMiddleware
|
from aiogram.contrib.middlewares.i18n import I18nMiddleware
|
||||||
|
|
||||||
TOKEN = 'BOT TOKEN HERE'
|
TOKEN = 'BOT_TOKEN_HERE'
|
||||||
I18N_DOMAIN = 'mybot'
|
I18N_DOMAIN = 'mybot'
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).parent
|
BASE_DIR = Path(__file__).parent
|
||||||
|
|
@ -54,14 +54,16 @@ dp.middleware.setup(i18n)
|
||||||
_ = i18n.gettext
|
_ = i18n.gettext
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands='start')
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
# Simply use `_('message')` instead of `'message'` and never use f-strings for translatable texts.
|
# Simply use `_('message')` instead of `'message'` and never use f-strings for translatable texts.
|
||||||
await message.reply(_('Hello, <b>{user}</b>!').format(user=message.from_user.full_name))
|
await message.reply(_('Hello, <b>{user}</b>!').format(user=message.from_user.full_name))
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['lang'])
|
@dp.message_handler(commands='lang')
|
||||||
async def cmd_lang(message: types.Message, locale):
|
async def cmd_lang(message: types.Message, locale):
|
||||||
|
# For setting custom lang you have to modify i18n middleware, like this:
|
||||||
|
# https://github.com/aiogram/EventsTrackerBot/blob/master/modules/base/middlewares.py
|
||||||
await message.reply(_('Your current language: <i>{language}</i>').format(language=locale))
|
await message.reply(_('Your current language: <i>{language}</i>').format(language=locale))
|
||||||
|
|
||||||
# If you care about pluralization, here's small handler
|
# If you care about pluralization, here's small handler
|
||||||
|
|
@ -70,15 +72,27 @@ async def cmd_lang(message: types.Message, locale):
|
||||||
# Alias for gettext method, parser will understand double underscore as plural (aka ngettext)
|
# Alias for gettext method, parser will understand double underscore as plural (aka ngettext)
|
||||||
__ = i18n.gettext
|
__ = i18n.gettext
|
||||||
|
|
||||||
# Some pseudo numeric value
|
|
||||||
TOTAL_LIKES = 0
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['like'])
|
# some likes manager
|
||||||
|
LIKES_STORAGE = {'count': 0}
|
||||||
|
|
||||||
|
|
||||||
|
def get_likes() -> int:
|
||||||
|
return LIKES_STORAGE['count']
|
||||||
|
|
||||||
|
|
||||||
|
def increase_likes() -> int:
|
||||||
|
LIKES_STORAGE['count'] += 1
|
||||||
|
return get_likes()
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(commands='like')
|
||||||
async def cmd_like(message: types.Message, locale):
|
async def cmd_like(message: types.Message, locale):
|
||||||
TOTAL_LIKES += 1
|
likes = increase_likes()
|
||||||
|
|
||||||
# NOTE: This is comment for a translator
|
# NOTE: This is comment for a translator
|
||||||
await message.reply(__('Aiogram has {number} like!', 'Aiogram has {number} likes!', TOTAL_LIKES).format(number=TOTAL_LIKES))
|
await message.reply(__('Aiogram has {number} like!', 'Aiogram has {number} likes!', likes).format(number=likes))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,35 @@
|
||||||
from aiogram import Bot, Dispatcher, executor, types
|
from aiogram import Bot, Dispatcher, executor, types
|
||||||
from aiogram.dispatcher.handler import SkipHandler
|
from aiogram.dispatcher.handler import SkipHandler
|
||||||
|
|
||||||
API_TOKEN = 'API_TOKE_HERE'
|
|
||||||
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
user_id_to_test = None # todo: Set id here
|
user_id_required = None # TODO: Set id here
|
||||||
chat_id_to_test = user_id_to_test
|
chat_id_required = user_id_required # Change for use in groups (user_id == chat_id in pm)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(user_id=user_id_to_test)
|
@dp.message_handler(user_id=user_id_required)
|
||||||
async def handler1(msg: types.Message):
|
async def handler1(msg: types.Message):
|
||||||
await bot.send_message(msg.chat.id,
|
await bot.send_message(msg.chat.id, "Hello, checking with user_id=")
|
||||||
"Hello, checking with user_id=")
|
raise SkipHandler # just for demo
|
||||||
raise SkipHandler
|
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(chat_id=chat_id_to_test)
|
@dp.message_handler(chat_id=chat_id_required)
|
||||||
async def handler2(msg: types.Message):
|
async def handler2(msg: types.Message):
|
||||||
await bot.send_message(msg.chat.id,
|
await bot.send_message(msg.chat.id, "Hello, checking with chat_id=")
|
||||||
"Hello, checking with chat_id=")
|
raise SkipHandler # just for demo
|
||||||
raise SkipHandler
|
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(user_id=user_id_to_test, chat_id=chat_id_to_test)
|
@dp.message_handler(user_id=user_id_required, chat_id=chat_id_required)
|
||||||
async def handler3(msg: types.Message):
|
async def handler3(msg: types.Message):
|
||||||
await bot.send_message(msg.chat.id,
|
await msg.reply("Hello from user= & chat_id=", reply=False)
|
||||||
"Hello from user= & chat_id=")
|
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(user_id=[user_id_to_test, 123]) # todo: add second id here
|
@dp.message_handler(user_id=[user_id_required, 42]) # TODO: You can add any number of ids here
|
||||||
async def handler4(msg: types.Message):
|
async def handler4(msg: types.Message):
|
||||||
print("Checked user_id with list!")
|
await msg.reply("Checked user_id with list!", reply=False)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import asyncio
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiogram import Bot, types, Dispatcher, executor
|
from aiogram import Bot, Dispatcher, executor
|
||||||
|
from aiogram.types import InlineQuery, \
|
||||||
|
InputTextMessageContent, InlineQueryResultArticle
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
@ -12,10 +14,22 @@ dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
@dp.inline_handler()
|
@dp.inline_handler()
|
||||||
async def inline_echo(inline_query: types.InlineQuery):
|
async def inline_echo(inline_query: InlineQuery):
|
||||||
input_content = types.InputTextMessageContent(inline_query.query or 'echo')
|
# id affects both preview and content,
|
||||||
item = types.InlineQueryResultArticle(id='1', title='echo',
|
# so it has to be unique for each result
|
||||||
input_message_content=input_content)
|
# (Unique identifier for this result, 1-64 Bytes)
|
||||||
|
# you can set your unique id's
|
||||||
|
# but for example i'll generate it based on text because I know, that
|
||||||
|
# only text will be passed in this example
|
||||||
|
text = inline_query.query or 'echo'
|
||||||
|
input_content = InputTextMessageContent(text)
|
||||||
|
result_id: str = hashlib.md5(text.encode()).hexdigest()
|
||||||
|
item = InlineQueryResultArticle(
|
||||||
|
id=result_id,
|
||||||
|
title=f'Result {text!r}',
|
||||||
|
input_message_content=input_content,
|
||||||
|
)
|
||||||
|
# don't forget to set cache_time=1 for testing (default is 300s or 5m)
|
||||||
await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1)
|
await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,50 +6,56 @@ import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, types
|
from aiogram import Bot, Dispatcher, executor, types
|
||||||
|
|
||||||
|
|
||||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
# Initialize bot and dispatcher
|
# Initialize bot and dispatcher
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands='start')
|
||||||
async def start_cmd_handler(message: types.Message):
|
async def start_cmd_handler(message: types.Message):
|
||||||
keyboard_markup = types.InlineKeyboardMarkup(row_width=3)
|
keyboard_markup = types.InlineKeyboardMarkup(row_width=3)
|
||||||
# default row_width is 3, so here we can omit it actually
|
# default row_width is 3, so here we can omit it actually
|
||||||
# kept for clearness
|
# kept for clearness
|
||||||
|
|
||||||
keyboard_markup.row(types.InlineKeyboardButton("Yes!", callback_data='yes'),
|
text_and_data = (
|
||||||
# in real life for the callback_data the callback data factory should be used
|
('Yes!', 'yes'),
|
||||||
# here the raw string is used for the simplicity
|
('No!', 'no'),
|
||||||
types.InlineKeyboardButton("No!", callback_data='no'))
|
)
|
||||||
|
# in real life for the callback_data the callback data factory should be used
|
||||||
|
# here the raw string is used for the simplicity
|
||||||
|
row_btns = (types.InlineKeyboardButton(text, callback_data=data) for text, data in text_and_data)
|
||||||
|
|
||||||
keyboard_markup.add(types.InlineKeyboardButton("aiogram link",
|
keyboard_markup.row(*row_btns)
|
||||||
url='https://github.com/aiogram/aiogram'))
|
keyboard_markup.add(
|
||||||
# url buttons has no callback data
|
# url buttons have no callback data
|
||||||
|
types.InlineKeyboardButton('aiogram source', url='https://github.com/aiogram/aiogram'),
|
||||||
|
)
|
||||||
|
|
||||||
await message.reply("Hi!\nDo you love aiogram?", reply_markup=keyboard_markup)
|
await message.reply("Hi!\nDo you love aiogram?", reply_markup=keyboard_markup)
|
||||||
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda cb: cb.data in ['yes', 'no']) # if cb.data is either 'yes' or 'no'
|
# Use multiple registrators. Handler will execute when one of the filters is OK
|
||||||
# @dp.callback_query_handler(text='yes') # if cb.data == 'yes'
|
@dp.callback_query_handler(text='no') # if cb.data == 'no'
|
||||||
|
@dp.callback_query_handler(text='yes') # if cb.data == 'yes'
|
||||||
async def inline_kb_answer_callback_handler(query: types.CallbackQuery):
|
async def inline_kb_answer_callback_handler(query: types.CallbackQuery):
|
||||||
await query.answer() # send answer to close the rounding circle
|
|
||||||
|
|
||||||
answer_data = query.data
|
answer_data = query.data
|
||||||
logger.debug(f"answer_data={answer_data}")
|
# always answer callback queries, even if you have nothing to say
|
||||||
# here we can work with query.data
|
await query.answer(f'You answered with {answer_data!r}')
|
||||||
|
|
||||||
if answer_data == 'yes':
|
if answer_data == 'yes':
|
||||||
await bot.send_message(query.from_user.id, "That's great!")
|
text = 'Great, me too!'
|
||||||
elif answer_data == 'no':
|
elif answer_data == 'no':
|
||||||
await bot.send_message(query.from_user.id, "Oh no...Why so?")
|
text = 'Oh no...Why so?'
|
||||||
else:
|
else:
|
||||||
await bot.send_message(query.from_user.id, "Invalid callback data!")
|
text = f'Unexpected callback data {answer_data!r}!'
|
||||||
|
|
||||||
|
await bot.send_message(query.from_user.id, text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,26 @@
|
||||||
# Translations template for PROJECT.
|
# Translations template for PROJECT.
|
||||||
# Copyright (C) 2018 ORGANIZATION
|
# Copyright (C) 2019 ORGANIZATION
|
||||||
# This file is distributed under the same license as the PROJECT project.
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2018-06-30 03:50+0300\n"
|
"POT-Creation-Date: 2019-08-10 17:51+0300\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.6.0\n"
|
"Generated-By: Babel 2.7.0\n"
|
||||||
|
|
||||||
#: i18n_example.py:48
|
#: i18n_example.py:60
|
||||||
msgid "Hello, <b>{user}</b>!"
|
msgid "Hello, <b>{user}</b>!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: i18n_example.py:53
|
#: i18n_example.py:67
|
||||||
msgid "Your current language: <i>{language}</i>"
|
msgid "Your current language: <i>{language}</i>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Aiogram has {number} like!"
|
|
||||||
msgid_plural "Aiogram has {number} likes!"
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
# Russian translations for PROJECT.
|
# Russian translations for PROJECT.
|
||||||
# Copyright (C) 2018 ORGANIZATION
|
# Copyright (C) 2019 ORGANIZATION
|
||||||
# This file is distributed under the same license as the PROJECT project.
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2018-06-30 03:50+0300\n"
|
"POT-Creation-Date: 2019-08-10 17:51+0300\n"
|
||||||
"PO-Revision-Date: 2018-06-30 03:43+0300\n"
|
"PO-Revision-Date: 2019-08-10 17:52+0300\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"Language-Team: ru <LL@li.org>\n"
|
"Language-Team: ru <LL@li.org>\n"
|
||||||
|
|
@ -17,18 +17,19 @@ msgstr ""
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.6.0\n"
|
"Generated-By: Babel 2.7.0\n"
|
||||||
|
|
||||||
#: i18n_example.py:48
|
#: i18n_example.py:60
|
||||||
msgid "Hello, <b>{user}</b>!"
|
msgid "Hello, <b>{user}</b>!"
|
||||||
msgstr "Привет, <b>{user}</b>!"
|
msgstr "Привет, <b>{user}</b>!"
|
||||||
|
|
||||||
#: i18n_example.py:53
|
#: i18n_example.py:67
|
||||||
msgid "Your current language: <i>{language}</i>"
|
msgid "Your current language: <i>{language}</i>"
|
||||||
msgstr "Твой язык: <i>{language}</i>"
|
msgstr "Твой язык: <i>{language}</i>"
|
||||||
|
|
||||||
|
#: i18n_example.py:95
|
||||||
msgid "Aiogram has {number} like!"
|
msgid "Aiogram has {number} like!"
|
||||||
msgid_plural "Aiogram has {number} likes!"
|
msgid_plural "Aiogram has {number} likes!"
|
||||||
msgstr[0] "Aiogram имеет {number} лайк!"
|
msgstr[0] "Aiogram имеет {number} лайк!"
|
||||||
msgstr[1] "Aiogram имеет {number} лайка!"
|
msgstr[1] "Aiogram имеет {number} лайка!"
|
||||||
msgstr[2] "Aiogram имеет {number} лайков!"
|
msgstr[2] "Aiogram имеет {number} лайков!"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import asyncio
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, filters, types
|
from aiogram import Bot, Dispatcher, executor, filters, types
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
|
||||||
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
@ -13,10 +14,10 @@ async def send_welcome(message: types.Message):
|
||||||
# So... At first I want to send something like this:
|
# So... At first I want to send something like this:
|
||||||
await message.reply("Do you want to see many pussies? Are you ready?")
|
await message.reply("Do you want to see many pussies? Are you ready?")
|
||||||
|
|
||||||
# And wait few seconds...
|
# Wait a little...
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
# Good bots should send chat actions. Or not.
|
# Good bots should send chat actions...
|
||||||
await types.ChatActions.upload_photo()
|
await types.ChatActions.upload_photo()
|
||||||
|
|
||||||
# Create media group
|
# Create media group
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from aiogram.dispatcher.handler import CancelHandler, current_handler
|
||||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||||
from aiogram.utils.exceptions import Throttled
|
from aiogram.utils.exceptions import Throttled
|
||||||
|
|
||||||
TOKEN = 'BOT TOKEN HERE'
|
TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
|
||||||
# In this example Redis storage is used
|
# In this example Redis storage is used
|
||||||
storage = RedisStorage2(db=5)
|
storage = RedisStorage2(db=5)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from aiogram.dispatcher import Dispatcher
|
from aiogram.dispatcher import Dispatcher
|
||||||
from aiogram.types.message import ContentTypes
|
from aiogram.types.message import ContentTypes
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
|
|
||||||
BOT_TOKEN = 'BOT TOKEN HERE'
|
|
||||||
PAYMENTS_PROVIDER_TOKEN = '123456789:TEST:1234567890abcdef1234567890abcdef'
|
BOT_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
PAYMENTS_PROVIDER_TOKEN = '123456789:TEST:1422'
|
||||||
|
|
||||||
bot = Bot(BOT_TOKEN)
|
bot = Bot(BOT_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
@ -15,13 +14,13 @@ dp = Dispatcher(bot)
|
||||||
# Setup prices
|
# Setup prices
|
||||||
prices = [
|
prices = [
|
||||||
types.LabeledPrice(label='Working Time Machine', amount=5750),
|
types.LabeledPrice(label='Working Time Machine', amount=5750),
|
||||||
types.LabeledPrice(label='Gift wrapping', amount=500)
|
types.LabeledPrice(label='Gift wrapping', amount=500),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Setup shipping options
|
# Setup shipping options
|
||||||
shipping_options = [
|
shipping_options = [
|
||||||
types.ShippingOption(id='instant', title='WorldWide Teleporter').add(types.LabeledPrice('Teleporter', 1000)),
|
types.ShippingOption(id='instant', title='WorldWide Teleporter').add(types.LabeledPrice('Teleporter', 1000)),
|
||||||
types.ShippingOption(id='pickup', title='Local pickup').add(types.LabeledPrice('Pickup', 300))
|
types.ShippingOption(id='pickup', title='Local pickup').add(types.LabeledPrice('Pickup', 300)),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -59,7 +58,7 @@ async def cmd_buy(message: types.Message):
|
||||||
' Order our Working Time Machine today!',
|
' Order our Working Time Machine today!',
|
||||||
provider_token=PAYMENTS_PROVIDER_TOKEN,
|
provider_token=PAYMENTS_PROVIDER_TOKEN,
|
||||||
currency='usd',
|
currency='usd',
|
||||||
photo_url='https://images.fineartamerica.com/images-medium-large/2-the-time-machine-dmitriy-khristenko.jpg',
|
photo_url='https://telegra.ph/file/d08ff863531f10bf2ea4b.jpg',
|
||||||
photo_height=512, # !=0/None or picture won't be shown
|
photo_height=512, # !=0/None or picture won't be shown
|
||||||
photo_width=512,
|
photo_width=512,
|
||||||
photo_size=512,
|
photo_size=512,
|
||||||
|
|
@ -69,14 +68,14 @@ async def cmd_buy(message: types.Message):
|
||||||
payload='HAPPY FRIDAYS COUPON')
|
payload='HAPPY FRIDAYS COUPON')
|
||||||
|
|
||||||
|
|
||||||
@dp.shipping_query_handler(func=lambda query: True)
|
@dp.shipping_query_handler(lambda query: True)
|
||||||
async def shipping(shipping_query: types.ShippingQuery):
|
async def shipping(shipping_query: types.ShippingQuery):
|
||||||
await bot.answer_shipping_query(shipping_query.id, ok=True, shipping_options=shipping_options,
|
await bot.answer_shipping_query(shipping_query.id, ok=True, shipping_options=shipping_options,
|
||||||
error_message='Oh, seems like our Dog couriers are having a lunch right now.'
|
error_message='Oh, seems like our Dog couriers are having a lunch right now.'
|
||||||
' Try again later!')
|
' Try again later!')
|
||||||
|
|
||||||
|
|
||||||
@dp.pre_checkout_query_handler(func=lambda query: True)
|
@dp.pre_checkout_query_handler(lambda query: True)
|
||||||
async def checkout(pre_checkout_query: types.PreCheckoutQuery):
|
async def checkout(pre_checkout_query: types.PreCheckoutQuery):
|
||||||
await bot.answer_pre_checkout_query(pre_checkout_query.id, ok=True,
|
await bot.answer_pre_checkout_query(pre_checkout_query.id, ok=True,
|
||||||
error_message="Aliens tried to steal your card's CVV,"
|
error_message="Aliens tried to steal your card's CVV,"
|
||||||
|
|
@ -95,4 +94,4 @@ async def got_payment(message: types.Message):
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
executor.start_polling(dp)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
@ -11,13 +10,13 @@ from aiogram.utils.executor import start_polling
|
||||||
from aiogram.utils.markdown import bold, code, italic, text
|
from aiogram.utils.markdown import bold, code, italic, text
|
||||||
|
|
||||||
# Configure bot here
|
# Configure bot here
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
PROXY_URL = 'http://PROXY_URL' # Or 'socks5://...'
|
PROXY_URL = 'http://PROXY_URL' # Or 'socks5://host:port'
|
||||||
|
|
||||||
# If authentication is required in your proxy then uncomment next line and change login/password for it
|
# NOTE: If authentication is required in your proxy then uncomment next line and change login/password for it
|
||||||
# PROXY_AUTH = aiohttp.BasicAuth(login='login', password='password')
|
# PROXY_AUTH = aiohttp.BasicAuth(login='login', password='password')
|
||||||
# And add `proxy_auth=PROXY_AUTH` argument in line 25, like this:
|
# And add `proxy_auth=PROXY_AUTH` argument in line 30, like this:
|
||||||
# >>> bot = Bot(token=API_TOKEN, loop=loop, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
|
# >>> bot = Bot(token=API_TOKEN, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
|
||||||
# Also you can use Socks5 proxy but you need manually install aiohttp_socks package.
|
# Also you can use Socks5 proxy but you need manually install aiohttp_socks package.
|
||||||
|
|
||||||
# Get my ip URL
|
# Get my ip URL
|
||||||
|
|
@ -26,26 +25,32 @@ GET_IP_URL = 'http://bot.whatismyipaddress.com/'
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN, proxy=PROXY_URL)
|
bot = Bot(token=API_TOKEN, proxy=PROXY_URL)
|
||||||
|
|
||||||
|
# If auth is required:
|
||||||
|
# bot = Bot(token=API_TOKEN, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
async def fetch(url, proxy=None, proxy_auth=None):
|
async def fetch(url, session):
|
||||||
async with aiohttp.ClientSession() as session:
|
async with session.get(url) as response:
|
||||||
async with session.get(url, proxy=proxy, proxy_auth=proxy_auth) as response:
|
return await response.text()
|
||||||
return await response.text()
|
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
|
# fetching urls will take some time, so notify user that everything is OK
|
||||||
|
await types.ChatActions.typing()
|
||||||
|
|
||||||
content = []
|
content = []
|
||||||
|
|
||||||
# Make request (without proxy)
|
# Make request (without proxy)
|
||||||
ip = await fetch(GET_IP_URL)
|
async with aiohttp.ClientSession() as session:
|
||||||
|
ip = await fetch(GET_IP_URL, session)
|
||||||
content.append(text(':globe_showing_Americas:', bold('IP:'), code(ip)))
|
content.append(text(':globe_showing_Americas:', bold('IP:'), code(ip)))
|
||||||
# This line is formatted to '🌎 *IP:* `YOUR IP`'
|
# This line is formatted to '🌎 *IP:* `YOUR IP`'
|
||||||
|
|
||||||
# Make request through proxy
|
# Make request through bot's proxy
|
||||||
ip = await fetch(GET_IP_URL, bot.proxy, bot.proxy_auth)
|
ip = await fetch(GET_IP_URL, bot.session)
|
||||||
content.append(text(':locked_with_key:', bold('IP:'), code(ip), italic('via proxy')))
|
content.append(text(':locked_with_key:', bold('IP:'), code(ip), italic('via proxy')))
|
||||||
# This line is formatted to '🔐 *IP:* `YOUR IP` _via proxy_'
|
# This line is formatted to '🔐 *IP:* `YOUR IP` _via proxy_'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,28 @@ from aiogram import Bot, types
|
||||||
from aiogram.dispatcher import Dispatcher, filters
|
from aiogram.dispatcher import Dispatcher, filters
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
|
|
||||||
bot = Bot(token='TOKEN')
|
|
||||||
|
bot = Bot(token='BOT_TOKEN_HERE', parse_mode=types.ParseMode.HTML)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=['item_([0-9]*)']))
|
@dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=['item_([0-9]*)']))
|
||||||
async def send_welcome(message: types.Message, regexp_command):
|
async def send_welcome(message: types.Message, regexp_command):
|
||||||
await message.reply("You have requested an item with number: {}".format(regexp_command.group(1)))
|
await message.reply(f"You have requested an item with id <code>{regexp_command.group(1)}</code>")
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(commands='start')
|
||||||
|
async def create_deeplink(message: types.Message):
|
||||||
|
bot_user = await bot.me
|
||||||
|
bot_username = bot_user.username
|
||||||
|
deeplink = f'https://t.me/{bot_username}?start=item_12345'
|
||||||
|
text = (
|
||||||
|
f'Either send a command /item_1234 or follow this link {deeplink} and then click start\n'
|
||||||
|
'It also can be hidden in a inline button\n\n'
|
||||||
|
'Or just send <code>/start item_123</code>'
|
||||||
|
)
|
||||||
|
await message.reply(text, disable_web_page_preview=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
executor.start_polling(dp)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, types
|
from aiogram import Bot, Dispatcher, executor, types
|
||||||
|
|
||||||
|
|
||||||
API_TOKEN = 'BOT_TOKEN_HERE'
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
|
|
@ -18,24 +19,27 @@ bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands='start')
|
||||||
async def start_cmd_handler(message: types.Message):
|
async def start_cmd_handler(message: types.Message):
|
||||||
keyboard_markup = types.ReplyKeyboardMarkup(row_width=3)
|
keyboard_markup = types.ReplyKeyboardMarkup(row_width=3)
|
||||||
# default row_width is 3, so here we can omit it actually
|
# default row_width is 3, so here we can omit it actually
|
||||||
# kept for clearness
|
# kept for clearness
|
||||||
|
|
||||||
keyboard_markup.row(types.KeyboardButton("Yes!"),
|
btns_text = ('Yes!', 'No!')
|
||||||
types.KeyboardButton("No!"))
|
keyboard_markup.row(*(types.KeyboardButton(text) for text in btns_text))
|
||||||
# adds buttons as a new row to the existing keyboard
|
# adds buttons as a new row to the existing keyboard
|
||||||
# the behaviour doesn't depend on row_width attribute
|
# the behaviour doesn't depend on row_width attribute
|
||||||
|
|
||||||
keyboard_markup.add(types.KeyboardButton("I don't know"),
|
more_btns_text = (
|
||||||
types.KeyboardButton("Who am i?"),
|
"I don't know",
|
||||||
types.KeyboardButton("Where am i?"),
|
"Who am i?",
|
||||||
types.KeyboardButton("Who is there?"))
|
"Where am i?",
|
||||||
# adds buttons. New rows is formed according to row_width parameter
|
"Who is there?",
|
||||||
|
)
|
||||||
|
keyboard_markup.add(*(types.KeyboardButton(text) for text in more_btns_text))
|
||||||
|
# adds buttons. New rows are formed according to row_width parameter
|
||||||
|
|
||||||
await message.reply("Hi!\nDo you love aiogram?", reply_markup=keyboard_markup)
|
await message.reply("Hi!\nDo you like aiogram?", reply_markup=keyboard_markup)
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler()
|
@dp.message_handler()
|
||||||
|
|
@ -45,15 +49,17 @@ async def all_msg_handler(message: types.Message):
|
||||||
# in real bot, it's better to define message_handler(text="...") for each button
|
# in real bot, it's better to define message_handler(text="...") for each button
|
||||||
# but here for the simplicity only one handler is defined
|
# but here for the simplicity only one handler is defined
|
||||||
|
|
||||||
text_of_button = message.text
|
button_text = message.text
|
||||||
logger.debug(text_of_button) # print the text we got
|
logger.debug('The answer is %r', button_text) # print the text we've got
|
||||||
|
|
||||||
if text_of_button == 'Yes!':
|
if button_text == 'Yes!':
|
||||||
await message.reply("That's great", reply_markup=types.ReplyKeyboardRemove())
|
reply_text = "That's great"
|
||||||
elif text_of_button == 'No!':
|
elif button_text == 'No!':
|
||||||
await message.reply("Oh no! Why?", reply_markup=types.ReplyKeyboardRemove())
|
reply_text = "Oh no! Why?"
|
||||||
else:
|
else:
|
||||||
await message.reply("Keep calm...Everything is fine", reply_markup=types.ReplyKeyboardRemove())
|
reply_text = "Keep calm...Everything is fine"
|
||||||
|
|
||||||
|
await message.reply(reply_text, reply_markup=types.ReplyKeyboardRemove())
|
||||||
# with message, we send types.ReplyKeyboardRemove() to hide the keyboard
|
# with message, we send types.ReplyKeyboardRemove() to hide the keyboard
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, executor, types
|
from aiogram import Bot, Dispatcher, executor, types
|
||||||
|
|
||||||
API_TOKEN = 'API_TOKEN_HERE'
|
|
||||||
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -16,10 +17,12 @@ logging.basicConfig(level=logging.INFO)
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
|
|
||||||
|
|
||||||
# if the text from user in the list
|
# if the text from user in the list
|
||||||
@dp.message_handler(text=['text1', 'text2'])
|
@dp.message_handler(text=['text1', 'text2'])
|
||||||
async def text_in_handler(message: types.Message):
|
async def text_in_handler(message: types.Message):
|
||||||
await message.answer("The message text is in the list!")
|
await message.answer("The message text equals to one of in the list!")
|
||||||
|
|
||||||
|
|
||||||
# if the text contains any string
|
# if the text contains any string
|
||||||
@dp.message_handler(text_contains='example1')
|
@dp.message_handler(text_contains='example1')
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ Example for throttling manager.
|
||||||
You can use that for flood controlling.
|
You can use that for flood controlling.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiogram import Bot, types
|
from aiogram import Bot, types
|
||||||
|
|
@ -13,14 +12,15 @@ from aiogram.dispatcher import Dispatcher
|
||||||
from aiogram.utils.exceptions import Throttled
|
from aiogram.utils.exceptions import Throttled
|
||||||
from aiogram.utils.executor import start_polling
|
from aiogram.utils.executor import start_polling
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
|
||||||
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
|
|
||||||
# Throttling manager does not work without Leaky Bucket.
|
# Throttling manager does not work without Leaky Bucket.
|
||||||
# Then need to use storages. For example use simple in-memory storage.
|
# You need to use a storage. For example use simple in-memory storage.
|
||||||
storage = MemoryStorage()
|
storage = MemoryStorage()
|
||||||
dp = Dispatcher(bot, storage=storage)
|
dp = Dispatcher(bot, storage=storage)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,176 +1,66 @@
|
||||||
"""
|
import logging
|
||||||
Example outdated
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import ssl
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
|
|
||||||
import aiogram
|
|
||||||
from aiogram import Bot, types
|
from aiogram import Bot, types
|
||||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.dispatcher import Dispatcher
|
from aiogram.dispatcher import Dispatcher
|
||||||
from aiogram.dispatcher.webhook import get_new_configured_app, SendMessage
|
from aiogram.dispatcher.webhook import SendMessage
|
||||||
from aiogram.types import ChatType, ParseMode, ContentTypes
|
from aiogram.utils.executor import start_webhook
|
||||||
from aiogram.utils.markdown import hbold, bold, text, link
|
|
||||||
|
|
||||||
TOKEN = 'BOT TOKEN HERE'
|
|
||||||
|
|
||||||
WEBHOOK_HOST = 'example.com' # Domain name or IP addres which your bot is located.
|
API_TOKEN = 'BOT_TOKEN_HERE'
|
||||||
WEBHOOK_PORT = 443 # Telegram Bot API allows only for usage next ports: 443, 80, 88 or 8443
|
|
||||||
WEBHOOK_URL_PATH = '/webhook' # Part of URL
|
|
||||||
|
|
||||||
# This options needed if you use self-signed SSL certificate
|
# webhook settings
|
||||||
# Instructions: https://core.telegram.org/bots/self-signed
|
WEBHOOK_HOST = 'https://your.domain'
|
||||||
WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate
|
WEBHOOK_PATH = '/path/to/api'
|
||||||
WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
|
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
|
||||||
|
|
||||||
WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}"
|
# webserver settings
|
||||||
|
WEBAPP_HOST = 'localhost' # or ip
|
||||||
# Web app settings:
|
|
||||||
# Use LAN address to listen webhooks
|
|
||||||
# User any available port in range from 1024 to 49151 if you're using proxy, or WEBHOOK_PORT if you're using direct webhook handling
|
|
||||||
WEBAPP_HOST = 'localhost'
|
|
||||||
WEBAPP_PORT = 3001
|
WEBAPP_PORT = 3001
|
||||||
|
|
||||||
BAD_CONTENT = ContentTypes.PHOTO & ContentTypes.DOCUMENT & ContentTypes.STICKER & ContentTypes.AUDIO
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
storage = MemoryStorage()
|
dp = Dispatcher(bot)
|
||||||
dp = Dispatcher(bot, storage=storage)
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
|
|
||||||
async def cmd_start(message: types.Message):
|
@dp.message_handler()
|
||||||
# Yep. aiogram allows to respond into webhook.
|
async def echo(message: types.Message):
|
||||||
# https://core.telegram.org/bots/api#making-requests-when-getting-updates
|
# Regular request
|
||||||
return SendMessage(chat_id=message.chat.id, text='Hi from webhook!',
|
# await bot.send_message(message.chat.id, message.text)
|
||||||
reply_to_message_id=message.message_id)
|
|
||||||
|
# or reply INTO webhook
|
||||||
|
return SendMessage(message.chat.id, message.text)
|
||||||
|
|
||||||
|
|
||||||
async def cmd_about(message: types.Message):
|
async def on_startup(dp):
|
||||||
# In this function markdown utils are userd for formatting message text
|
await bot.set_webhook(WEBHOOK_URL)
|
||||||
return SendMessage(message.chat.id, text(
|
# insert code here to run it after start
|
||||||
bold('Hi! I\'m just a simple telegram bot.'),
|
|
||||||
'',
|
|
||||||
text('I\'m powered by', bold('Python', Version(*sys.version_info[:]))),
|
|
||||||
text('With', link(text('aiogram', aiogram.VERSION), 'https://github.com/aiogram/aiogram')),
|
|
||||||
sep='\n'
|
|
||||||
), parse_mode=ParseMode.MARKDOWN)
|
|
||||||
|
|
||||||
|
|
||||||
async def cancel(message: types.Message):
|
async def on_shutdown(dp):
|
||||||
# Get current state context
|
logging.warning('Shutting down..')
|
||||||
state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
|
|
||||||
|
|
||||||
# If current user in any state - cancel it.
|
# insert code here to run it before shutdown
|
||||||
if await state.get_state() is not None:
|
|
||||||
await state.set_state(state=None)
|
|
||||||
return SendMessage(message.chat.id, 'Current action is canceled.')
|
|
||||||
# Otherwise do nothing
|
|
||||||
|
|
||||||
|
# Remove webhook (not acceptable in some cases)
|
||||||
async def unknown(message: types.Message):
|
|
||||||
"""
|
|
||||||
Handler for unknown messages.
|
|
||||||
"""
|
|
||||||
return SendMessage(message.chat.id,
|
|
||||||
f"I don\'t know what to do with content type `{message.content_type()}`. Sorry :c")
|
|
||||||
|
|
||||||
|
|
||||||
async def cmd_id(message: types.Message):
|
|
||||||
"""
|
|
||||||
Return info about user.
|
|
||||||
"""
|
|
||||||
if message.reply_to_message:
|
|
||||||
target = message.reply_to_message.from_user
|
|
||||||
chat = message.chat
|
|
||||||
elif message.forward_from and message.chat.type == ChatType.PRIVATE:
|
|
||||||
target = message.forward_from
|
|
||||||
chat = message.forward_from or message.chat
|
|
||||||
else:
|
|
||||||
target = message.from_user
|
|
||||||
chat = message.chat
|
|
||||||
|
|
||||||
result_msg = [hbold('Info about user:'),
|
|
||||||
f"First name: {target.first_name}"]
|
|
||||||
if target.last_name:
|
|
||||||
result_msg.append(f"Last name: {target.last_name}")
|
|
||||||
if target.username:
|
|
||||||
result_msg.append(f"Username: {target.mention}")
|
|
||||||
result_msg.append(f"User ID: {target.id}")
|
|
||||||
|
|
||||||
result_msg.extend([hbold('Chat:'),
|
|
||||||
f"Type: {chat.type}",
|
|
||||||
f"Chat ID: {chat.id}"])
|
|
||||||
if chat.type != ChatType.PRIVATE:
|
|
||||||
result_msg.append(f"Title: {chat.title}")
|
|
||||||
else:
|
|
||||||
result_msg.append(f"Title: {chat.full_name}")
|
|
||||||
return SendMessage(message.chat.id, '\n'.join(result_msg), reply_to_message_id=message.message_id,
|
|
||||||
parse_mode=ParseMode.HTML)
|
|
||||||
|
|
||||||
|
|
||||||
async def on_startup(app):
|
|
||||||
# Demonstrate one of the available methods for registering handlers
|
|
||||||
# This command available only in main state (state=None)
|
|
||||||
dp.register_message_handler(cmd_start, commands=['start'])
|
|
||||||
|
|
||||||
# This handler is available in all states at any time.
|
|
||||||
dp.register_message_handler(cmd_about, commands=['help', 'about'], state='*')
|
|
||||||
dp.register_message_handler(unknown, content_types=BAD_CONTENT,
|
|
||||||
func=lambda message: message.chat.type == ChatType.PRIVATE)
|
|
||||||
|
|
||||||
# You are able to register one function handler for multiple conditions
|
|
||||||
dp.register_message_handler(cancel, commands=['cancel'], state='*')
|
|
||||||
dp.register_message_handler(cancel, func=lambda message: message.text.lower().strip() in ['cancel'], state='*')
|
|
||||||
|
|
||||||
dp.register_message_handler(cmd_id, commands=['id'], state='*')
|
|
||||||
dp.register_message_handler(cmd_id, func=lambda message: message.forward_from or
|
|
||||||
message.reply_to_message and
|
|
||||||
message.chat.type == ChatType.PRIVATE, state='*')
|
|
||||||
|
|
||||||
# Get current webhook status
|
|
||||||
webhook = await bot.get_webhook_info()
|
|
||||||
|
|
||||||
# If URL is bad
|
|
||||||
if webhook.url != WEBHOOK_URL:
|
|
||||||
# If URL doesnt match current - remove webhook
|
|
||||||
if not webhook.url:
|
|
||||||
await bot.delete_webhook()
|
|
||||||
|
|
||||||
# Set new URL for webhook
|
|
||||||
await bot.set_webhook(WEBHOOK_URL, certificate=open(WEBHOOK_SSL_CERT, 'rb'))
|
|
||||||
# If you want to use free certificate signed by LetsEncrypt you need to set only URL without sending certificate.
|
|
||||||
|
|
||||||
|
|
||||||
async def on_shutdown(app):
|
|
||||||
"""
|
|
||||||
Graceful shutdown. This method is recommended by aiohttp docs.
|
|
||||||
"""
|
|
||||||
# Remove webhook.
|
|
||||||
await bot.delete_webhook()
|
await bot.delete_webhook()
|
||||||
|
|
||||||
# Close Redis connection.
|
# Close DB connection (if used)
|
||||||
await dp.storage.close()
|
await dp.storage.close()
|
||||||
await dp.storage.wait_closed()
|
await dp.storage.wait_closed()
|
||||||
|
|
||||||
|
logging.warning('Bye!')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Get instance of :class:`aiohttp.web.Application` with configured router.
|
start_webhook(
|
||||||
app = get_new_configured_app(dispatcher=dp, path=WEBHOOK_URL_PATH)
|
dispatcher=dp,
|
||||||
|
webhook_path=WEBHOOK_PATH,
|
||||||
# Setup event handlers.
|
on_startup=on_startup,
|
||||||
app.on_startup.append(on_startup)
|
on_shutdown=on_shutdown,
|
||||||
app.on_shutdown.append(on_shutdown)
|
skip_updates=True,
|
||||||
|
host=WEBAPP_HOST,
|
||||||
# Generate SSL context
|
port=WEBAPP_PORT,
|
||||||
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
)
|
||||||
context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV)
|
|
||||||
|
|
||||||
# Start web-application.
|
|
||||||
web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT, ssl_context=context)
|
|
||||||
# Note:
|
|
||||||
# If you start your bot using nginx or Apache web server, SSL context is not required.
|
|
||||||
# Otherwise you need to set `ssl_context` parameter.
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from aiogram import Bot, types
|
|
||||||
from aiogram.dispatcher import Dispatcher
|
|
||||||
from aiogram.utils.executor import start_webhook
|
|
||||||
|
|
||||||
API_TOKEN = 'BOT TOKEN HERE'
|
|
||||||
|
|
||||||
# webhook settings
|
|
||||||
WEBHOOK_HOST = 'https://your.domain'
|
|
||||||
WEBHOOK_PATH = '/path/to/api'
|
|
||||||
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
|
|
||||||
|
|
||||||
# webserver settings
|
|
||||||
WEBAPP_HOST = 'localhost' # or ip
|
|
||||||
WEBAPP_PORT = 3001
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
|
||||||
dp = Dispatcher(bot)
|
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler()
|
|
||||||
async def echo(message: types.Message):
|
|
||||||
await bot.send_message(message.chat.id, message.text)
|
|
||||||
|
|
||||||
|
|
||||||
async def on_startup(dp):
|
|
||||||
await bot.set_webhook(WEBHOOK_URL)
|
|
||||||
# insert code here to run it after start
|
|
||||||
|
|
||||||
|
|
||||||
async def on_shutdown(dp):
|
|
||||||
# insert code here to run it before shutdown
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
start_webhook(dispatcher=dp, webhook_path=WEBHOOK_PATH, on_startup=on_startup, on_shutdown=on_shutdown,
|
|
||||||
skip_updates=True, host=WEBAPP_HOST, port=WEBAPP_PORT)
|
|
||||||
176
examples/webhook_example_old.py
Normal file
176
examples/webhook_example_old.py
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
"""
|
||||||
|
Example outdated
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
import aiogram
|
||||||
|
from aiogram import Bot, types
|
||||||
|
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||||
|
from aiogram.dispatcher import Dispatcher
|
||||||
|
from aiogram.dispatcher.webhook import get_new_configured_app, SendMessage
|
||||||
|
from aiogram.types import ChatType, ParseMode, ContentTypes
|
||||||
|
from aiogram.utils.markdown import hbold, bold, text, link
|
||||||
|
|
||||||
|
TOKEN = 'BOT TOKEN HERE'
|
||||||
|
|
||||||
|
WEBHOOK_HOST = 'example.com' # Domain name or IP addres which your bot is located.
|
||||||
|
WEBHOOK_PORT = 443 # Telegram Bot API allows only for usage next ports: 443, 80, 88 or 8443
|
||||||
|
WEBHOOK_URL_PATH = '/webhook' # Part of URL
|
||||||
|
|
||||||
|
# This options needed if you use self-signed SSL certificate
|
||||||
|
# Instructions: https://core.telegram.org/bots/self-signed
|
||||||
|
WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate
|
||||||
|
WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
|
||||||
|
|
||||||
|
WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}"
|
||||||
|
|
||||||
|
# Web app settings:
|
||||||
|
# Use LAN address to listen webhooks
|
||||||
|
# User any available port in range from 1024 to 49151 if you're using proxy, or WEBHOOK_PORT if you're using direct webhook handling
|
||||||
|
WEBAPP_HOST = 'localhost'
|
||||||
|
WEBAPP_PORT = 3001
|
||||||
|
|
||||||
|
BAD_CONTENT = ContentTypes.PHOTO & ContentTypes.DOCUMENT & ContentTypes.STICKER & ContentTypes.AUDIO
|
||||||
|
|
||||||
|
bot = Bot(TOKEN)
|
||||||
|
storage = MemoryStorage()
|
||||||
|
dp = Dispatcher(bot, storage=storage)
|
||||||
|
|
||||||
|
|
||||||
|
async def cmd_start(message: types.Message):
|
||||||
|
# Yep. aiogram allows to respond into webhook.
|
||||||
|
# https://core.telegram.org/bots/api#making-requests-when-getting-updates
|
||||||
|
return SendMessage(chat_id=message.chat.id, text='Hi from webhook!',
|
||||||
|
reply_to_message_id=message.message_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def cmd_about(message: types.Message):
|
||||||
|
# In this function markdown utils are userd for formatting message text
|
||||||
|
return SendMessage(message.chat.id, text(
|
||||||
|
bold('Hi! I\'m just a simple telegram bot.'),
|
||||||
|
'',
|
||||||
|
text('I\'m powered by', bold('Python', Version(*sys.version_info[:]))),
|
||||||
|
text('With', link(text('aiogram', aiogram.VERSION), 'https://github.com/aiogram/aiogram')),
|
||||||
|
sep='\n'
|
||||||
|
), parse_mode=ParseMode.MARKDOWN)
|
||||||
|
|
||||||
|
|
||||||
|
async def cancel(message: types.Message):
|
||||||
|
# Get current state context
|
||||||
|
state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
|
||||||
|
|
||||||
|
# If current user in any state - cancel it.
|
||||||
|
if await state.get_state() is not None:
|
||||||
|
await state.set_state(state=None)
|
||||||
|
return SendMessage(message.chat.id, 'Current action is canceled.')
|
||||||
|
# Otherwise do nothing
|
||||||
|
|
||||||
|
|
||||||
|
async def unknown(message: types.Message):
|
||||||
|
"""
|
||||||
|
Handler for unknown messages.
|
||||||
|
"""
|
||||||
|
return SendMessage(message.chat.id,
|
||||||
|
f"I don\'t know what to do with content type `{message.content_type()}`. Sorry :c")
|
||||||
|
|
||||||
|
|
||||||
|
async def cmd_id(message: types.Message):
|
||||||
|
"""
|
||||||
|
Return info about user.
|
||||||
|
"""
|
||||||
|
if message.reply_to_message:
|
||||||
|
target = message.reply_to_message.from_user
|
||||||
|
chat = message.chat
|
||||||
|
elif message.forward_from and message.chat.type == ChatType.PRIVATE:
|
||||||
|
target = message.forward_from
|
||||||
|
chat = message.forward_from or message.chat
|
||||||
|
else:
|
||||||
|
target = message.from_user
|
||||||
|
chat = message.chat
|
||||||
|
|
||||||
|
result_msg = [hbold('Info about user:'),
|
||||||
|
f"First name: {target.first_name}"]
|
||||||
|
if target.last_name:
|
||||||
|
result_msg.append(f"Last name: {target.last_name}")
|
||||||
|
if target.username:
|
||||||
|
result_msg.append(f"Username: {target.mention}")
|
||||||
|
result_msg.append(f"User ID: {target.id}")
|
||||||
|
|
||||||
|
result_msg.extend([hbold('Chat:'),
|
||||||
|
f"Type: {chat.type}",
|
||||||
|
f"Chat ID: {chat.id}"])
|
||||||
|
if chat.type != ChatType.PRIVATE:
|
||||||
|
result_msg.append(f"Title: {chat.title}")
|
||||||
|
else:
|
||||||
|
result_msg.append(f"Title: {chat.full_name}")
|
||||||
|
return SendMessage(message.chat.id, '\n'.join(result_msg), reply_to_message_id=message.message_id,
|
||||||
|
parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_startup(app):
|
||||||
|
# Demonstrate one of the available methods for registering handlers
|
||||||
|
# This command available only in main state (state=None)
|
||||||
|
dp.register_message_handler(cmd_start, commands=['start'])
|
||||||
|
|
||||||
|
# This handler is available in all states at any time.
|
||||||
|
dp.register_message_handler(cmd_about, commands=['help', 'about'], state='*')
|
||||||
|
dp.register_message_handler(unknown, content_types=BAD_CONTENT,
|
||||||
|
func=lambda message: message.chat.type == ChatType.PRIVATE)
|
||||||
|
|
||||||
|
# You are able to register one function handler for multiple conditions
|
||||||
|
dp.register_message_handler(cancel, commands=['cancel'], state='*')
|
||||||
|
dp.register_message_handler(cancel, func=lambda message: message.text.lower().strip() in ['cancel'], state='*')
|
||||||
|
|
||||||
|
dp.register_message_handler(cmd_id, commands=['id'], state='*')
|
||||||
|
dp.register_message_handler(cmd_id, func=lambda message: message.forward_from or
|
||||||
|
message.reply_to_message and
|
||||||
|
message.chat.type == ChatType.PRIVATE, state='*')
|
||||||
|
|
||||||
|
# Get current webhook status
|
||||||
|
webhook = await bot.get_webhook_info()
|
||||||
|
|
||||||
|
# If URL is bad
|
||||||
|
if webhook.url != WEBHOOK_URL:
|
||||||
|
# If URL doesnt match current - remove webhook
|
||||||
|
if not webhook.url:
|
||||||
|
await bot.delete_webhook()
|
||||||
|
|
||||||
|
# Set new URL for webhook
|
||||||
|
await bot.set_webhook(WEBHOOK_URL, certificate=open(WEBHOOK_SSL_CERT, 'rb'))
|
||||||
|
# If you want to use free certificate signed by LetsEncrypt you need to set only URL without sending certificate.
|
||||||
|
|
||||||
|
|
||||||
|
async def on_shutdown(app):
|
||||||
|
"""
|
||||||
|
Graceful shutdown. This method is recommended by aiohttp docs.
|
||||||
|
"""
|
||||||
|
# Remove webhook.
|
||||||
|
await bot.delete_webhook()
|
||||||
|
|
||||||
|
# Close Redis connection.
|
||||||
|
await dp.storage.close()
|
||||||
|
await dp.storage.wait_closed()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Get instance of :class:`aiohttp.web.Application` with configured router.
|
||||||
|
app = get_new_configured_app(dispatcher=dp, path=WEBHOOK_URL_PATH)
|
||||||
|
|
||||||
|
# Setup event handlers.
|
||||||
|
app.on_startup.append(on_startup)
|
||||||
|
app.on_shutdown.append(on_shutdown)
|
||||||
|
|
||||||
|
# Generate SSL context
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||||
|
context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV)
|
||||||
|
|
||||||
|
# Start web-application.
|
||||||
|
web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT, ssl_context=context)
|
||||||
|
# Note:
|
||||||
|
# If you start your bot using nginx or Apache web server, SSL context is not required.
|
||||||
|
# Otherwise you need to set `ssl_context` parameter.
|
||||||
18
tests/test_dispatcher/test_filters/test_builtin.py
Normal file
18
tests/test_dispatcher/test_filters/test_builtin.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.dispatcher.filters.builtin import Text
|
||||||
|
|
||||||
|
|
||||||
|
class TestText:
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('param, key', [
|
||||||
|
('text', 'equals'),
|
||||||
|
('text_contains', 'contains'),
|
||||||
|
('text_startswith', 'startswith'),
|
||||||
|
('text_endswith', 'endswith'),
|
||||||
|
])
|
||||||
|
def test_validate(self, param, key):
|
||||||
|
value = 'spam and eggs'
|
||||||
|
config = {param: value}
|
||||||
|
res = Text.validate(config)
|
||||||
|
assert res == {key: value}
|
||||||
18
tests/test_dispatcher/test_filters/test_state.py
Normal file
18
tests/test_dispatcher/test_filters/test_state.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from aiogram.dispatcher.filters.state import StatesGroup
|
||||||
|
|
||||||
|
class TestStatesGroup:
|
||||||
|
|
||||||
|
def test_all_childs(self):
|
||||||
|
|
||||||
|
class InnerState1(StatesGroup):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InnerState2(InnerState1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Form(StatesGroup):
|
||||||
|
inner1 = InnerState1
|
||||||
|
inner2 = InnerState2
|
||||||
|
|
||||||
|
form_childs = Form.all_childs
|
||||||
|
assert form_childs == (InnerState1, InnerState2)
|
||||||
|
|
@ -4,38 +4,35 @@ from aiogram.dispatcher.filters import Text
|
||||||
from aiogram.types import Message, CallbackQuery, InlineQuery, Poll
|
from aiogram.types import Message, CallbackQuery, InlineQuery, Poll
|
||||||
|
|
||||||
|
|
||||||
|
def data_sample_1():
|
||||||
|
return [
|
||||||
|
('', ''),
|
||||||
|
('', 'exAmple_string'),
|
||||||
|
|
||||||
|
('example_string', 'example_string'),
|
||||||
|
('example_string', 'exAmple_string'),
|
||||||
|
('exAmple_string', 'example_string'),
|
||||||
|
|
||||||
|
('example_string', 'example_string_dsf'),
|
||||||
|
('example_string', 'example_striNG_dsf'),
|
||||||
|
('example_striNG', 'example_string_dsf'),
|
||||||
|
|
||||||
|
('example_string', 'not_example_string'),
|
||||||
|
('example_string', 'not_eXample_string'),
|
||||||
|
('EXample_string', 'not_example_string'),
|
||||||
|
]
|
||||||
|
|
||||||
class TestTextFilter:
|
class TestTextFilter:
|
||||||
|
|
||||||
|
async def _run_check(self, check, test_text):
|
||||||
|
assert await check(Message(text=test_text))
|
||||||
|
assert await check(CallbackQuery(data=test_text))
|
||||||
|
assert await check(InlineQuery(query=test_text))
|
||||||
|
assert await check(Poll(question=test_text))
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_prefix, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[('', '', True),
|
@pytest.mark.parametrize("test_prefix, test_text", data_sample_1())
|
||||||
('', 'exAmple_string', True),
|
|
||||||
('', '', False),
|
|
||||||
('', 'exAmple_string', False),
|
|
||||||
|
|
||||||
('example_string', 'example_string', True),
|
|
||||||
('example_string', 'exAmple_string', True),
|
|
||||||
('exAmple_string', 'example_string', True),
|
|
||||||
|
|
||||||
('example_string', 'example_string', False),
|
|
||||||
('example_string', 'exAmple_string', False),
|
|
||||||
('exAmple_string', 'example_string', False),
|
|
||||||
|
|
||||||
('example_string', 'example_string_dsf', True),
|
|
||||||
('example_string', 'example_striNG_dsf', True),
|
|
||||||
('example_striNG', 'example_string_dsf', True),
|
|
||||||
|
|
||||||
('example_string', 'example_string_dsf', False),
|
|
||||||
('example_string', 'example_striNG_dsf', False),
|
|
||||||
('example_striNG', 'example_string_dsf', False),
|
|
||||||
|
|
||||||
('example_string', 'not_example_string', True),
|
|
||||||
('example_string', 'not_eXample_string', True),
|
|
||||||
('EXample_string', 'not_example_string', True),
|
|
||||||
|
|
||||||
('example_string', 'not_example_string', False),
|
|
||||||
('example_string', 'not_eXample_string', False),
|
|
||||||
('EXample_string', 'not_example_string', False),
|
|
||||||
])
|
|
||||||
async def test_startswith(self, test_prefix, test_text, ignore_case):
|
async def test_startswith(self, test_prefix, test_text, ignore_case):
|
||||||
test_filter = Text(startswith=test_prefix, ignore_case=ignore_case)
|
test_filter = Text(startswith=test_prefix, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
@ -50,42 +47,26 @@ class TestTextFilter:
|
||||||
|
|
||||||
return result is _test_text.startswith(_test_prefix)
|
return result is _test_text.startswith(_test_prefix)
|
||||||
|
|
||||||
assert await check(Message(text=test_text))
|
await self._run_check(check, test_text)
|
||||||
assert await check(CallbackQuery(data=test_text))
|
|
||||||
assert await check(InlineQuery(query=test_text))
|
|
||||||
assert await check(Poll(question=test_text))
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_prefix_list, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[(['not_example', ''], '', True),
|
@pytest.mark.parametrize("test_prefix_list, test_text", [
|
||||||
(['', 'not_example'], 'exAmple_string', True),
|
(['not_example', ''], ''),
|
||||||
(['not_example', ''], '', False),
|
(['', 'not_example'], 'exAmple_string'),
|
||||||
(['', 'not_example'], 'exAmple_string', False),
|
|
||||||
|
|
||||||
(['example_string', 'not_example'], 'example_string', True),
|
(['not_example', 'example_string'], 'example_string'),
|
||||||
(['not_example', 'example_string'], 'exAmple_string', True),
|
(['example_string', 'not_example'], 'exAmple_string'),
|
||||||
(['exAmple_string', 'not_example'], 'example_string', True),
|
(['not_example', 'exAmple_string'], 'example_string'),
|
||||||
|
|
||||||
(['not_example', 'example_string'], 'example_string', False),
|
(['not_example', 'example_string'], 'example_string_dsf'),
|
||||||
(['example_string', 'not_example'], 'exAmple_string', False),
|
(['example_string', 'not_example'], 'example_striNG_dsf'),
|
||||||
(['not_example', 'exAmple_string'], 'example_string', False),
|
(['not_example', 'example_striNG'], 'example_string_dsf'),
|
||||||
|
|
||||||
(['example_string', 'not_example'], 'example_string_dsf', True),
|
(['not_example', 'example_string'], 'not_example_string'),
|
||||||
(['not_example', 'example_string'], 'example_striNG_dsf', True),
|
(['example_string', 'not_example'], 'not_eXample_string'),
|
||||||
(['example_striNG', 'not_example'], 'example_string_dsf', True),
|
(['not_example', 'EXample_string'], 'not_example_string'),
|
||||||
|
])
|
||||||
(['not_example', 'example_string'], 'example_string_dsf', False),
|
|
||||||
(['example_string', 'not_example'], 'example_striNG_dsf', False),
|
|
||||||
(['not_example', 'example_striNG'], 'example_string_dsf', False),
|
|
||||||
|
|
||||||
(['example_string', 'not_example'], 'not_example_string', True),
|
|
||||||
(['not_example', 'example_string'], 'not_eXample_string', True),
|
|
||||||
(['EXample_string', 'not_example'], 'not_example_string', True),
|
|
||||||
|
|
||||||
(['not_example', 'example_string'], 'not_example_string', False),
|
|
||||||
(['example_string', 'not_example'], 'not_eXample_string', False),
|
|
||||||
(['not_example', 'EXample_string'], 'not_example_string', False),
|
|
||||||
])
|
|
||||||
async def test_startswith_list(self, test_prefix_list, test_text, ignore_case):
|
async def test_startswith_list(self, test_prefix_list, test_text, ignore_case):
|
||||||
test_filter = Text(startswith=test_prefix_list, ignore_case=ignore_case)
|
test_filter = Text(startswith=test_prefix_list, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
@ -100,42 +81,11 @@ class TestTextFilter:
|
||||||
|
|
||||||
return result is any(map(_test_text.startswith, _test_prefix_list))
|
return result is any(map(_test_text.startswith, _test_prefix_list))
|
||||||
|
|
||||||
assert await check(Message(text=test_text))
|
await self._run_check(check, test_text)
|
||||||
assert await check(CallbackQuery(data=test_text))
|
|
||||||
assert await check(InlineQuery(query=test_text))
|
|
||||||
assert await check(Poll(question=test_text))
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_postfix, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[('', '', True),
|
@pytest.mark.parametrize("test_postfix, test_text", data_sample_1())
|
||||||
('', 'exAmple_string', True),
|
|
||||||
('', '', False),
|
|
||||||
('', 'exAmple_string', False),
|
|
||||||
|
|
||||||
('example_string', 'example_string', True),
|
|
||||||
('example_string', 'exAmple_string', True),
|
|
||||||
('exAmple_string', 'example_string', True),
|
|
||||||
|
|
||||||
('example_string', 'example_string', False),
|
|
||||||
('example_string', 'exAmple_string', False),
|
|
||||||
('exAmple_string', 'example_string', False),
|
|
||||||
|
|
||||||
('example_string', 'example_string_dsf', True),
|
|
||||||
('example_string', 'example_striNG_dsf', True),
|
|
||||||
('example_striNG', 'example_string_dsf', True),
|
|
||||||
|
|
||||||
('example_string', 'example_string_dsf', False),
|
|
||||||
('example_string', 'example_striNG_dsf', False),
|
|
||||||
('example_striNG', 'example_string_dsf', False),
|
|
||||||
|
|
||||||
('example_string', 'not_example_string', True),
|
|
||||||
('example_string', 'not_eXample_string', True),
|
|
||||||
('EXample_string', 'not_eXample_string', True),
|
|
||||||
|
|
||||||
('example_string', 'not_example_string', False),
|
|
||||||
('example_string', 'not_eXample_string', False),
|
|
||||||
('EXample_string', 'not_example_string', False),
|
|
||||||
])
|
|
||||||
async def test_endswith(self, test_postfix, test_text, ignore_case):
|
async def test_endswith(self, test_postfix, test_text, ignore_case):
|
||||||
test_filter = Text(endswith=test_postfix, ignore_case=ignore_case)
|
test_filter = Text(endswith=test_postfix, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
@ -150,42 +100,26 @@ class TestTextFilter:
|
||||||
|
|
||||||
return result is _test_text.endswith(_test_postfix)
|
return result is _test_text.endswith(_test_postfix)
|
||||||
|
|
||||||
assert await check(Message(text=test_text))
|
await self._run_check(check, test_text)
|
||||||
assert await check(CallbackQuery(data=test_text))
|
|
||||||
assert await check(InlineQuery(query=test_text))
|
|
||||||
assert await check(Poll(question=test_text))
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_postfix_list, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[(['', 'not_example'], '', True),
|
@pytest.mark.parametrize("test_postfix_list, test_text", [
|
||||||
(['not_example', ''], 'exAmple_string', True),
|
(['', 'not_example'], ''),
|
||||||
(['', 'not_example'], '', False),
|
(['not_example', ''], 'exAmple_string'),
|
||||||
(['not_example', ''], 'exAmple_string', False),
|
|
||||||
|
|
||||||
(['example_string', 'not_example'], 'example_string', True),
|
(['example_string', 'not_example'], 'example_string'),
|
||||||
(['not_example', 'example_string'], 'exAmple_string', True),
|
(['not_example', 'example_string'], 'exAmple_string'),
|
||||||
(['exAmple_string', 'not_example'], 'example_string', True),
|
(['exAmple_string', 'not_example'], 'example_string'),
|
||||||
|
|
||||||
(['example_string', 'not_example'], 'example_string', False),
|
(['not_example', 'example_string'], 'example_string_dsf'),
|
||||||
(['not_example', 'example_string'], 'exAmple_string', False),
|
(['example_string', 'not_example'], 'example_striNG_dsf'),
|
||||||
(['exAmple_string', 'not_example'], 'example_string', False),
|
(['not_example', 'example_striNG'], 'example_string_dsf'),
|
||||||
|
|
||||||
(['example_string', 'not_example'], 'example_string_dsf', True),
|
(['not_example', 'example_string'], 'not_example_string'),
|
||||||
(['not_example', 'example_string'], 'example_striNG_dsf', True),
|
(['example_string', 'not_example'], 'not_eXample_string'),
|
||||||
(['example_striNG', 'not_example'], 'example_string_dsf', True),
|
(['not_example', 'EXample_string'], 'not_example_string'),
|
||||||
|
])
|
||||||
(['not_example', 'example_string'], 'example_string_dsf', False),
|
|
||||||
(['example_string', 'not_example'], 'example_striNG_dsf', False),
|
|
||||||
(['not_example', 'example_striNG'], 'example_string_dsf', False),
|
|
||||||
|
|
||||||
(['not_example', 'example_string'], 'not_example_string', True),
|
|
||||||
(['example_string', 'not_example'], 'not_eXample_string', True),
|
|
||||||
(['not_example', 'EXample_string'], 'not_eXample_string', True),
|
|
||||||
|
|
||||||
(['not_example', 'example_string'], 'not_example_string', False),
|
|
||||||
(['example_string', 'not_example'], 'not_eXample_string', False),
|
|
||||||
(['not_example', 'EXample_string'], 'not_example_string', False),
|
|
||||||
])
|
|
||||||
async def test_endswith_list(self, test_postfix_list, test_text, ignore_case):
|
async def test_endswith_list(self, test_postfix_list, test_text, ignore_case):
|
||||||
test_filter = Text(endswith=test_postfix_list, ignore_case=ignore_case)
|
test_filter = Text(endswith=test_postfix_list, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
@ -199,42 +133,26 @@ class TestTextFilter:
|
||||||
_test_text = test_text
|
_test_text = test_text
|
||||||
|
|
||||||
return result is any(map(_test_text.endswith, _test_postfix_list))
|
return result is any(map(_test_text.endswith, _test_postfix_list))
|
||||||
assert await check(Message(text=test_text))
|
await self._run_check(check, test_text)
|
||||||
assert await check(CallbackQuery(data=test_text))
|
|
||||||
assert await check(InlineQuery(query=test_text))
|
|
||||||
assert await check(Poll(question=test_text))
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_string, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[('', '', True),
|
@pytest.mark.parametrize("test_string, test_text", [
|
||||||
('', 'exAmple_string', True),
|
('', ''),
|
||||||
('', '', False),
|
('', 'exAmple_string'),
|
||||||
('', 'exAmple_string', False),
|
|
||||||
|
|
||||||
('example_string', 'example_string', True),
|
('example_string', 'example_string'),
|
||||||
('example_string', 'exAmple_string', True),
|
('example_string', 'exAmple_string'),
|
||||||
('exAmple_string', 'example_string', True),
|
('exAmple_string', 'example_string'),
|
||||||
|
|
||||||
('example_string', 'example_string', False),
|
('example_string', 'example_string_dsf'),
|
||||||
('example_string', 'exAmple_string', False),
|
('example_string', 'example_striNG_dsf'),
|
||||||
('exAmple_string', 'example_string', False),
|
('example_striNG', 'example_string_dsf'),
|
||||||
|
|
||||||
('example_string', 'example_string_dsf', True),
|
('example_string', 'not_example_strin'),
|
||||||
('example_string', 'example_striNG_dsf', True),
|
('example_string', 'not_eXample_strin'),
|
||||||
('example_striNG', 'example_string_dsf', True),
|
('EXample_string', 'not_example_strin'),
|
||||||
|
])
|
||||||
('example_string', 'example_string_dsf', False),
|
|
||||||
('example_string', 'example_striNG_dsf', False),
|
|
||||||
('example_striNG', 'example_string_dsf', False),
|
|
||||||
|
|
||||||
('example_string', 'not_example_strin', True),
|
|
||||||
('example_string', 'not_eXample_strin', True),
|
|
||||||
('EXample_string', 'not_eXample_strin', True),
|
|
||||||
|
|
||||||
('example_string', 'not_example_strin', False),
|
|
||||||
('example_string', 'not_eXample_strin', False),
|
|
||||||
('EXample_string', 'not_example_strin', False),
|
|
||||||
])
|
|
||||||
async def test_contains(self, test_string, test_text, ignore_case):
|
async def test_contains(self, test_string, test_text, ignore_case):
|
||||||
test_filter = Text(contains=test_string, ignore_case=ignore_case)
|
test_filter = Text(contains=test_string, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
@ -249,23 +167,16 @@ class TestTextFilter:
|
||||||
|
|
||||||
return result is (_test_string in _test_text)
|
return result is (_test_string in _test_text)
|
||||||
|
|
||||||
assert await check(Message(text=test_text))
|
await self._run_check(check, test_text)
|
||||||
assert await check(CallbackQuery(data=test_text))
|
|
||||||
assert await check(InlineQuery(query=test_text))
|
|
||||||
assert await check(Poll(question=test_text))
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_filter_list, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[(['a', 'ab', 'abc'], 'A', True),
|
@pytest.mark.parametrize("test_filter_list, test_text", [
|
||||||
(['a', 'ab', 'abc'], 'ab', True),
|
(['a', 'ab', 'abc'], 'A'),
|
||||||
(['a', 'ab', 'abc'], 'aBc', True),
|
(['a', 'ab', 'abc'], 'ab'),
|
||||||
(['a', 'ab', 'abc'], 'd', True),
|
(['a', 'ab', 'abc'], 'aBc'),
|
||||||
|
(['a', 'ab', 'abc'], 'd'),
|
||||||
(['a', 'ab', 'abc'], 'A', False),
|
])
|
||||||
(['a', 'ab', 'abc'], 'ab', False),
|
|
||||||
(['a', 'ab', 'abc'], 'aBc', False),
|
|
||||||
(['a', 'ab', 'abc'], 'd', False),
|
|
||||||
])
|
|
||||||
async def test_contains_list(self, test_filter_list, test_text, ignore_case):
|
async def test_contains_list(self, test_filter_list, test_text, ignore_case):
|
||||||
test_filter = Text(contains=test_filter_list, ignore_case=ignore_case)
|
test_filter = Text(contains=test_filter_list, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
@ -280,34 +191,22 @@ class TestTextFilter:
|
||||||
|
|
||||||
return result is all(map(_test_text.__contains__, _test_filter_list))
|
return result is all(map(_test_text.__contains__, _test_filter_list))
|
||||||
|
|
||||||
assert await check(Message(text=test_text))
|
await self._run_check(check, test_text)
|
||||||
assert await check(CallbackQuery(data=test_text))
|
|
||||||
assert await check(InlineQuery(query=test_text))
|
|
||||||
assert await check(Poll(question=test_text))
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_filter_text, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[('', '', True),
|
@pytest.mark.parametrize("test_filter_text, test_text", [
|
||||||
('', 'exAmple_string', True),
|
('', ''),
|
||||||
('', '', False),
|
('', 'exAmple_string'),
|
||||||
('', 'exAmple_string', False),
|
|
||||||
|
|
||||||
('example_string', 'example_string', True),
|
('example_string', 'example_string'),
|
||||||
('example_string', 'exAmple_string', True),
|
('example_string', 'exAmple_string'),
|
||||||
('exAmple_string', 'example_string', True),
|
('exAmple_string', 'example_string'),
|
||||||
|
|
||||||
('example_string', 'example_string', False),
|
('example_string', 'not_example_string'),
|
||||||
('example_string', 'exAmple_string', False),
|
('example_string', 'not_eXample_string'),
|
||||||
('exAmple_string', 'example_string', False),
|
('EXample_string', 'not_example_string'),
|
||||||
|
])
|
||||||
('example_string', 'not_example_string', True),
|
|
||||||
('example_string', 'not_eXample_string', True),
|
|
||||||
('EXample_string', 'not_eXample_string', True),
|
|
||||||
|
|
||||||
('example_string', 'not_example_string', False),
|
|
||||||
('example_string', 'not_eXample_string', False),
|
|
||||||
('EXample_string', 'not_example_string', False),
|
|
||||||
])
|
|
||||||
async def test_equals_string(self, test_filter_text, test_text, ignore_case):
|
async def test_equals_string(self, test_filter_text, test_text, ignore_case):
|
||||||
test_filter = Text(equals=test_filter_text, ignore_case=ignore_case)
|
test_filter = Text(equals=test_filter_text, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
@ -321,50 +220,30 @@ class TestTextFilter:
|
||||||
_test_text = test_text
|
_test_text = test_text
|
||||||
return result is (_test_text == _test_filter_text)
|
return result is (_test_text == _test_filter_text)
|
||||||
|
|
||||||
assert await check(Message(text=test_text))
|
await self._run_check(check, test_text)
|
||||||
assert await check(CallbackQuery(data=test_text))
|
|
||||||
assert await check(InlineQuery(query=test_text))
|
|
||||||
assert await check(Poll(question=test_text))
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize("test_filter_list, test_text, ignore_case",
|
@pytest.mark.parametrize('ignore_case', (True, False))
|
||||||
[(['', 'new_string'], '', True),
|
@pytest.mark.parametrize("test_filter_list, test_text", [
|
||||||
(['new_string', ''], 'exAmple_string', True),
|
(['new_string', ''], ''),
|
||||||
(['new_string', ''], '', False),
|
(['', 'new_string'], 'exAmple_string'),
|
||||||
(['', 'new_string'], 'exAmple_string', False),
|
|
||||||
|
|
||||||
(['example_string'], 'example_string', True),
|
(['example_string'], 'example_string'),
|
||||||
(['example_string'], 'exAmple_string', True),
|
(['example_string'], 'exAmple_string'),
|
||||||
(['exAmple_string'], 'example_string', True),
|
(['exAmple_string'], 'example_string'),
|
||||||
|
|
||||||
(['example_string'], 'example_string', False),
|
(['example_string'], 'not_example_string'),
|
||||||
(['example_string'], 'exAmple_string', False),
|
(['example_string'], 'not_eXample_string'),
|
||||||
(['exAmple_string'], 'example_string', False),
|
(['EXample_string'], 'not_example_string'),
|
||||||
|
|
||||||
(['example_string'], 'not_example_string', True),
|
(['example_string', 'new_string'], 'example_string'),
|
||||||
(['example_string'], 'not_eXample_string', True),
|
(['new_string', 'example_string'], 'exAmple_string'),
|
||||||
(['EXample_string'], 'not_eXample_string', True),
|
(['exAmple_string', 'new_string'], 'example_string'),
|
||||||
|
|
||||||
(['example_string'], 'not_example_string', False),
|
(['example_string', 'new_string'], 'not_example_string'),
|
||||||
(['example_string'], 'not_eXample_string', False),
|
(['new_string', 'example_string'], 'not_eXample_string'),
|
||||||
(['EXample_string'], 'not_example_string', False),
|
(['EXample_string', 'new_string'], 'not_example_string'),
|
||||||
|
])
|
||||||
(['example_string', 'new_string'], 'example_string', True),
|
|
||||||
(['new_string', 'example_string'], 'exAmple_string', True),
|
|
||||||
(['exAmple_string', 'new_string'], 'example_string', True),
|
|
||||||
|
|
||||||
(['example_string', 'new_string'], 'example_string', False),
|
|
||||||
(['new_string', 'example_string'], 'exAmple_string', False),
|
|
||||||
(['exAmple_string', 'new_string'], 'example_string', False),
|
|
||||||
|
|
||||||
(['example_string', 'new_string'], 'not_example_string', True),
|
|
||||||
(['new_string', 'example_string'], 'not_eXample_string', True),
|
|
||||||
(['EXample_string', 'new_string'], 'not_eXample_string', True),
|
|
||||||
|
|
||||||
(['example_string', 'new_string'], 'not_example_string', False),
|
|
||||||
(['new_string', 'example_string'], 'not_eXample_string', False),
|
|
||||||
(['EXample_string', 'new_string'], 'not_example_string', False),
|
|
||||||
])
|
|
||||||
async def test_equals_list(self, test_filter_list, test_text, ignore_case):
|
async def test_equals_list(self, test_filter_list, test_text, ignore_case):
|
||||||
test_filter = Text(equals=test_filter_list, ignore_case=ignore_case)
|
test_filter = Text(equals=test_filter_list, ignore_case=ignore_case)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ USER = {
|
||||||
"first_name": "FirstName",
|
"first_name": "FirstName",
|
||||||
"last_name": "LastName",
|
"last_name": "LastName",
|
||||||
"username": "username",
|
"username": "username",
|
||||||
"language_code": "ru"
|
"language_code": "ru",
|
||||||
}
|
}
|
||||||
|
|
||||||
CHAT = {
|
CHAT = {
|
||||||
|
|
@ -16,14 +16,14 @@ CHAT = {
|
||||||
"first_name": "FirstName",
|
"first_name": "FirstName",
|
||||||
"last_name": "LastName",
|
"last_name": "LastName",
|
||||||
"username": "username",
|
"username": "username",
|
||||||
"type": "private"
|
"type": "private",
|
||||||
}
|
}
|
||||||
|
|
||||||
PHOTO = {
|
PHOTO = {
|
||||||
"file_id": "AgADBAADFak0G88YZAf8OAug7bHyS9x2ZxkABHVfpJywcloRAAGAAQABAg",
|
"file_id": "AgADBAADFak0G88YZAf8OAug7bHyS9x2ZxkABHVfpJywcloRAAGAAQABAg",
|
||||||
"file_size": 1101,
|
"file_size": 1101,
|
||||||
"width": 90,
|
"width": 90,
|
||||||
"height": 51
|
"height": 51,
|
||||||
}
|
}
|
||||||
|
|
||||||
AUDIO = {
|
AUDIO = {
|
||||||
|
|
@ -32,7 +32,7 @@ AUDIO = {
|
||||||
"title": "The Best Song",
|
"title": "The Best Song",
|
||||||
"performer": "The Best Singer",
|
"performer": "The Best Singer",
|
||||||
"file_id": "CQADAgADbQEAAsnrIUpNoRRNsH7_hAI",
|
"file_id": "CQADAgADbQEAAsnrIUpNoRRNsH7_hAI",
|
||||||
"file_size": 9507774
|
"file_size": 9507774,
|
||||||
}
|
}
|
||||||
|
|
||||||
CHAT_MEMBER = {
|
CHAT_MEMBER = {
|
||||||
|
|
@ -44,7 +44,7 @@ CHAT_MEMBER = {
|
||||||
"can_invite_users": True,
|
"can_invite_users": True,
|
||||||
"can_restrict_members": True,
|
"can_restrict_members": True,
|
||||||
"can_pin_messages": True,
|
"can_pin_messages": True,
|
||||||
"can_promote_members": False
|
"can_promote_members": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONTACT = {
|
CONTACT = {
|
||||||
|
|
@ -57,7 +57,7 @@ DOCUMENT = {
|
||||||
"file_name": "test.docx",
|
"file_name": "test.docx",
|
||||||
"mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
"mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
"file_id": "BQADAgADpgADy_JxS66XQTBRHFleAg",
|
"file_id": "BQADAgADpgADy_JxS66XQTBRHFleAg",
|
||||||
"file_size": 21331
|
"file_size": 21331,
|
||||||
}
|
}
|
||||||
|
|
||||||
ANIMATION = {
|
ANIMATION = {
|
||||||
|
|
@ -65,51 +65,51 @@ ANIMATION = {
|
||||||
"mime_type": "video/mp4",
|
"mime_type": "video/mp4",
|
||||||
"thumb": PHOTO,
|
"thumb": PHOTO,
|
||||||
"file_id": "CgADBAAD4DUAAoceZAe2WiE9y0crrAI",
|
"file_id": "CgADBAAD4DUAAoceZAe2WiE9y0crrAI",
|
||||||
"file_size": 65837
|
"file_size": 65837,
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTITY_BOLD = {
|
ENTITY_BOLD = {
|
||||||
"offset": 5,
|
"offset": 5,
|
||||||
"length": 2,
|
"length": 2,
|
||||||
"type": "bold"
|
"type": "bold",
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTITY_ITALIC = {
|
ENTITY_ITALIC = {
|
||||||
"offset": 8,
|
"offset": 8,
|
||||||
"length": 1,
|
"length": 1,
|
||||||
"type": "italic"
|
"type": "italic",
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTITY_LINK = {
|
ENTITY_LINK = {
|
||||||
"offset": 10,
|
"offset": 10,
|
||||||
"length": 6,
|
"length": 6,
|
||||||
"type": "text_link",
|
"type": "text_link",
|
||||||
"url": "http://google.com/"
|
"url": "http://google.com/",
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTITY_CODE = {
|
ENTITY_CODE = {
|
||||||
"offset": 17,
|
"offset": 17,
|
||||||
"length": 7,
|
"length": 7,
|
||||||
"type": "code"
|
"type": "code",
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTITY_PRE = {
|
ENTITY_PRE = {
|
||||||
"offset": 30,
|
"offset": 30,
|
||||||
"length": 4,
|
"length": 4,
|
||||||
"type": "pre"
|
"type": "pre",
|
||||||
}
|
}
|
||||||
|
|
||||||
ENTITY_MENTION = {
|
ENTITY_MENTION = {
|
||||||
"offset": 47,
|
"offset": 47,
|
||||||
"length": 9,
|
"length": 9,
|
||||||
"type": "mention"
|
"type": "mention",
|
||||||
}
|
}
|
||||||
|
|
||||||
GAME = {
|
GAME = {
|
||||||
"title": "Karate Kido",
|
"title": "Karate Kido",
|
||||||
"description": "No trees were harmed in the making of this game :)",
|
"description": "No trees were harmed in the making of this game :)",
|
||||||
"photo": [PHOTO, PHOTO, PHOTO],
|
"photo": [PHOTO, PHOTO, PHOTO],
|
||||||
"animation": ANIMATION
|
"animation": ANIMATION,
|
||||||
}
|
}
|
||||||
|
|
||||||
INVOICE = {
|
INVOICE = {
|
||||||
|
|
@ -120,19 +120,19 @@ INVOICE = {
|
||||||
"Order our Working Time Machine today!",
|
"Order our Working Time Machine today!",
|
||||||
"start_parameter": "time-machine-example",
|
"start_parameter": "time-machine-example",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"total_amount": 6250
|
"total_amount": 6250,
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCATION = {
|
LOCATION = {
|
||||||
"latitude": 50.693416,
|
"latitude": 50.693416,
|
||||||
"longitude": 30.624605
|
"longitude": 30.624605,
|
||||||
}
|
}
|
||||||
|
|
||||||
VENUE = {
|
VENUE = {
|
||||||
"location": LOCATION,
|
"location": LOCATION,
|
||||||
"title": "Venue Name",
|
"title": "Venue Name",
|
||||||
"address": "Venue Address",
|
"address": "Venue Address",
|
||||||
"foursquare_id": "4e6f2cec483bad563d150f98"
|
"foursquare_id": "4e6f2cec483bad563d150f98",
|
||||||
}
|
}
|
||||||
|
|
||||||
SHIPPING_ADDRESS = {
|
SHIPPING_ADDRESS = {
|
||||||
|
|
@ -141,7 +141,7 @@ SHIPPING_ADDRESS = {
|
||||||
"city": "DefaultCity",
|
"city": "DefaultCity",
|
||||||
"street_line1": "Central",
|
"street_line1": "Central",
|
||||||
"street_line2": "Middle",
|
"street_line2": "Middle",
|
||||||
"post_code": "424242"
|
"post_code": "424242",
|
||||||
}
|
}
|
||||||
|
|
||||||
STICKER = {
|
STICKER = {
|
||||||
|
|
@ -156,7 +156,7 @@ STICKER = {
|
||||||
"height": 128
|
"height": 128
|
||||||
},
|
},
|
||||||
"file_id": "AAbbCCddEEffGGhh1234567890",
|
"file_id": "AAbbCCddEEffGGhh1234567890",
|
||||||
"file_size": 12345
|
"file_size": 12345,
|
||||||
}
|
}
|
||||||
|
|
||||||
SUCCESSFUL_PAYMENT = {
|
SUCCESSFUL_PAYMENT = {
|
||||||
|
|
@ -164,7 +164,7 @@ SUCCESSFUL_PAYMENT = {
|
||||||
"total_amount": 6250,
|
"total_amount": 6250,
|
||||||
"invoice_payload": "HAPPY FRIDAYS COUPON",
|
"invoice_payload": "HAPPY FRIDAYS COUPON",
|
||||||
"telegram_payment_charge_id": "_",
|
"telegram_payment_charge_id": "_",
|
||||||
"provider_payment_charge_id": "12345678901234_test"
|
"provider_payment_charge_id": "12345678901234_test",
|
||||||
}
|
}
|
||||||
|
|
||||||
VIDEO = {
|
VIDEO = {
|
||||||
|
|
@ -174,7 +174,7 @@ VIDEO = {
|
||||||
"mime_type": "video/quicktime",
|
"mime_type": "video/quicktime",
|
||||||
"thumb": PHOTO,
|
"thumb": PHOTO,
|
||||||
"file_id": "BAADAgpAADdawy_JxS72kRvV3cortAg",
|
"file_id": "BAADAgpAADdawy_JxS72kRvV3cortAg",
|
||||||
"file_size": 10099782
|
"file_size": 10099782,
|
||||||
}
|
}
|
||||||
|
|
||||||
VIDEO_NOTE = {
|
VIDEO_NOTE = {
|
||||||
|
|
@ -182,14 +182,14 @@ VIDEO_NOTE = {
|
||||||
"length": 240,
|
"length": 240,
|
||||||
"thumb": PHOTO,
|
"thumb": PHOTO,
|
||||||
"file_id": "AbCdEfGhIjKlMnOpQrStUvWxYz",
|
"file_id": "AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||||
"file_size": 186562
|
"file_size": 186562,
|
||||||
}
|
}
|
||||||
|
|
||||||
VOICE = {
|
VOICE = {
|
||||||
"duration": 1,
|
"duration": 1,
|
||||||
"mime_type": "audio/ogg",
|
"mime_type": "audio/ogg",
|
||||||
"file_id": "AwADawAgADADy_JxS2gopIVIIxlhAg",
|
"file_id": "AwADawAgADADy_JxS2gopIVIIxlhAg",
|
||||||
"file_size": 4321
|
"file_size": 4321,
|
||||||
}
|
}
|
||||||
|
|
||||||
CALLBACK_QUERY = {}
|
CALLBACK_QUERY = {}
|
||||||
|
|
@ -206,7 +206,7 @@ EDITED_MESSAGE = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508825372,
|
"date": 1508825372,
|
||||||
"edit_date": 1508825379,
|
"edit_date": 1508825379,
|
||||||
"text": "hi there (edited)"
|
"text": "hi there (edited)",
|
||||||
}
|
}
|
||||||
|
|
||||||
FORWARDED_MESSAGE = {
|
FORWARDED_MESSAGE = {
|
||||||
|
|
@ -219,7 +219,7 @@ FORWARDED_MESSAGE = {
|
||||||
"forward_date": 1522749037,
|
"forward_date": 1522749037,
|
||||||
"text": "Forwarded text with entities from public channel ",
|
"text": "Forwarded text with entities from public channel ",
|
||||||
"entities": [ENTITY_BOLD, ENTITY_CODE, ENTITY_ITALIC, ENTITY_LINK,
|
"entities": [ENTITY_BOLD, ENTITY_CODE, ENTITY_ITALIC, ENTITY_LINK,
|
||||||
ENTITY_LINK, ENTITY_MENTION, ENTITY_PRE]
|
ENTITY_LINK, ENTITY_MENTION, ENTITY_PRE],
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE_QUERY = {}
|
INLINE_QUERY = {}
|
||||||
|
|
@ -229,7 +229,7 @@ MESSAGE = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508709711,
|
"date": 1508709711,
|
||||||
"text": "Hi, world!"
|
"text": "Hi, world!",
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_AUDIO = {
|
MESSAGE_WITH_AUDIO = {
|
||||||
|
|
@ -238,7 +238,7 @@ MESSAGE_WITH_AUDIO = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508739776,
|
"date": 1508739776,
|
||||||
"audio": AUDIO,
|
"audio": AUDIO,
|
||||||
"caption": "This is my favourite song"
|
"caption": "This is my favourite song",
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_AUTHOR_SIGNATURE = {}
|
MESSAGE_WITH_AUTHOR_SIGNATURE = {}
|
||||||
|
|
@ -250,7 +250,7 @@ MESSAGE_WITH_CONTACT = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1522850298,
|
"date": 1522850298,
|
||||||
"contact": CONTACT
|
"contact": CONTACT,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_DELETE_CHAT_PHOTO = {}
|
MESSAGE_WITH_DELETE_CHAT_PHOTO = {}
|
||||||
|
|
@ -261,7 +261,7 @@ MESSAGE_WITH_DOCUMENT = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508768012,
|
"date": 1508768012,
|
||||||
"document": DOCUMENT,
|
"document": DOCUMENT,
|
||||||
"caption": "Read my document"
|
"caption": "Read my document",
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_EDIT_DATE = {}
|
MESSAGE_WITH_EDIT_DATE = {}
|
||||||
|
|
@ -273,7 +273,7 @@ MESSAGE_WITH_GAME = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508824810,
|
"date": 1508824810,
|
||||||
"game": GAME
|
"game": GAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_GROUP_CHAT_CREATED = {}
|
MESSAGE_WITH_GROUP_CHAT_CREATED = {}
|
||||||
|
|
@ -283,7 +283,7 @@ MESSAGE_WITH_INVOICE = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508761719,
|
"date": 1508761719,
|
||||||
"invoice": INVOICE
|
"invoice": INVOICE,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_LEFT_CHAT_MEMBER = {}
|
MESSAGE_WITH_LEFT_CHAT_MEMBER = {}
|
||||||
|
|
@ -293,7 +293,7 @@ MESSAGE_WITH_LOCATION = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508755473,
|
"date": 1508755473,
|
||||||
"location": LOCATION
|
"location": LOCATION,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_MIGRATE_TO_CHAT_ID = {
|
MESSAGE_WITH_MIGRATE_TO_CHAT_ID = {
|
||||||
|
|
@ -301,7 +301,7 @@ MESSAGE_WITH_MIGRATE_TO_CHAT_ID = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1526943253,
|
"date": 1526943253,
|
||||||
"migrate_to_chat_id": -1234567890987
|
"migrate_to_chat_id": -1234567890987,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_MIGRATE_FROM_CHAT_ID = {
|
MESSAGE_WITH_MIGRATE_FROM_CHAT_ID = {
|
||||||
|
|
@ -309,7 +309,7 @@ MESSAGE_WITH_MIGRATE_FROM_CHAT_ID = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1526943253,
|
"date": 1526943253,
|
||||||
"migrate_from_chat_id": -123456789
|
"migrate_from_chat_id": -123456789,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_NEW_CHAT_MEMBERS = {}
|
MESSAGE_WITH_NEW_CHAT_MEMBERS = {}
|
||||||
|
|
@ -324,7 +324,7 @@ MESSAGE_WITH_PHOTO = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508825154,
|
"date": 1508825154,
|
||||||
"photo": [PHOTO, PHOTO, PHOTO, PHOTO],
|
"photo": [PHOTO, PHOTO, PHOTO, PHOTO],
|
||||||
"caption": "photo description"
|
"caption": "photo description",
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_MEDIA_GROUP = {
|
MESSAGE_WITH_MEDIA_GROUP = {
|
||||||
|
|
@ -333,7 +333,7 @@ MESSAGE_WITH_MEDIA_GROUP = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1522843665,
|
"date": 1522843665,
|
||||||
"media_group_id": "12182749320567362",
|
"media_group_id": "12182749320567362",
|
||||||
"photo": [PHOTO, PHOTO, PHOTO, PHOTO]
|
"photo": [PHOTO, PHOTO, PHOTO, PHOTO],
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_PINNED_MESSAGE = {}
|
MESSAGE_WITH_PINNED_MESSAGE = {}
|
||||||
|
|
@ -345,7 +345,7 @@ MESSAGE_WITH_STICKER = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508771450,
|
"date": 1508771450,
|
||||||
"sticker": STICKER
|
"sticker": STICKER,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_SUCCESSFUL_PAYMENT = {
|
MESSAGE_WITH_SUCCESSFUL_PAYMENT = {
|
||||||
|
|
@ -353,7 +353,7 @@ MESSAGE_WITH_SUCCESSFUL_PAYMENT = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508761169,
|
"date": 1508761169,
|
||||||
"successful_payment": SUCCESSFUL_PAYMENT
|
"successful_payment": SUCCESSFUL_PAYMENT,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_SUPERGROUP_CHAT_CREATED = {}
|
MESSAGE_WITH_SUPERGROUP_CHAT_CREATED = {}
|
||||||
|
|
@ -364,7 +364,7 @@ MESSAGE_WITH_VENUE = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1522849819,
|
"date": 1522849819,
|
||||||
"location": LOCATION,
|
"location": LOCATION,
|
||||||
"venue": VENUE
|
"venue": VENUE,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_VIDEO = {
|
MESSAGE_WITH_VIDEO = {
|
||||||
|
|
@ -373,7 +373,7 @@ MESSAGE_WITH_VIDEO = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508756494,
|
"date": 1508756494,
|
||||||
"video": VIDEO,
|
"video": VIDEO,
|
||||||
"caption": "description"
|
"caption": "description",
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_VIDEO_NOTE = {
|
MESSAGE_WITH_VIDEO_NOTE = {
|
||||||
|
|
@ -381,7 +381,7 @@ MESSAGE_WITH_VIDEO_NOTE = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1522835890,
|
"date": 1522835890,
|
||||||
"video_note": VIDEO_NOTE
|
"video_note": VIDEO_NOTE,
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_WITH_VOICE = {
|
MESSAGE_WITH_VOICE = {
|
||||||
|
|
@ -389,7 +389,7 @@ MESSAGE_WITH_VOICE = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508768403,
|
"date": 1508768403,
|
||||||
"voice": VOICE
|
"voice": VOICE,
|
||||||
}
|
}
|
||||||
|
|
||||||
PRE_CHECKOUT_QUERY = {
|
PRE_CHECKOUT_QUERY = {
|
||||||
|
|
@ -397,7 +397,7 @@ PRE_CHECKOUT_QUERY = {
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
"total_amount": 6250,
|
"total_amount": 6250,
|
||||||
"invoice_payload": "HAPPY FRIDAYS COUPON"
|
"invoice_payload": "HAPPY FRIDAYS COUPON",
|
||||||
}
|
}
|
||||||
|
|
||||||
REPLY_MESSAGE = {
|
REPLY_MESSAGE = {
|
||||||
|
|
@ -406,37 +406,37 @@ REPLY_MESSAGE = {
|
||||||
"chat": CHAT,
|
"chat": CHAT,
|
||||||
"date": 1508751866,
|
"date": 1508751866,
|
||||||
"reply_to_message": MESSAGE,
|
"reply_to_message": MESSAGE,
|
||||||
"text": "Reply to quoted message"
|
"text": "Reply to quoted message",
|
||||||
}
|
}
|
||||||
|
|
||||||
SHIPPING_QUERY = {
|
SHIPPING_QUERY = {
|
||||||
"id": "262181558684397422",
|
"id": "262181558684397422",
|
||||||
"from": USER,
|
"from": USER,
|
||||||
"invoice_payload": "HAPPY FRIDAYS COUPON",
|
"invoice_payload": "HAPPY FRIDAYS COUPON",
|
||||||
"shipping_address": SHIPPING_ADDRESS
|
"shipping_address": SHIPPING_ADDRESS,
|
||||||
}
|
}
|
||||||
|
|
||||||
USER_PROFILE_PHOTOS = {
|
USER_PROFILE_PHOTOS = {
|
||||||
"total_count": 1, "photos": [
|
"total_count": 1, "photos": [
|
||||||
[PHOTO, PHOTO, PHOTO]
|
[PHOTO, PHOTO, PHOTO],
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE = {
|
FILE = {
|
||||||
"file_id": "XXXYYYZZZ",
|
"file_id": "XXXYYYZZZ",
|
||||||
"file_size": 5254,
|
"file_size": 5254,
|
||||||
"file_path": "voice\/file_8"
|
"file_path": "voice/file_8",
|
||||||
}
|
}
|
||||||
|
|
||||||
INVITE_LINK = 'https://t.me/joinchat/AbCdEfjKILDADwdd123'
|
INVITE_LINK = 'https://t.me/joinchat/AbCdEfjKILDADwdd123'
|
||||||
|
|
||||||
UPDATE = {
|
UPDATE = {
|
||||||
"update_id": 123456789,
|
"update_id": 123456789,
|
||||||
"message": MESSAGE
|
"message": MESSAGE,
|
||||||
}
|
}
|
||||||
|
|
||||||
WEBHOOK_INFO = {
|
WEBHOOK_INFO = {
|
||||||
"url": "",
|
"url": "",
|
||||||
"has_custom_certificate": False,
|
"has_custom_certificate": False,
|
||||||
"pending_update_count": 0
|
"pending_update_count": 0,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue