Deprecated custombot submodule
This commit is contained in:
parent
baf1dbbe62
commit
3e00381e0a
@ -5,21 +5,16 @@ See `bot.py` for information about Bot class.
|
|||||||
from davtelepot.bot import Bot
|
from davtelepot.bot import Bot
|
||||||
help(Bot)
|
help(Bot)
|
||||||
```
|
```
|
||||||
|
|
||||||
Legacy `custombot.py` is kept for backward compatibility but will finally
|
|
||||||
be deprecated.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = "Davide Testa"
|
__author__ = "Davide Testa"
|
||||||
__email__ = "davide@davte.it"
|
__email__ = "davide@davte.it"
|
||||||
__credits__ = ["Marco Origlia", "Nick Lee @Nickoala"]
|
__credits__ = ["Marco Origlia", "Nick Lee @Nickoala"]
|
||||||
__license__ = "GNU General Public License v3.0"
|
__license__ = "GNU General Public License v3.0"
|
||||||
__version__ = "2.5.1"
|
__version__ = "2.5.2"
|
||||||
__maintainer__ = "Davide Testa"
|
__maintainer__ = "Davide Testa"
|
||||||
__contact__ = "t.me/davte"
|
__contact__ = "t.me/davte"
|
||||||
|
|
||||||
# Legacy module; please use `from davtelepot.bot import Bot` from now on
|
|
||||||
from .custombot import Bot
|
|
||||||
from . import administration_tools, authorization, bot, helper, languages, suggestions, utilities
|
from . import administration_tools, authorization, bot, helper, languages, suggestions, utilities
|
||||||
|
|
||||||
__all__ = [administration_tools, authorization, Bot, bot, helper, languages, suggestions, utilities]
|
__all__ = [administration_tools, authorization, bot, helper, languages, suggestions, utilities]
|
||||||
|
@ -18,7 +18,7 @@ import logging
|
|||||||
from sqlalchemy.exc import ResourceClosedError
|
from sqlalchemy.exc import ResourceClosedError
|
||||||
|
|
||||||
# Project modules
|
# Project modules
|
||||||
from . import bot as davtelepot_bot, messages, __version__
|
from . import bot as davtelepot_bot, messages
|
||||||
from .utilities import (
|
from .utilities import (
|
||||||
async_wrapper, CachedPage, Confirmator, extract, get_cleaned_text,
|
async_wrapper, CachedPage, Confirmator, extract, get_cleaned_text,
|
||||||
get_user, escape_html_chars, line_drawing_unordered_list, make_button,
|
get_user, escape_html_chars, line_drawing_unordered_list, make_button,
|
||||||
|
@ -1,743 +0,0 @@
|
|||||||
"""WARNING: this is only a legacy module, written for backward compatibility.
|
|
||||||
|
|
||||||
For newer versions use `bot.py`.
|
|
||||||
This module used to rely on third party `telepot` library by Nick Lee
|
|
||||||
(@Nickoala).
|
|
||||||
The `telepot` repository was archived in may 2019 and will no longer be listed
|
|
||||||
in requirements. To run legacy code, install telepot manually.
|
|
||||||
`pip install telepot`
|
|
||||||
|
|
||||||
Subclass of third party telepot.aio.Bot, providing the following features.
|
|
||||||
|
|
||||||
- It prevents hitting Telegram flood limits by waiting
|
|
||||||
between text and photo messages.
|
|
||||||
- It provides command, parser, button and other decorators to associate
|
|
||||||
common Telegram actions with custom handlers.
|
|
||||||
- It supports multiple bots running in the same script
|
|
||||||
and allows communications between them
|
|
||||||
as well as complete independency from each other.
|
|
||||||
- Each bot is associated with a sqlite database
|
|
||||||
using dataset, a third party library.
|
|
||||||
|
|
||||||
Please note that you need Python3.5+ to run async code
|
|
||||||
Check requirements.txt for third party dependencies.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Standard library modules
|
|
||||||
import asyncio
|
|
||||||
from collections import OrderedDict
|
|
||||||
import datetime
|
|
||||||
import inspect
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Third party modules
|
|
||||||
import davtelepot.bot
|
|
||||||
|
|
||||||
# Project modules
|
|
||||||
from .utilities import (
|
|
||||||
get_secure_key, extract, sleep_until
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Bot(davtelepot.bot.Bot):
|
|
||||||
"""Legacy adapter for backward compatibility.
|
|
||||||
|
|
||||||
Old description:
|
|
||||||
telepot.aio.Bot (async Telegram bot framework) convenient subclass.
|
|
||||||
|
|
||||||
=== General functioning ===
|
|
||||||
- While Bot.run() coroutine is executed, HTTP get requests are made
|
|
||||||
to Telegram servers asking for new messages for each Bot instance.
|
|
||||||
- Each message causes the proper Bot instance method coroutine
|
|
||||||
to be awaited, according to its flavour (see routing_table)
|
|
||||||
-- For example, chat messages cause `Bot().on_chat_message(message)`
|
|
||||||
to be awaited.
|
|
||||||
- This even-processing coroutine ensures the proper handling function
|
|
||||||
a future and returns.
|
|
||||||
-- That means that simpler tasks are completed before slower ones,
|
|
||||||
since handling functions are not awaited but scheduled
|
|
||||||
by `asyncio.ensure_future(handling_function(...))`
|
|
||||||
-- For example, chat text messages are handled by
|
|
||||||
`handle_text_message`, which looks for the proper function
|
|
||||||
to elaborate the request (in bot's commands and parsers)
|
|
||||||
- The handling function evaluates an answer, depending on the message
|
|
||||||
content, and eventually provides a reply
|
|
||||||
-- For example, `handle_text_message` sends its
|
|
||||||
answer via `send_message`
|
|
||||||
- All Bot.instances run simultaneously and faster requests
|
|
||||||
are completed earlier.
|
|
||||||
- All uncaught events are ignored.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, token, db_name=None, **kwargs):
|
|
||||||
"""Instantiate Bot instance, given a token and a db name."""
|
|
||||||
davtelepot.bot.Bot.__init__(
|
|
||||||
self,
|
|
||||||
token=token,
|
|
||||||
database_url=db_name,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
self.message_handlers['pinned_message'] = self.handle_pinned_message
|
|
||||||
self.message_handlers['photo'] = self.handle_photo_message
|
|
||||||
self.message_handlers['location'] = self.handle_location
|
|
||||||
self.custom_photo_parsers = dict()
|
|
||||||
self.custom_location_parsers = dict()
|
|
||||||
self.to_be_obscured = []
|
|
||||||
self.to_be_destroyed = []
|
|
||||||
self.chat_actions = dict(
|
|
||||||
pinned=OrderedDict()
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unauthorized_message(self):
|
|
||||||
"""Return this if user is unauthorized to make a request.
|
|
||||||
|
|
||||||
This property is deprecated: use `authorization_denied_message`
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
return self.authorization_denied_message
|
|
||||||
|
|
||||||
@property
|
|
||||||
def maintenance(self):
|
|
||||||
"""Check whether bot is under maintenance.
|
|
||||||
|
|
||||||
This property is deprecated: use `under_maintenance` instead.
|
|
||||||
"""
|
|
||||||
return self.under_maintenance
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_class_unauthorized_message(csl, unauthorized_message):
|
|
||||||
"""Set class unauthorized message.
|
|
||||||
|
|
||||||
This method is deprecated: use `set_class_authorization_denied_message`
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
return csl.set_class_authorization_denied_message(unauthorized_message)
|
|
||||||
|
|
||||||
def set_unauthorized_message(self, unauthorized_message):
|
|
||||||
"""Set instance unauthorized message.
|
|
||||||
|
|
||||||
This method is deprecated: use `set_authorization_denied_message`
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
return self.set_authorization_denied_message(unauthorized_message)
|
|
||||||
|
|
||||||
def set_authorization_function(self, authorization_function):
|
|
||||||
"""Set a custom authorization_function.
|
|
||||||
|
|
||||||
It should evaluate True if user is authorized to perform a specific
|
|
||||||
action and False otherwise.
|
|
||||||
It should take update and role and return a Boolean.
|
|
||||||
Default authorization_function always evaluates True.
|
|
||||||
"""
|
|
||||||
def _authorization_function(update, authorization_level,
|
|
||||||
user_record=None):
|
|
||||||
privileges = authorization_level # noqa: W0612, this variable
|
|
||||||
# is used by locals()
|
|
||||||
return authorization_function(
|
|
||||||
**{
|
|
||||||
name: argument
|
|
||||||
for name, argument in locals().items()
|
|
||||||
if name in inspect.signature(
|
|
||||||
authorization_function
|
|
||||||
).parameters
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.authorization_function = _authorization_function
|
|
||||||
|
|
||||||
def set_maintenance(self, maintenance_message):
|
|
||||||
"""Put the bot under maintenance or end it.
|
|
||||||
|
|
||||||
This method is deprecated: use `change_maintenance_status` instead.
|
|
||||||
"""
|
|
||||||
bot_in_maintenance = self.change_maintenance_status(
|
|
||||||
maintenance_message=maintenance_message
|
|
||||||
)
|
|
||||||
if bot_in_maintenance:
|
|
||||||
return (
|
|
||||||
"<i>Bot has just been put under maintenance!</i>\n\n"
|
|
||||||
"Until further notice, it will reply to users "
|
|
||||||
"with the following message:\n\n{}"
|
|
||||||
).format(
|
|
||||||
self.maintenance_message
|
|
||||||
)
|
|
||||||
return "<i>Maintenance ended!</i>"
|
|
||||||
|
|
||||||
def set_get_chat_id_function(self, get_chat_id_function):
|
|
||||||
"""Set a custom get_chat_id function.
|
|
||||||
|
|
||||||
This method is deprecated: use `set_chat_id_getter` instead.
|
|
||||||
"""
|
|
||||||
return self.set_chat_id_getter(get_chat_id_function)
|
|
||||||
|
|
||||||
async def on_chat_message(self, update, user_record=None):
|
|
||||||
"""Handle text message.
|
|
||||||
|
|
||||||
This method is deprecated: use `text_message_handler` instead.
|
|
||||||
"""
|
|
||||||
return await self.text_message_handler(
|
|
||||||
update=update,
|
|
||||||
user_record=user_record
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_inline_result_handler(self, user_id, result_id, func):
|
|
||||||
"""Associate a `func` with a `result_id` for `user_id`.
|
|
||||||
|
|
||||||
This method is deprecated: use `set_chosen_inline_result_handler`
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
if not asyncio.iscoroutinefunction(func):
|
|
||||||
async def _func(update):
|
|
||||||
return func(update)
|
|
||||||
else:
|
|
||||||
_func = func
|
|
||||||
return self.set_chosen_inline_result_handler(
|
|
||||||
user_id=user_id,
|
|
||||||
result_id=result_id,
|
|
||||||
handler=_func
|
|
||||||
)
|
|
||||||
|
|
||||||
async def handle_pinned_message(self, update, user_record=None):
|
|
||||||
"""Handle pinned message chat action."""
|
|
||||||
if self.maintenance:
|
|
||||||
return
|
|
||||||
answerer = None
|
|
||||||
for criteria, handler in self.chat_actions['pinned'].items():
|
|
||||||
if criteria(update):
|
|
||||||
answerer = handler['function']
|
|
||||||
break
|
|
||||||
if answerer is None:
|
|
||||||
return
|
|
||||||
elif asyncio.iscoroutinefunction(answerer):
|
|
||||||
answer = await answerer(update)
|
|
||||||
else:
|
|
||||||
answer = answerer(update)
|
|
||||||
if answer:
|
|
||||||
try:
|
|
||||||
return await self.send_message(
|
|
||||||
answer=answer,
|
|
||||||
chat_id=update['chat']['id']
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(
|
|
||||||
"Failed to process answer:\n{}".format(
|
|
||||||
e
|
|
||||||
),
|
|
||||||
exc_info=True
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
async def handle_photo_message(self, update, user_record=None):
|
|
||||||
"""Handle photo chat message."""
|
|
||||||
user_id = update['from']['id'] if 'from' in update else None
|
|
||||||
answerer, answer = None, None
|
|
||||||
if self.maintenance:
|
|
||||||
if update['chat']['id'] > 0:
|
|
||||||
answer = self.maintenance_message
|
|
||||||
elif user_id in self.custom_photo_parsers:
|
|
||||||
answerer = self.custom_photo_parsers[user_id]
|
|
||||||
del self.custom_photo_parsers[user_id]
|
|
||||||
if answerer:
|
|
||||||
if asyncio.iscoroutinefunction(answerer):
|
|
||||||
answer = await answerer(update)
|
|
||||||
else:
|
|
||||||
answer = answerer(update)
|
|
||||||
if answer:
|
|
||||||
try:
|
|
||||||
return await self.send_message(answer=answer, chat_id=update)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(
|
|
||||||
"Failed to process answer:\n{}".format(
|
|
||||||
e
|
|
||||||
),
|
|
||||||
exc_info=True
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
async def handle_location(self, *args, **kwargs):
|
|
||||||
"""Handle location sent by user.
|
|
||||||
|
|
||||||
This method is deprecated: use `location_message_handler` instead.
|
|
||||||
"""
|
|
||||||
return await super().location_message_handler(*args, **kwargs)
|
|
||||||
|
|
||||||
def set_custom_parser(self, parser, update=None, user=None):
|
|
||||||
"""Set a custom parser for the user.
|
|
||||||
|
|
||||||
This method is deprecated: use `set_individual_text_message_handler`
|
|
||||||
instead.
|
|
||||||
"""
|
|
||||||
return self.set_individual_text_message_handler(
|
|
||||||
handler=parser,
|
|
||||||
update=update,
|
|
||||||
user_id=user
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_custom_photo_parser(self, parser, update=None, user=None):
|
|
||||||
"""Set a custom photo parser for the user.
|
|
||||||
|
|
||||||
Any photo chat update coming from the user will be handled by
|
|
||||||
this custom parser instead of default parsers.
|
|
||||||
Custom photo parsers last one single use, but their handler can
|
|
||||||
call this function to provide multiple tries.
|
|
||||||
"""
|
|
||||||
if user and type(user) is int:
|
|
||||||
pass
|
|
||||||
elif type(update) is int:
|
|
||||||
user = update
|
|
||||||
elif type(user) is dict:
|
|
||||||
user = (
|
|
||||||
user['from']['id']
|
|
||||||
if 'from' in user
|
|
||||||
and 'id' in user['from']
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
elif not user and type(update) is dict:
|
|
||||||
user = (
|
|
||||||
update['from']['id']
|
|
||||||
if 'from' in update
|
|
||||||
and 'id' in update['from']
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise TypeError(
|
|
||||||
'Invalid user.\nuser: {}\nupdate: {}'.format(
|
|
||||||
user,
|
|
||||||
update
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not type(user) is int:
|
|
||||||
raise TypeError(
|
|
||||||
'User {} is not an int id'.format(
|
|
||||||
user
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not callable(parser):
|
|
||||||
raise TypeError(
|
|
||||||
'Parser {} is not a callable'.format(
|
|
||||||
parser.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.custom_photo_parsers[user] = parser
|
|
||||||
return
|
|
||||||
|
|
||||||
def set_custom_location_parser(self, parser, update=None, user=None):
|
|
||||||
"""Set a custom location parser for the user.
|
|
||||||
|
|
||||||
Any location chat update coming from the user will be handled by
|
|
||||||
this custom parser instead of default parsers.
|
|
||||||
Custom location parsers last one single use, but their handler can
|
|
||||||
call this function to provide multiple tries.
|
|
||||||
"""
|
|
||||||
if user and type(user) is int:
|
|
||||||
pass
|
|
||||||
elif type(update) is int:
|
|
||||||
user = update
|
|
||||||
elif type(user) is dict:
|
|
||||||
user = (
|
|
||||||
user['from']['id']
|
|
||||||
if 'from' in user
|
|
||||||
and 'id' in user['from']
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
elif not user and type(update) is dict:
|
|
||||||
user = (
|
|
||||||
update['from']['id']
|
|
||||||
if 'from' in update
|
|
||||||
and 'id' in update['from']
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise TypeError(
|
|
||||||
'Invalid user.\nuser: {}\nupdate: {}'.format(
|
|
||||||
user,
|
|
||||||
update
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not type(user) is int:
|
|
||||||
raise TypeError(
|
|
||||||
'User {} is not an int id'.format(
|
|
||||||
user
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not callable(parser):
|
|
||||||
raise TypeError(
|
|
||||||
'Parser {} is not a callable'.format(
|
|
||||||
parser.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.custom_location_parsers[user] = parser
|
|
||||||
return
|
|
||||||
|
|
||||||
def command(self, command, aliases=None, show_in_keyboard=False,
|
|
||||||
reply_keyboard_button=None, descr="", auth='admin',
|
|
||||||
description=None,
|
|
||||||
help_section=None,
|
|
||||||
authorization_level=None):
|
|
||||||
"""Define a bot command.
|
|
||||||
|
|
||||||
`descr` and `auth` parameters are deprecated: use `description` and
|
|
||||||
`authorization_level` instead.
|
|
||||||
"""
|
|
||||||
authorization_level = authorization_level or auth
|
|
||||||
description = description or descr
|
|
||||||
return super().command(
|
|
||||||
command=command,
|
|
||||||
aliases=aliases,
|
|
||||||
reply_keyboard_button=reply_keyboard_button,
|
|
||||||
show_in_keyboard=show_in_keyboard,
|
|
||||||
description=description,
|
|
||||||
help_section=help_section,
|
|
||||||
authorization_level=authorization_level
|
|
||||||
)
|
|
||||||
|
|
||||||
def parser(self, condition, descr='', auth='admin', argument='text',
|
|
||||||
description=None,
|
|
||||||
authorization_level=None):
|
|
||||||
"""Define a message parser.
|
|
||||||
|
|
||||||
`descr` and `auth` parameters are deprecated: use `description` and
|
|
||||||
`authorization_level` instead.
|
|
||||||
"""
|
|
||||||
authorization_level = authorization_level or auth
|
|
||||||
description = description or descr
|
|
||||||
return super().parser(
|
|
||||||
condition=condition,
|
|
||||||
description=description,
|
|
||||||
authorization_level=authorization_level,
|
|
||||||
argument=argument
|
|
||||||
)
|
|
||||||
|
|
||||||
def pinned(self, condition, descr='', auth='admin'):
|
|
||||||
"""Handle pinned messages.
|
|
||||||
|
|
||||||
Decorator: `@bot.pinned(condition)`
|
|
||||||
If condition evaluates True when run on a pinned_message update,
|
|
||||||
such decorated function gets called on update.
|
|
||||||
Conditions are evaluated in order; when one is True,
|
|
||||||
others will be skipped.
|
|
||||||
`descr` is a description
|
|
||||||
`auth` is the lowest authorization level needed to run the command
|
|
||||||
"""
|
|
||||||
if not callable(condition):
|
|
||||||
raise TypeError(
|
|
||||||
'Condition {c} is not a callable'.format(
|
|
||||||
c=condition.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def decorator(func):
|
|
||||||
if asyncio.iscoroutinefunction(func):
|
|
||||||
async def decorated(message):
|
|
||||||
logging.info(
|
|
||||||
"PINNED MESSAGE MATCHING({c}) @{n} FROM({f})".format(
|
|
||||||
c=condition.__name__,
|
|
||||||
n=self.name,
|
|
||||||
f=(
|
|
||||||
message['from']
|
|
||||||
if 'from' in message
|
|
||||||
else message['chat']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if self.authorization_function(message, auth):
|
|
||||||
return await func(message)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
def decorated(message):
|
|
||||||
logging.info(
|
|
||||||
"PINNED MESSAGE MATCHING({c}) @{n} FROM({f})".format(
|
|
||||||
c=condition.__name__,
|
|
||||||
n=self.name,
|
|
||||||
f=(
|
|
||||||
message['from']
|
|
||||||
if 'from' in message
|
|
||||||
else message['chat']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if self.authorization_function(message, auth):
|
|
||||||
return func(message)
|
|
||||||
return
|
|
||||||
self.chat_actions['pinned'][condition] = dict(
|
|
||||||
function=decorated,
|
|
||||||
descr=descr,
|
|
||||||
auth=auth
|
|
||||||
)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def button(self, data=None, descr='', auth='admin',
|
|
||||||
authorization_level=None, prefix=None, description=None,
|
|
||||||
separator=None):
|
|
||||||
"""Define a bot button.
|
|
||||||
|
|
||||||
`descr` and `auth` parameters are deprecated: use `description` and
|
|
||||||
`authorization_level` instead.
|
|
||||||
`data` parameter renamed `prefix`.
|
|
||||||
"""
|
|
||||||
authorization_level = authorization_level or auth
|
|
||||||
description = description or descr
|
|
||||||
prefix = prefix or data
|
|
||||||
return super().button(
|
|
||||||
prefix=prefix,
|
|
||||||
separator=separator,
|
|
||||||
description=description,
|
|
||||||
authorization_level=authorization_level,
|
|
||||||
)
|
|
||||||
|
|
||||||
def query(self, condition, descr='', auth='admin', description=None,
|
|
||||||
authorization_level=None):
|
|
||||||
"""Define an inline query.
|
|
||||||
|
|
||||||
`descr` and `auth` parameters are deprecated: use `description` and
|
|
||||||
`authorization_level` instead.
|
|
||||||
"""
|
|
||||||
authorization_level = authorization_level or auth
|
|
||||||
description = description or descr
|
|
||||||
return super().query(
|
|
||||||
condition=condition,
|
|
||||||
description=description,
|
|
||||||
authorization_level=authorization_level,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def edit_message(self, update, *args, **kwargs):
|
|
||||||
"""Edit given update with given *args and **kwargs.
|
|
||||||
|
|
||||||
This method is deprecated: use `edit_message_text` instead.
|
|
||||||
"""
|
|
||||||
return await self.edit_message_text(
|
|
||||||
*args,
|
|
||||||
update=update,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
async def send_message(self, answer=dict(), chat_id=None, text='',
|
|
||||||
parse_mode="HTML", disable_web_page_preview=None,
|
|
||||||
disable_notification=None, reply_to_message_id=None,
|
|
||||||
reply_markup=None, update=dict(),
|
|
||||||
reply_to_update=False, send_default_keyboard=True):
|
|
||||||
"""Send a message.
|
|
||||||
|
|
||||||
This method is deprecated: use `super().send_message` instead.
|
|
||||||
"""
|
|
||||||
if update is None:
|
|
||||||
update = dict()
|
|
||||||
parameters = dict()
|
|
||||||
for parameter, value in locals().items():
|
|
||||||
if parameter in ['self', 'answer', 'parameters', '__class__']:
|
|
||||||
continue
|
|
||||||
if parameter in answer:
|
|
||||||
parameters[parameter] = answer[parameter]
|
|
||||||
else:
|
|
||||||
parameters[parameter] = value
|
|
||||||
if type(parameters['chat_id']) is dict:
|
|
||||||
parameters['update'] = parameters['chat_id']
|
|
||||||
del parameters['chat_id']
|
|
||||||
return await super().send_message(**parameters)
|
|
||||||
|
|
||||||
async def send_photo(self, chat_id=None, answer=dict(),
|
|
||||||
photo=None, caption='', parse_mode='HTML',
|
|
||||||
disable_notification=None, reply_to_message_id=None,
|
|
||||||
reply_markup=None, use_stored=True,
|
|
||||||
second_chance=False, use_stored_file_id=None,
|
|
||||||
update=dict(), reply_to_update=False,
|
|
||||||
send_default_keyboard=True):
|
|
||||||
"""Send a photo.
|
|
||||||
|
|
||||||
This method is deprecated: use `super().send_photo` instead.
|
|
||||||
"""
|
|
||||||
if update is None:
|
|
||||||
update = dict()
|
|
||||||
if use_stored is not None:
|
|
||||||
use_stored_file_id = use_stored
|
|
||||||
parameters = dict()
|
|
||||||
for parameter, value in locals().items():
|
|
||||||
if parameter in ['self', 'answer', 'parameters', '__class__',
|
|
||||||
'second_chance', 'use_stored']:
|
|
||||||
continue
|
|
||||||
if parameter in answer:
|
|
||||||
parameters[parameter] = answer[parameter]
|
|
||||||
else:
|
|
||||||
parameters[parameter] = value
|
|
||||||
if type(parameters['chat_id']) is dict:
|
|
||||||
parameters['update'] = parameters['chat_id']
|
|
||||||
del parameters['chat_id']
|
|
||||||
return await super().send_photo(**parameters)
|
|
||||||
|
|
||||||
async def send_and_destroy(self, chat_id, answer,
|
|
||||||
timer=60, mode='text', **kwargs):
|
|
||||||
"""Send a message or photo and delete it after `timer` seconds."""
|
|
||||||
if mode == 'text':
|
|
||||||
sent_message = await self.send_message(
|
|
||||||
chat_id=chat_id,
|
|
||||||
answer=answer,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
elif mode == 'pic':
|
|
||||||
sent_message = await self.send_photo(
|
|
||||||
chat_id=chat_id,
|
|
||||||
answer=answer,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
if sent_message is None:
|
|
||||||
return
|
|
||||||
self.to_be_destroyed.append(sent_message)
|
|
||||||
await asyncio.sleep(timer)
|
|
||||||
if await self.delete_message(sent_message):
|
|
||||||
self.to_be_destroyed.remove(sent_message)
|
|
||||||
return
|
|
||||||
|
|
||||||
async def wait_and_obscure(self, update, when, inline_message_id):
|
|
||||||
"""Obscure messages which can't be deleted.
|
|
||||||
|
|
||||||
Obscure an inline_message `timer` seconds after sending it,
|
|
||||||
by editing its text or caption.
|
|
||||||
At the moment Telegram won't let bots delete sent inline query results.
|
|
||||||
"""
|
|
||||||
if type(when) is int:
|
|
||||||
when = datetime.datetime.now() + datetime.timedelta(seconds=when)
|
|
||||||
assert type(when) is datetime.datetime, (
|
|
||||||
"when must be a datetime instance or a number of seconds (int) "
|
|
||||||
"to be awaited"
|
|
||||||
)
|
|
||||||
if 'inline_message_id' not in update:
|
|
||||||
logging.info(
|
|
||||||
"This inline query result owns no inline_keyboard, so it "
|
|
||||||
"can't be modified"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
inline_message_id = update['inline_message_id']
|
|
||||||
self.to_be_obscured.append(inline_message_id)
|
|
||||||
await sleep_until(when)
|
|
||||||
try:
|
|
||||||
await self.editMessageCaption(
|
|
||||||
inline_message_id=inline_message_id,
|
|
||||||
text="Time over"
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
await self.editMessageText(
|
|
||||||
inline_message_id=inline_message_id,
|
|
||||||
text="Time over"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(
|
|
||||||
"Couldn't obscure message\n{}\n\n{}".format(
|
|
||||||
inline_message_id,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.to_be_obscured.remove(inline_message_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
async def save_picture(self, update, file_name=None, path='img/',
|
|
||||||
extension='jpg'):
|
|
||||||
"""Store `update` picture as `path`/`file_name`.`extension`."""
|
|
||||||
if not path.endswith('/'):
|
|
||||||
path = '{p}/'.format(
|
|
||||||
p=path
|
|
||||||
)
|
|
||||||
if not os.path.isdir(path):
|
|
||||||
path = '{path}/img/'.format(
|
|
||||||
path=self.path
|
|
||||||
)
|
|
||||||
if file_name is None:
|
|
||||||
file_name = get_secure_key(length=6)
|
|
||||||
if file_name.endswith('.'):
|
|
||||||
file_name = file_name[:-1]
|
|
||||||
complete_file_name = '{path}{name}.{ext}'.format(
|
|
||||||
path=self.path,
|
|
||||||
name=file_name,
|
|
||||||
ext=extension
|
|
||||||
)
|
|
||||||
while os.path.isfile(complete_file_name):
|
|
||||||
file_name += get_secure_key(length=1)
|
|
||||||
complete_file_name = '{path}{name}.{ext}'.format(
|
|
||||||
path=self.path,
|
|
||||||
name=file_name,
|
|
||||||
ext=extension
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await self.download_file(
|
|
||||||
update['photo'][-1]['file_id'],
|
|
||||||
complete_file_name
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
return dict(
|
|
||||||
result=1, # Error
|
|
||||||
file_name=None,
|
|
||||||
error=e
|
|
||||||
)
|
|
||||||
return dict(
|
|
||||||
result=0, # Success
|
|
||||||
file_name=complete_file_name,
|
|
||||||
error=None
|
|
||||||
)
|
|
||||||
|
|
||||||
def stop_bots(self):
|
|
||||||
"""Exit script with code 0.
|
|
||||||
|
|
||||||
This method is deprecated: use `Bot.stop` instead.
|
|
||||||
"""
|
|
||||||
self.__class__.stop(
|
|
||||||
message=f"Stopping bots via bot `@{self.name}` method.",
|
|
||||||
final_state=0
|
|
||||||
)
|
|
||||||
|
|
||||||
def restart_bots(self):
|
|
||||||
"""Restart the script exiting with code 65.
|
|
||||||
|
|
||||||
This method is deprecated: use `Bot.stop` instead.
|
|
||||||
"""
|
|
||||||
self.__class__.stop(
|
|
||||||
message=f"Restarting bots via bot `@{self.name}` method.",
|
|
||||||
final_state=65
|
|
||||||
)
|
|
||||||
|
|
||||||
async def delete_and_obscure_messages(self):
|
|
||||||
"""Run after stop, before the script exits.
|
|
||||||
|
|
||||||
Await final tasks, obscure and delete pending messages,
|
|
||||||
log current operation (stop/restart).
|
|
||||||
"""
|
|
||||||
for message in self.to_be_destroyed:
|
|
||||||
try:
|
|
||||||
await self.delete_message(message)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(
|
|
||||||
"Couldn't delete message\n{}\n\n{}".format(
|
|
||||||
message,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for inline_message_id in self.to_be_obscured:
|
|
||||||
try:
|
|
||||||
await self.editMessageCaption(
|
|
||||||
inline_message_id,
|
|
||||||
text="Time over"
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
await self.editMessageText(
|
|
||||||
inline_message_id=inline_message_id,
|
|
||||||
text="Time over"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(
|
|
||||||
"Couldn't obscure message\n{}\n\n{}".format(
|
|
||||||
inline_message_id,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def run(cls, loop=None, *args, **kwargs):
|
|
||||||
"""Call this method to run the async bots.
|
|
||||||
|
|
||||||
This method is deprecated: use `super(Bot, cls).run` instead.
|
|
||||||
`loop` must not be determined outside that method.
|
|
||||||
"""
|
|
||||||
for bot in cls.bots:
|
|
||||||
bot.additional_task('AFTER')(bot.delete_and_obscure_messages)
|
|
||||||
return super(Bot, cls).run(*args, **kwargs)
|
|
Loading…
x
Reference in New Issue
Block a user