Initial commit
This commit is contained in:
161
.gitignore
vendored
Normal file
161
.gitignore
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
pyproject.toml
|
||||
setup.cfg
|
||||
--------
|
||||
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit tests / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# Visual studio code
|
||||
.vscode
|
||||
|
||||
#private keys and certs
|
||||
cert.pem
|
||||
private.key
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
|
||||
# Databases
|
||||
*.db
|
||||
|
||||
# Deprecateds
|
||||
*.dep
|
||||
|
||||
# Telethon sessions
|
||||
*.session*
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 AlberLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
16
README.rst
Normal file
16
README.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
FlanaBot
|
||||
========
|
||||
|
||||
.. image:: https://img.shields.io/github/license/AlberLC/flanabot?style=flat
|
||||
:target: https://github.com/AlberLC/flanabot/blob/main/LICENSE
|
||||
:alt: License
|
||||
|
||||
Flanagan's bot.
|
||||
|
||||
Installation
|
||||
------------
|
||||
Python 3.10 or higher is required.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pip install flanabot
|
||||
806
flanabot/bots/flana_bot.py
Normal file
806
flanabot/bots/flana_bot.py
Normal file
@@ -0,0 +1,806 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import itertools
|
||||
import pprint
|
||||
import random
|
||||
from abc import ABC
|
||||
from typing import Iterable, Iterator, Type
|
||||
|
||||
import flanaapis.geolocation.functions
|
||||
import flanaapis.weather.functions
|
||||
import flanautils
|
||||
import plotly.graph_objects
|
||||
from flanaapis import InstagramLoginError, MediaNotFoundError, Place, PlaceNotFoundError, WeatherEmoji, instagram, tiktok, twitter
|
||||
from flanautils import Media, MediaType, NotFoundError, OrderedSet, Source, TimeUnits, TraceMetadata, return_if_first_empty
|
||||
from multibot import Action, BotAction, MultiBot, SendError, admin, bot_mentioned, constants as multibot_constants, group, ignore_self_message, inline, reply
|
||||
|
||||
from flanabot import constants
|
||||
from flanabot.exceptions import BadRoleError, UserDisconnectedError
|
||||
from flanabot.models.chat import Chat
|
||||
from flanabot.models.message import Message
|
||||
from flanabot.models.punishments import Mute, Punishment, PunishmentBase
|
||||
from flanabot.models.weather_chart import WeatherChart
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------- #
|
||||
# --------------------------------------------- FLANA_BOT --------------------------------------------- #
|
||||
# ----------------------------------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
class FlanaBot(MultiBot, ABC):
|
||||
# ----------------------------------------------------------- #
|
||||
# -------------------- PROTECTED METHODS -------------------- #
|
||||
# ----------------------------------------------------------- #
|
||||
def _add_handlers(self):
|
||||
super()._add_handlers()
|
||||
|
||||
self.register(self._on_bye, constants.KEYWORDS['bye'], min_ratio=1)
|
||||
|
||||
self.register(self._on_config_list_show, constants.KEYWORDS['config'])
|
||||
self.register(self._on_config_list_show, constants.KEYWORDS['help'])
|
||||
self.register(self._on_config_list_show, (constants.KEYWORDS['show'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_config_list_show, (constants.KEYWORDS['help'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_config_list_show, (constants.KEYWORDS['show'], constants.KEYWORDS['help']))
|
||||
self.register(self._on_config_list_show, (constants.KEYWORDS['show'], constants.KEYWORDS['help'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_covid_chart, constants.KEYWORDS['covid_chart'])
|
||||
|
||||
self.register(self._on_currency_chart, constants.KEYWORDS['currency_chart'])
|
||||
self.register(self._on_currency_chart, (constants.KEYWORDS['show'], constants.KEYWORDS['currency_chart']))
|
||||
|
||||
self.register(self._on_currency_chart_config_activate, (constants.KEYWORDS['activate'], constants.KEYWORDS['currency_chart']))
|
||||
self.register(self._on_currency_chart_config_activate, (constants.KEYWORDS['activate'], constants.KEYWORDS['currency_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_currency_chart_config_change, (constants.KEYWORDS['change'], constants.KEYWORDS['currency_chart']))
|
||||
self.register(self._on_currency_chart_config_change, (constants.KEYWORDS['change'], constants.KEYWORDS['currency_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_currency_chart_config_deactivate, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['currency_chart']))
|
||||
self.register(self._on_currency_chart_config_deactivate, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['currency_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_currency_chart_config_show, (constants.KEYWORDS['currency_chart'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_currency_chart_config_show, (constants.KEYWORDS['show'], constants.KEYWORDS['currency_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_delete_original_config_activate, (constants.KEYWORDS['activate'], multibot_constants.KEYWORDS['delete']))
|
||||
self.register(self._on_delete_original_config_activate, (constants.KEYWORDS['activate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
|
||||
self.register(self._on_delete_original_config_activate, (constants.KEYWORDS['activate'], multibot_constants.KEYWORDS['delete'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_delete_original_config_activate, (constants.KEYWORDS['activate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_delete_original_config_change, (constants.KEYWORDS['change'], multibot_constants.KEYWORDS['delete']))
|
||||
self.register(self._on_delete_original_config_change, (constants.KEYWORDS['change'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
|
||||
self.register(self._on_delete_original_config_change, (constants.KEYWORDS['change'], multibot_constants.KEYWORDS['delete'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_delete_original_config_change, (constants.KEYWORDS['change'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_delete_original_config_deactivate, (constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['delete']))
|
||||
self.register(self._on_delete_original_config_deactivate, (constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
|
||||
self.register(self._on_delete_original_config_deactivate, (constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['delete'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_delete_original_config_deactivate, (constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_delete_original_config_show, (constants.KEYWORDS['show'], multibot_constants.KEYWORDS['delete']))
|
||||
self.register(self._on_delete_original_config_show, (multibot_constants.KEYWORDS['delete'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_delete_original_config_show, (constants.KEYWORDS['show'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
|
||||
self.register(self._on_delete_original_config_show, (constants.KEYWORDS['show'], multibot_constants.KEYWORDS['delete'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_delete_original_config_show, (multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_delete_original_config_show, (constants.KEYWORDS['show'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_hello, constants.KEYWORDS['hello'])
|
||||
|
||||
self.register(self._on_mute, constants.KEYWORDS['mute'])
|
||||
self.register(self._on_mute, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['unmute']))
|
||||
self.register(self._on_mute, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['sound']))
|
||||
|
||||
self.register(self._on_new_message_default, default=True)
|
||||
|
||||
self.register(self._on_no_delete_original, (constants.KEYWORDS['negate'], multibot_constants.KEYWORDS['delete']))
|
||||
self.register(self._on_no_delete_original, (constants.KEYWORDS['negate'], multibot_constants.KEYWORDS['message']))
|
||||
self.register(self._on_no_delete_original, (constants.KEYWORDS['negate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
|
||||
self.register(self._on_no_delete_original, (constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
|
||||
|
||||
self.register(self._on_punish, constants.KEYWORDS['punish'])
|
||||
self.register(self._on_punish, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['unpunish']))
|
||||
self.register(self._on_punish, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['permission']))
|
||||
|
||||
self.register(self._on_recover_message, (constants.KEYWORDS['reset'], multibot_constants.KEYWORDS['message']))
|
||||
|
||||
self.register(self._on_scraping, constants.KEYWORDS['scraping'])
|
||||
|
||||
self.register(self._on_scraping_config_activate, (constants.KEYWORDS['activate'], constants.KEYWORDS['scraping']))
|
||||
self.register(self._on_scraping_config_activate, (constants.KEYWORDS['activate'], constants.KEYWORDS['scraping'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_scraping_config_change, (constants.KEYWORDS['change'], constants.KEYWORDS['scraping']))
|
||||
self.register(self._on_scraping_config_change, (constants.KEYWORDS['change'], constants.KEYWORDS['scraping'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_scraping_config_deactivate, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['scraping']))
|
||||
self.register(self._on_scraping_config_deactivate, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['scraping'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_scraping_config_show, (constants.KEYWORDS['show'], constants.KEYWORDS['scraping']))
|
||||
self.register(self._on_scraping_config_show, (constants.KEYWORDS['scraping'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_scraping_config_show, (constants.KEYWORDS['show'], constants.KEYWORDS['scraping'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_song_info, constants.KEYWORDS['song_info'])
|
||||
|
||||
self.register(self._on_unmute, constants.KEYWORDS['unmute'])
|
||||
self.register(self._on_unmute, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['mute']))
|
||||
self.register(self._on_unmute, (constants.KEYWORDS['activate'], constants.KEYWORDS['sound']))
|
||||
|
||||
self.register(self._on_unpunish, constants.KEYWORDS['unpunish'])
|
||||
|
||||
self.register(self._on_unpunish, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['punish']))
|
||||
self.register(self._on_unpunish, (constants.KEYWORDS['activate'], constants.KEYWORDS['permission']))
|
||||
|
||||
self.register(self._on_weather_chart, constants.KEYWORDS['weather_chart'])
|
||||
self.register(self._on_weather_chart, (constants.KEYWORDS['show'], constants.KEYWORDS['weather_chart']))
|
||||
|
||||
self.register(self._on_weather_chart_config_activate, (constants.KEYWORDS['activate'], constants.KEYWORDS['weather_chart']))
|
||||
self.register(self._on_weather_chart_config_activate, (constants.KEYWORDS['activate'], constants.KEYWORDS['weather_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_weather_chart_config_change, (constants.KEYWORDS['change'], constants.KEYWORDS['weather_chart']))
|
||||
self.register(self._on_weather_chart_config_change, (constants.KEYWORDS['change'], constants.KEYWORDS['weather_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_weather_chart_config_deactivate, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['weather_chart']))
|
||||
self.register(self._on_weather_chart_config_deactivate, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['weather_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
self.register(self._on_weather_chart_config_show, (constants.KEYWORDS['weather_chart'], constants.KEYWORDS['config']))
|
||||
self.register(self._on_weather_chart_config_show, (constants.KEYWORDS['show'], constants.KEYWORDS['weather_chart'], constants.KEYWORDS['config']))
|
||||
|
||||
async def _change_config(self, config_name: str, message: Message, value: bool = None):
|
||||
if value is None:
|
||||
value = not message.chat.config[config_name]
|
||||
|
||||
message.chat.config[config_name] = value
|
||||
message.chat.save()
|
||||
|
||||
await self.send(f"He {'activado ✔' if value else 'desactivado ❌'} {config_name}.", message)
|
||||
|
||||
@admin(False)
|
||||
@group
|
||||
async def _check_message_flood(self, message: Message):
|
||||
if message.author.is_punished:
|
||||
return
|
||||
|
||||
last_2s_messages = Message.find({
|
||||
'author': message.author.object_id,
|
||||
'last_update': {
|
||||
'$gte': datetime.datetime.now() - datetime.timedelta(seconds=2)
|
||||
}
|
||||
})
|
||||
last_7s_messages = Message.find({
|
||||
'author': message.author.object_id,
|
||||
'last_update': {
|
||||
'$gte': datetime.datetime.now() - datetime.timedelta(seconds=7),
|
||||
'$lt': datetime.datetime.now() - datetime.timedelta(seconds=2)
|
||||
}
|
||||
})
|
||||
|
||||
if len(last_2s_messages) >= 5 or len(last_7s_messages) >= 7:
|
||||
n_punishments = len(Punishment.find({'user_id': message.author.id, 'group_id': message.chat.group_id}))
|
||||
punishment_seconds = (n_punishments + 2) ** constants.PUNISHMENT_INCREMENT_EXPONENT
|
||||
try:
|
||||
await self.punish(message.author.id, message.chat.group_id, punishment_seconds, message)
|
||||
except BadRoleError as e:
|
||||
await self._manage_exceptions(e, message)
|
||||
else:
|
||||
# noinspection PyTypeChecker
|
||||
Punishment(message.author.id, message.author.group_id, datetime.datetime.now() + datetime.timedelta(punishment_seconds)).save()
|
||||
await self.send(f'Castigado durante {TimeUnits(punishment_seconds).to_words()}.', message)
|
||||
|
||||
@staticmethod
|
||||
async def _check_messages():
|
||||
Message.collection.delete_many({'last_update': {'$lte': datetime.datetime.now() - multibot_constants.MESSAGE_EXPIRATION_TIME}})
|
||||
|
||||
async def _check_mutes(self):
|
||||
mute_groups = self._get_grouped_punishments(Mute)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
for (user_id, group_id), sorted_mutes in mute_groups:
|
||||
if (last_mute := sorted_mutes[-1]).until <= now:
|
||||
await self.unmute(user_id, group_id)
|
||||
last_mute.delete()
|
||||
|
||||
async def _check_punishments(self):
|
||||
punishment_groups = self._get_grouped_punishments(Punishment)
|
||||
|
||||
now = datetime.datetime.now()
|
||||
for (user_id, group_id), sorted_punishments in punishment_groups:
|
||||
if now < (last_punishment := sorted_punishments[-1]).until:
|
||||
continue
|
||||
|
||||
if last_punishment.until + constants.PUNISHMENTS_RESET <= now:
|
||||
for old_punishment in sorted_punishments:
|
||||
old_punishment.delete()
|
||||
|
||||
if last_punishment.is_active:
|
||||
await self.unpunish(user_id, group_id)
|
||||
last_punishment.is_active = False
|
||||
last_punishment.save()
|
||||
|
||||
@staticmethod
|
||||
def _get_grouped_punishments(PunishmentClass: Type[PunishmentBase]) -> tuple[tuple[tuple[int, int], list[PunishmentBase]]]:
|
||||
sorted_punishments = PunishmentClass.find(sort_keys=('user_id', 'group_id', 'until'))
|
||||
group_iterator: Iterator[
|
||||
tuple[
|
||||
tuple[int, int],
|
||||
Iterator[PunishmentClass]
|
||||
]
|
||||
] = itertools.groupby(sorted_punishments, key=lambda punishment: (punishment.user_id, punishment.group_id))
|
||||
return tuple(((user_id, group_id), list(group_)) for (user_id, group_id), group_ in group_iterator)
|
||||
|
||||
@return_if_first_empty(exclude_self_types='FlanaBot', globals_=globals())
|
||||
async def _manage_exceptions(self, exceptions: BaseException | Iterable[BaseException], message: Message):
|
||||
if not isinstance(exceptions, Iterable):
|
||||
exceptions = (exceptions,)
|
||||
|
||||
for exception in exceptions:
|
||||
try:
|
||||
raise exception
|
||||
except BadRoleError as e:
|
||||
await self.send_error(f'Rol no encontrado en {e}', message)
|
||||
except InstagramLoginError as e:
|
||||
await self.send_error(f'No me puedo loguear en Instagram {random.choice(multibot_constants.SAD_EMOJIS)} 👉 {e}', message)
|
||||
except MediaNotFoundError as e:
|
||||
await self.send_error(f'No he podido sacar nada de {e.source} {random.choice(multibot_constants.SAD_EMOJIS)}', message)
|
||||
except PlaceNotFoundError as e:
|
||||
await self.send_error(f'No he podido encontrar "{e}" {random.choice(multibot_constants.SAD_EMOJIS)}', message)
|
||||
except Exception as e:
|
||||
await super()._manage_exceptions(e, message)
|
||||
|
||||
@staticmethod
|
||||
def _medias_sended_info(medias: Iterable[Media]) -> str:
|
||||
medias_count = {
|
||||
Source.TWITTER: {MediaType.IMAGE: 0, MediaType.AUDIO: 0, MediaType.GIF: 0, MediaType.VIDEO: 0, None: 0, MediaType.ERROR: 0},
|
||||
Source.INSTAGRAM: {MediaType.IMAGE: 0, MediaType.AUDIO: 0, MediaType.GIF: 0, MediaType.VIDEO: 0, None: 0, MediaType.ERROR: 0},
|
||||
Source.TIKTOK: {MediaType.IMAGE: 0, MediaType.AUDIO: 0, MediaType.GIF: 0, MediaType.VIDEO: 0, None: 0, MediaType.ERROR: 0},
|
||||
Source.REDDIT: {MediaType.IMAGE: 0, MediaType.AUDIO: 0, MediaType.GIF: 0, MediaType.VIDEO: 0, None: 0, MediaType.ERROR: 0}
|
||||
}
|
||||
for media in medias:
|
||||
medias_count[media.source][media.type_] += 1
|
||||
|
||||
medias_sended_info = []
|
||||
for source, media_type_count in medias_count.items():
|
||||
source_medias_sended_info = []
|
||||
for media_type, count in media_type_count.items():
|
||||
if count:
|
||||
if count == 1:
|
||||
type_text = {MediaType.IMAGE: 'imagen',
|
||||
MediaType.AUDIO: 'audio',
|
||||
MediaType.GIF: 'gif',
|
||||
MediaType.VIDEO: 'vídeo',
|
||||
None: 'cosa que no sé que tipo de archivo es',
|
||||
MediaType.ERROR: 'error'}[media_type]
|
||||
else:
|
||||
type_text = {MediaType.IMAGE: 'imágenes',
|
||||
MediaType.AUDIO: 'audios',
|
||||
MediaType.GIF: 'gifs',
|
||||
MediaType.VIDEO: 'vídeos',
|
||||
None: 'cosas que no sé que tipos de archivos son',
|
||||
MediaType.ERROR: 'errores'}[media_type]
|
||||
source_medias_sended_info.append(f'{count} {type_text}')
|
||||
if source_medias_sended_info:
|
||||
medias_sended_info.append(f"{flanautils.join_last_separator(source_medias_sended_info, ', ', ' y ')} de {source.name}")
|
||||
|
||||
medias_sended_info_joined = flanautils.join_last_separator(medias_sended_info, ',\n', ' y\n')
|
||||
new_line = ' ' if len(medias_sended_info) == 1 else '\n'
|
||||
return f'{new_line}{medias_sended_info_joined}:'
|
||||
|
||||
async def _mute(self, user_id: int, group_id: int):
|
||||
pass
|
||||
|
||||
async def _punish(self, user_id: int, group_id: int):
|
||||
pass
|
||||
|
||||
async def _search_and_send_medias(self, message: Message, send_song_info=False) -> list[Message]:
|
||||
sended_media_messages = []
|
||||
if medias := await self._search_medias(message):
|
||||
sended_media_messages, _ = await self.send_medias(medias, message, send_song_info)
|
||||
|
||||
return sended_media_messages
|
||||
|
||||
async def _search_medias(self, message: Message) -> OrderedSet[Media]:
|
||||
results: tuple[OrderedSet[Media] | BaseException, ...] = await asyncio.gather(
|
||||
twitter.get_medias(message.text),
|
||||
instagram.get_medias(message.text),
|
||||
tiktok.get_medias(message.text),
|
||||
return_exceptions=True
|
||||
)
|
||||
results, exceptions = flanautils.filter_exceptions(results)
|
||||
|
||||
await self._manage_exceptions(exceptions, message)
|
||||
|
||||
return OrderedSet(*results)
|
||||
|
||||
async def _show_config(self, config_name: str, message: Message):
|
||||
await self.send(f"{config_name} está {'activado ✔' if message.chat.config.get(config_name) else 'desactivado ❌'}", message)
|
||||
|
||||
async def _unmute(self, user_id: int, group_id: int):
|
||||
pass
|
||||
|
||||
async def _unpunish(self, user_id: int, group_id: int):
|
||||
pass
|
||||
|
||||
# ---------------------------------------------- #
|
||||
# HANDLERS #
|
||||
# ---------------------------------------------- #
|
||||
async def _on_bye(self, message: Message):
|
||||
if not message.chat.is_group or self.is_bot_mentioned(message):
|
||||
await self.send_bye(message)
|
||||
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_config_list_show(self, message: Message):
|
||||
config_info = pprint.pformat(message.chat.config)
|
||||
config_info = flanautils.translate(config_info, {'{': None, '}': None, ',': None, 'True': '✔', "'": None, 'False': '❌'})
|
||||
config_info = config_info.splitlines()
|
||||
config_info = '\n'.join(config_info_.strip() for config_info_ in config_info)
|
||||
await self.send(f'<b>Estos son los ajustes del grupo:</b>\n\n<code>{config_info}</code>', message)
|
||||
|
||||
async def _on_covid_chart(self, message: Message): # todo2
|
||||
pass
|
||||
|
||||
async def _on_currency_chart(self, message: Message): # todo2
|
||||
pass
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_currency_chart_config_activate(self, message: Message):
|
||||
await self._change_config('auto_currency_chart', message, True)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_currency_chart_config_change(self, message: Message):
|
||||
await self._change_config('auto_currency_chart', message)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_currency_chart_config_deactivate(self, message: Message):
|
||||
await self._change_config('auto_currency_chart', message, False)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_currency_chart_config_show(self, message: Message):
|
||||
await self._show_config('auto_currency_chart', message)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_delete_original_config_activate(self, message: Message):
|
||||
await self._change_config('auto_delete_original', message, True)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_delete_original_config_change(self, message: Message):
|
||||
await self._change_config('auto_delete_original', message)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_delete_original_config_deactivate(self, message: Message):
|
||||
await self._change_config('auto_delete_original', message, False)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_delete_original_config_show(self, message: Message):
|
||||
await self._show_config('auto_delete_original', message)
|
||||
|
||||
async def _on_hello(self, message: Message):
|
||||
if not message.chat.is_group or self.is_bot_mentioned(message):
|
||||
await self.send_hello(message)
|
||||
|
||||
@group
|
||||
@bot_mentioned
|
||||
@admin(send_negative=True)
|
||||
async def _on_mute(self, message: Message):
|
||||
await self._update_punishment(self.mute, message, time=flanautils.words_to_time(message.text))
|
||||
|
||||
async def _on_new_message_default(self, message: Message):
|
||||
if message.is_inline:
|
||||
await self._on_scraping(message)
|
||||
elif (
|
||||
message.chat.is_group
|
||||
and
|
||||
not self.is_bot_mentioned(message)
|
||||
and
|
||||
not message.chat.config['auto_scraping']
|
||||
or
|
||||
not await self._on_scraping(message)
|
||||
):
|
||||
await self.send_bad_phrase(message)
|
||||
|
||||
@ignore_self_message
|
||||
async def _on_new_message_raw(self, message: Message):
|
||||
await super()._on_new_message_raw(message)
|
||||
if not message.is_inline:
|
||||
await self._check_message_flood(message)
|
||||
|
||||
async def _on_no_delete_original(self, message: Message):
|
||||
if not await self._on_scraping(message, delete_original=False):
|
||||
await self._on_recover_message(message)
|
||||
|
||||
@bot_mentioned
|
||||
@group
|
||||
@admin(send_negative=True)
|
||||
async def _on_punish(self, message: Message):
|
||||
await self._update_punishment(self.punish, message, time=flanautils.words_to_time(message.text))
|
||||
|
||||
async def _on_ready(self):
|
||||
await super()._on_ready()
|
||||
await flanautils.do_every(constants.CHECK_MUTES_EVERY_SECONDS, self._check_mutes)
|
||||
await flanautils.do_every(constants.CHECK_MESSAGE_EVERY_SECONDS, self._check_messages)
|
||||
await flanautils.do_every(constants.CHECK_PUNISHMENTS_EVERY_SECONDS, self._check_punishments)
|
||||
|
||||
@inline(False)
|
||||
async def _on_recover_message(self, message: Message):
|
||||
if message.replied_message:
|
||||
message_deleted_bot_action = BotAction.find_one({'action': bytes(Action.MESSAGE_DELETED), 'chat': message.chat.object_id, 'affected_objects': message.replied_message.object_id})
|
||||
elif self.is_bot_mentioned(message):
|
||||
message_deleted_bot_action = BotAction.find_one({'action': bytes(Action.MESSAGE_DELETED), 'chat': message.chat.object_id, 'date': {'$gt': datetime.datetime.now() - constants.RECOVERY_DELETED_MESSAGE_BEFORE}})
|
||||
else:
|
||||
return
|
||||
|
||||
if not message_deleted_bot_action:
|
||||
return
|
||||
|
||||
affected_object_ids = [affected_message_object_id for affected_message_object_id in message_deleted_bot_action.affected_objects]
|
||||
deleted_messages: list[Message] = [affected_message for affected_object_id in affected_object_ids if (affected_message := Message.find_one({'_id': affected_object_id})).author.id != self.bot_id]
|
||||
for deleted_message in deleted_messages:
|
||||
await self.send(deleted_message.text, message)
|
||||
|
||||
async def _on_scraping(self, message: Message, delete_original: bool = None) -> OrderedSet[Media]:
|
||||
sended_media_messages = await self._search_and_send_medias(message.replied_message) if message.replied_message else OrderedSet()
|
||||
sended_media_messages += await self._search_and_send_medias(message)
|
||||
await self.send_inline_results(message)
|
||||
|
||||
if (
|
||||
sended_media_messages
|
||||
and
|
||||
message.chat.is_group
|
||||
and
|
||||
(
|
||||
(
|
||||
delete_original is None
|
||||
and
|
||||
message.chat.config['auto_delete_original']
|
||||
)
|
||||
or
|
||||
(
|
||||
delete_original is not None
|
||||
and
|
||||
delete_original
|
||||
)
|
||||
)
|
||||
):
|
||||
# noinspection PyTypeChecker
|
||||
BotAction(Action.MESSAGE_DELETED, message, affected_objects=[message, *sended_media_messages]).save()
|
||||
await self.delete_message(message)
|
||||
|
||||
return sended_media_messages
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_scraping_config_activate(self, message: Message):
|
||||
await self._change_config('auto_scraping', message, True)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_scraping_config_change(self, message: Message):
|
||||
await self._change_config('auto_scraping', message)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_scraping_config_deactivate(self, message: Message):
|
||||
await self._change_config('auto_scraping', message, False)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_scraping_config_show(self, message: Message):
|
||||
await self._show_config('auto_scraping', message)
|
||||
|
||||
@reply
|
||||
async def _on_song_info(self, message: Message):
|
||||
song_infos = message.replied_message.song_infos if message.replied_message else []
|
||||
|
||||
if song_infos:
|
||||
for song_info in song_infos:
|
||||
await self.send_song_info(song_info, message)
|
||||
elif self.is_bot_mentioned(message) or not message.chat.is_group:
|
||||
await self._manage_exceptions(SendError('No hay información musical en ese mensaje.'), message)
|
||||
|
||||
@group
|
||||
@bot_mentioned
|
||||
@admin(send_negative=True)
|
||||
async def _on_unmute(self, message: Message):
|
||||
await self._update_punishment(self.unmute, message)
|
||||
|
||||
@group
|
||||
@bot_mentioned
|
||||
@admin(send_negative=True)
|
||||
async def _on_unpunish(self, message: Message):
|
||||
await self._update_punishment(self.unpunish, message)
|
||||
|
||||
async def _on_weather_chart(self, message: Message):
|
||||
bot_state_message: Message | None = None
|
||||
if message.is_inline:
|
||||
show_progress_state = False
|
||||
elif message.chat.is_group and not self.is_bot_mentioned(message):
|
||||
if message.chat.config['auto_weather_chart']:
|
||||
if BotAction.find_one({'action': bytes(Action.AUTO_WEATHER_CHART), 'chat': message.chat.object_id, 'date': {'$gt': datetime.datetime.now() - constants.AUTO_WEATHER_EVERY}}):
|
||||
return
|
||||
show_progress_state = False
|
||||
else:
|
||||
return
|
||||
else:
|
||||
show_progress_state = True
|
||||
|
||||
user_names_with_at_sign = {user.name.lower() for user in message.chat.users}
|
||||
user_names_without_at_sign = {user.name.lower().replace('@', '') for user in message.chat.users}
|
||||
original_text_words = flanautils.remove_accents(message.text.lower())
|
||||
original_text_words = flanautils.translate(
|
||||
original_text_words,
|
||||
{symbol: None for symbol in set(flanautils.SYMBOLS) - {'-', '.'}}
|
||||
).split()
|
||||
place_words = (
|
||||
OrderedSet(original_text_words)
|
||||
- flanautils.cartesian_product_string_matching(original_text_words, constants.KEYWORDS['show'], min_ratio=0.8).keys()
|
||||
- flanautils.cartesian_product_string_matching(original_text_words, constants.KEYWORDS['weather_chart'], min_ratio=0.8).keys()
|
||||
- flanautils.cartesian_product_string_matching(original_text_words, constants.KEYWORDS['date'], min_ratio=0.8).keys()
|
||||
- flanautils.cartesian_product_string_matching(original_text_words, constants.KEYWORDS['thanks'], min_ratio=0.8).keys()
|
||||
- user_names_with_at_sign
|
||||
- user_names_without_at_sign
|
||||
- flanautils.CommonWords.words
|
||||
)
|
||||
if not place_words:
|
||||
await self.send_error(random.choice(('¿Tiempo dónde?', 'Indica el sitio.', 'Y el sitio?', 'y el sitio? me lo invento?')), message)
|
||||
return
|
||||
|
||||
if 'calle' in original_text_words:
|
||||
place_words.insert(0, 'calle')
|
||||
place_query = ' '.join(place_words)
|
||||
if len(place_query) >= constants.MAX_PLACE_QUERY_LENGTH:
|
||||
await self.send_error(Media('resources/mucho_texto.png'), message)
|
||||
return
|
||||
if show_progress_state:
|
||||
bot_state_message = await self.send(f'Buscando "{place_query}" en el mapa 🧐...', message)
|
||||
|
||||
result: str | Place | None = None
|
||||
async for result in flanaapis.geolocation.functions.find_place_showing_progress(place_query):
|
||||
if isinstance(result, str) and bot_state_message:
|
||||
await self.edit(result, bot_state_message)
|
||||
|
||||
place: Place = result
|
||||
if not place:
|
||||
if bot_state_message:
|
||||
await self.delete_message(bot_state_message)
|
||||
await self._manage_exceptions(PlaceNotFoundError(place_query), message)
|
||||
return
|
||||
|
||||
if bot_state_message:
|
||||
bot_state_message = await self.edit(f'Obteniendo datos del tiempo para "{place_query}"...', bot_state_message)
|
||||
current_weather, day_weathers = await flanaapis.weather.functions.get_day_weathers_by_place(place)
|
||||
|
||||
if bot_state_message:
|
||||
bot_state_message = await self.edit('Creando gráficas del tiempo...', bot_state_message)
|
||||
|
||||
weather_chart = WeatherChart(
|
||||
_font={'size': 30},
|
||||
_title={
|
||||
'text': place.name[:40].strip(' ,-'),
|
||||
'xref': 'paper',
|
||||
'yref': 'paper',
|
||||
'xanchor': 'left',
|
||||
'yanchor': 'top',
|
||||
'x': 0.025,
|
||||
'y': 0.975,
|
||||
'font': {
|
||||
'size': 50,
|
||||
'family': 'open sans'
|
||||
}
|
||||
},
|
||||
_legend={'x': 0.99, 'y': 0.99, 'xanchor': 'right', 'yanchor': 'top', 'bgcolor': 'rgba(0,0,0,0)'},
|
||||
_margin={'l': 20, 'r': 20, 't': 20, 'b': 20},
|
||||
trace_metadatas={
|
||||
'temperature': TraceMetadata(name='temperature', group='temperature', legend='Temperatura', show=False, color='#ff8400', default_min=0, default_max=40, y_tick_suffix=' ºC', y_axis_width=130),
|
||||
'temperature_feel': TraceMetadata(name='temperature_feel', group='temperature', legend='Sensación de temperatura', show=True, color='red', default_min=0, default_max=40, y_tick_suffix=' ºC', y_axis_width=130),
|
||||
'clouds': TraceMetadata(name='clouds', legend='Nubes', show=False, color='#86abe3', default_min=-100, default_max=100, y_tick_suffix=' %', hide_y_ticks_if='{tick} < 0'),
|
||||
'visibility': TraceMetadata(name='visibility', legend='Visibilidad', show=False, color='#c99a34', default_min=0, default_max='{max_y_data} * 2', y_tick_suffix=' km', y_delta_tick=2, hide_y_ticks_if='{tick} > {max_y_data}'),
|
||||
'uvi': TraceMetadata(name='uvi', legend='UVI', show=False, color='#ffd000', default_min=-12, default_max=12, hide_y_ticks_if='{tick} < 0', y_delta_tick=1, y_axis_width=75),
|
||||
'humidity': TraceMetadata(name='humidity', legend='Humedad', show=False, color='#2baab5', default_min=0, default_max=100, y_tick_suffix=' %'),
|
||||
'precipitation_probability': TraceMetadata(name='precipitation_probability', legend='Probabilidad de precipitaciones', show=True, color='#0033ff', default_min=-100, default_max=100, y_tick_suffix=' %', hide_y_ticks_if='{tick} < 0'),
|
||||
'rain_volume': TraceMetadata(plotly.graph_objects.Histogram, name='rain_volume', group='precipitation', legend='Volumen de lluvia', show=True, color='#34a4eb', opacity=0.3, default_min=-10, default_max=10, y_tick_suffix=' mm', y_delta_tick=1, hide_y_ticks_if='{tick} < 0', y_axis_width=130),
|
||||
'snow_volume': TraceMetadata(plotly.graph_objects.Histogram, name='snow_volume', group='precipitation', legend='Volumen de nieve', show=True, color='#34a4eb', opacity=0.8, pattern={'shape': '.', 'fgcolor': '#ffffff', 'bgcolor': '#b0d6f3', 'solidity': 0.5, 'size': 14}, default_min=-10, default_max=10, y_tick_suffix=' mm', y_delta_tick=1, hide_y_ticks_if='{tick} < 0', y_axis_width=130),
|
||||
'pressure': TraceMetadata(name='pressure', legend='Presión', show=False, color='#31a339', default_min=1013.25 - 90, default_max=1013.25 + 90, y_tick_suffix=' hPa', y_axis_width=225),
|
||||
'wind_speed': TraceMetadata(name='wind_speed', legend='Velocidad del viento', show=False, color='#d8abff', default_min=-120, default_max=120, y_tick_suffix=' km/h', hide_y_ticks_if='{tick} < 0', y_axis_width=165)
|
||||
},
|
||||
x_data=[instant_weather.date_time for day_weather in day_weathers for instant_weather in day_weather.instant_weathers],
|
||||
all_y_data=[],
|
||||
current_weather=current_weather,
|
||||
day_weathers=day_weathers,
|
||||
timezone=(timezone := next(iter(day_weathers)).timezone),
|
||||
place=place,
|
||||
view_position=datetime.datetime.now(timezone)
|
||||
)
|
||||
|
||||
weather_chart.apply_zoom()
|
||||
weather_chart.draw()
|
||||
if not (image_bytes := weather_chart.to_image()):
|
||||
if bot_state_message:
|
||||
await self.delete_message(bot_state_message)
|
||||
raise NotFoundError('No hay suficientes datos del tiempo.')
|
||||
|
||||
if bot_state_message:
|
||||
bot_state_message = await self.edit('Enviando...', bot_state_message)
|
||||
bot_message: Message = await self.send(
|
||||
Media(image_bytes, MediaType.IMAGE),
|
||||
[
|
||||
[WeatherEmoji.ZOOM_IN.value, WeatherEmoji.ZOOM_OUT.value, WeatherEmoji.LEFT.value, WeatherEmoji.RIGHT.value],
|
||||
[WeatherEmoji.TEMPERATURE.value, WeatherEmoji.TEMPERATURE_FEEL.value, WeatherEmoji.CLOUDS.value, WeatherEmoji.VISIBILITY.value, WeatherEmoji.UVI.value],
|
||||
[WeatherEmoji.HUMIDITY.value, WeatherEmoji.PRECIPITATION_PROBABILITY.value, WeatherEmoji.PRECIPITATION_VOLUME.value, WeatherEmoji.PRESSURE.value, WeatherEmoji.WIND_SPEED.value]
|
||||
],
|
||||
message,
|
||||
send_as_file=False
|
||||
)
|
||||
await self.send_inline_results(message)
|
||||
|
||||
if bot_state_message:
|
||||
await self.delete_message(bot_state_message)
|
||||
|
||||
if bot_message:
|
||||
bot_message.weather_chart = weather_chart
|
||||
bot_message.save()
|
||||
if not self.is_bot_mentioned(message):
|
||||
# noinspection PyTypeChecker
|
||||
BotAction(Action.AUTO_WEATHER_CHART, message, affected_objects=[bot_message]).save()
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_weather_chart_config_activate(self, message: Message):
|
||||
await self._change_config('auto_weather_chart', message, True)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_weather_chart_config_change(self, message: Message):
|
||||
await self._change_config('auto_weather_chart', message)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_weather_chart_config_deactivate(self, message: Message):
|
||||
await self._change_config('auto_weather_chart', message, False)
|
||||
|
||||
@admin
|
||||
@group
|
||||
@bot_mentioned
|
||||
async def _on_weather_chart_config_show(self, message: Message):
|
||||
await self._show_config('auto_weather_chart', message)
|
||||
|
||||
# -------------------------------------------------------- #
|
||||
# -------------------- PUBLIC METHODS -------------------- #
|
||||
# -------------------------------------------------------- #
|
||||
async def is_deaf(self, user_id: int, group_id: int) -> bool:
|
||||
pass
|
||||
|
||||
async def is_muted(self, user_id: int, group_id: int) -> bool:
|
||||
pass
|
||||
|
||||
async def mute(self, user_id: int, group_id: int, time: int | datetime.timedelta, message: Message = None):
|
||||
if isinstance(time, int):
|
||||
time = datetime.timedelta(seconds=time)
|
||||
|
||||
try:
|
||||
await self._mute(user_id, group_id)
|
||||
except UserDisconnectedError as e:
|
||||
await self._manage_exceptions(e, message if message else Message(chat=Chat(group_id=group_id)))
|
||||
else:
|
||||
if time:
|
||||
# noinspection PyTypeChecker
|
||||
Mute(user_id, group_id, until=datetime.datetime.now() + time).save()
|
||||
if datetime.timedelta() < time <= constants.TIME_THRESHOLD_TO_MANUAL_UNMUTE:
|
||||
await flanautils.do_later(time, self._check_mutes)
|
||||
else:
|
||||
# noinspection PyTypeChecker
|
||||
Mute(user_id, group_id).save()
|
||||
|
||||
async def punish(self, user_id: int, group_id: int, time: int | datetime.timedelta, message: Message = None):
|
||||
if isinstance(time, int):
|
||||
time = datetime.timedelta(seconds=time)
|
||||
|
||||
try:
|
||||
await self._punish(user_id, group_id)
|
||||
except BadRoleError as e:
|
||||
await self._manage_exceptions(e, message if message else Message(chat=Chat(group_id=group_id)))
|
||||
else:
|
||||
if time:
|
||||
# noinspection PyTypeChecker
|
||||
Punishment(user_id, group_id, until=datetime.datetime.now() + time).save()
|
||||
if datetime.timedelta() < time <= constants.TIME_THRESHOLD_TO_MANUAL_UNPUNISH:
|
||||
await flanautils.do_later(time, self._check_punishments)
|
||||
else:
|
||||
# noinspection PyTypeChecker
|
||||
Punishment(user_id, group_id).save()
|
||||
|
||||
async def send_bad_phrase(self, message: Message, probability=0.00166666667) -> multibot_constants.ORIGINAL_MESSAGE | None:
|
||||
if not self.is_bot_mentioned(message) and random.random() >= probability or message.author.id == self.owner_id:
|
||||
return
|
||||
|
||||
await self.typing_delay(message)
|
||||
|
||||
return await self.send(random.choice(constants.BAD_PHRASES), message)
|
||||
|
||||
async def send_bye(self, message: Message) -> multibot_constants.ORIGINAL_MESSAGE:
|
||||
return await self.send(random.choice((*constants.BYE_PHRASES, flanautils.CommonWords.random_time_greeting())), message)
|
||||
|
||||
async def send_hello(self, message: Message) -> multibot_constants.ORIGINAL_MESSAGE:
|
||||
return await self.send(random.choice((*constants.HELLO_PHRASES, flanautils.CommonWords.random_time_greeting())), message)
|
||||
|
||||
@return_if_first_empty(exclude_self_types='FlanaBot', globals_=globals())
|
||||
async def send_medias(self, medias: OrderedSet[Media], message: Message, send_song_info=False) -> tuple[list[Message], int]:
|
||||
sended_media_messages = []
|
||||
fails = 0
|
||||
bot_state_message: Message | None = None
|
||||
sended_info_message: Message | None = None
|
||||
|
||||
if not message.is_inline:
|
||||
bot_state_message: Message = await self.send('Descargando...', message)
|
||||
|
||||
if message.chat.is_group:
|
||||
sended_info_message = await self.send(f'{message.author.name} compartió{self._medias_sended_info(medias)}', message)
|
||||
|
||||
for media in medias:
|
||||
if media.song_info:
|
||||
message.song_infos.add(media.song_info)
|
||||
message.save()
|
||||
|
||||
try:
|
||||
bot_message = await self.send(media, message)
|
||||
except SendError as e:
|
||||
await self._manage_exceptions(e, message)
|
||||
fails += 1
|
||||
else:
|
||||
sended_media_messages.append(bot_message)
|
||||
if media.song_info and bot_message:
|
||||
bot_message.song_infos.add(media.song_info)
|
||||
bot_message.save()
|
||||
|
||||
if send_song_info and media.song_info:
|
||||
await self.send_song_info(media.song_info, message)
|
||||
|
||||
if fails and sended_info_message:
|
||||
if fails == len(medias):
|
||||
await self.delete_message(sended_info_message)
|
||||
if bot_state_message:
|
||||
await self.delete_message(bot_state_message)
|
||||
|
||||
return sended_media_messages, fails
|
||||
|
||||
@return_if_first_empty(exclude_self_types='FlanaBot', globals_=globals())
|
||||
async def send_song_info(self, song_info: Media, message: Message):
|
||||
attributes = (
|
||||
f'Título: {song_info.title}\n' if song_info.title else '',
|
||||
f'Autor: {song_info.author}\n' if song_info.author else '',
|
||||
f"{f'Álbum: {song_info.album}'}\n" if song_info.album else '',
|
||||
f'Previa:'
|
||||
)
|
||||
await self.send(''.join(attributes), message)
|
||||
if song_info:
|
||||
await self.send(song_info, message)
|
||||
|
||||
async def unmute(self, user_id: int, group_id: int, message: Message = None):
|
||||
try:
|
||||
await self._unmute(user_id, group_id)
|
||||
except UserDisconnectedError as e:
|
||||
await self._manage_exceptions(e, message if message else Message(chat=Chat(group_id=group_id)))
|
||||
|
||||
async def unpunish(self, user_id: int, group_id: int, message: Message = None):
|
||||
try:
|
||||
await self._unpunish(user_id, group_id)
|
||||
except BadRoleError as e:
|
||||
await self._manage_exceptions(e, message if message else Message(chat=Chat(group_id=group_id)))
|
||||
133
flanabot/bots/flana_disc_bot.py
Normal file
133
flanabot/bots/flana_disc_bot.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
import discord
|
||||
from multibot import DiscordBot
|
||||
|
||||
from flanabot import constants
|
||||
from flanabot.bots.flana_bot import FlanaBot
|
||||
from flanabot.exceptions import BadRoleError, UserDisconnectedError
|
||||
from flanabot.models import User
|
||||
|
||||
HEAT_NAMES = [
|
||||
'Canal Congelado',
|
||||
'Canal Fresquito',
|
||||
'Canal Templaillo',
|
||||
'Canal Calentito',
|
||||
'Canal Caloret',
|
||||
'Canal Caliente',
|
||||
'Canal Olor a Vasco',
|
||||
'Verano Cordobés al Sol',
|
||||
'Canal Ardiendo',
|
||||
'abrid las putas ventanas y traed el extintor',
|
||||
'Canal INFIERNO',
|
||||
'La Palma 🌋'
|
||||
]
|
||||
HOT_CHANNEL_ID = 493530483045564417
|
||||
ROLES = {
|
||||
'Administrador': 387344390030360587,
|
||||
'Carroñero': 493523298429435905,
|
||||
'al lol': 881238165476741161,
|
||||
'Persona': 866046517998387220,
|
||||
'Castigado': 877662459568209921,
|
||||
'Bot': 493784221085597706
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------ FLANA_DISC_BOT ------------------------------------------ #
|
||||
# ---------------------------------------------------------------------------------------------------- #
|
||||
class FlanaDiscBot(DiscordBot, FlanaBot):
|
||||
def __init__(self):
|
||||
super().__init__(os.environ['DISCORD_BOT_TOKEN'])
|
||||
self.heating = False
|
||||
self.heat_level = 0
|
||||
|
||||
# ----------------------------------------------------------- #
|
||||
# -------------------- PROTECTED METHODS -------------------- #
|
||||
# ----------------------------------------------------------- #
|
||||
def _add_handlers(self):
|
||||
super()._add_handlers()
|
||||
self.bot_client.add_listener(self._on_voice_state_update, 'on_voice_state_update')
|
||||
|
||||
async def _heat_channel(self, channel: discord.VoiceChannel):
|
||||
while True:
|
||||
await asyncio.sleep(constants.HEAT_PERIOD_SECONDS)
|
||||
|
||||
if channel.members:
|
||||
if self.heat_level == len(HEAT_NAMES) - 1:
|
||||
return
|
||||
self.heat_level += 0.5
|
||||
elif not channel.members:
|
||||
if not self.heat_level:
|
||||
return
|
||||
self.heat_level -= 0.5
|
||||
else:
|
||||
continue
|
||||
|
||||
await channel.edit(name=HEAT_NAMES[int(self.heat_level)])
|
||||
|
||||
async def _mute(self, user_id: int, group_id: int):
|
||||
user = await self.get_user(user_id, group_id)
|
||||
try:
|
||||
await user.original_object.edit(mute=True)
|
||||
except discord.errors.HTTPException:
|
||||
raise UserDisconnectedError
|
||||
|
||||
async def _punish(self, user_id: int, group_id: int):
|
||||
user = await self.get_user(user_id, group_id)
|
||||
try:
|
||||
await user.original_object.remove_roles(self._find_role_by_id(ROLES['Persona'], user.original_object.guild.roles))
|
||||
await user.original_object.add_roles(self._find_role_by_id(ROLES['Castigado'], user.original_object.guild.roles))
|
||||
except AttributeError:
|
||||
raise BadRoleError(str(self._punish))
|
||||
|
||||
async def _unmute(self, user_id: int, group_id: int):
|
||||
user = await self.get_user(user_id, group_id)
|
||||
try:
|
||||
await user.original_object.edit(mute=False)
|
||||
except discord.errors.HTTPException:
|
||||
raise UserDisconnectedError
|
||||
|
||||
async def _unpunish(self, user_id: int, group_id: int):
|
||||
user = await self.get_user(user_id, group_id)
|
||||
try:
|
||||
await user.original_object.remove_roles(self._find_role_by_id(ROLES['Castigado'], user.original_object.guild.roles))
|
||||
await user.original_object.add_roles(self._find_role_by_id(ROLES['Persona'], user.original_object.guild.roles))
|
||||
except AttributeError:
|
||||
raise BadRoleError(str(self._unpunish))
|
||||
|
||||
# ---------------------------------------------- #
|
||||
# HANDLERS #
|
||||
# ---------------------------------------------- #
|
||||
async def _on_voice_state_update(self, _: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
|
||||
if getattr(before.channel, 'id', None) == HOT_CHANNEL_ID:
|
||||
channel = before.channel
|
||||
elif getattr(after.channel, 'id', None) == HOT_CHANNEL_ID:
|
||||
channel = after.channel
|
||||
else:
|
||||
return
|
||||
|
||||
if not self.heating:
|
||||
self.heating = True
|
||||
await self._heat_channel(channel)
|
||||
self.heating = False
|
||||
|
||||
# -------------------------------------------------------- #
|
||||
# -------------------- PUBLIC METHODS -------------------- #
|
||||
# -------------------------------------------------------- #
|
||||
async def is_deaf(self, user_id: int, group_id: int) -> bool:
|
||||
user = await self.get_user(user_id, group_id)
|
||||
return user.original_object.voice.deaf
|
||||
|
||||
async def is_muted(self, user_id: int, group_id: int) -> bool:
|
||||
user = await self.get_user(user_id, group_id)
|
||||
return user.original_object.voice.mute
|
||||
|
||||
@staticmethod
|
||||
def is_self_deaf(user: User) -> bool:
|
||||
return user.original_object.voice.self_deaf
|
||||
|
||||
@staticmethod
|
||||
def is_self_muted(user: User) -> bool:
|
||||
return user.original_object.voice.self_mute
|
||||
127
flanabot/bots/flana_tele_bot.py
Normal file
127
flanabot/bots/flana_tele_bot.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from __future__ import annotations # todo0 remove in 3.11
|
||||
|
||||
import functools
|
||||
import os
|
||||
from typing import Any, Callable
|
||||
|
||||
import telethon.tl.functions
|
||||
from flanaapis.weather.constants import WeatherEmoji
|
||||
from flanautils import Media, MediaType, return_if_first_empty
|
||||
from multibot import TelegramBot, constants as multibot_constants, find_message, user_client
|
||||
|
||||
from flanabot.bots.flana_bot import FlanaBot
|
||||
from flanabot.models.chat import Chat
|
||||
from flanabot.models.message import Message
|
||||
from flanabot.models.user import User
|
||||
|
||||
|
||||
# ---------------------------------------------------------- #
|
||||
# ----------------------- DECORATORS ----------------------- #
|
||||
# ---------------------------------------------------------- #
|
||||
|
||||
|
||||
def whitelisted_event(func: Callable) -> Callable:
|
||||
@functools.wraps(func)
|
||||
@find_message
|
||||
async def wrapper(self: FlanaTeleBot, message: Message):
|
||||
if message.author.id not in self.whitelist_ids:
|
||||
return
|
||||
|
||||
return await func(self, message)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------- #
|
||||
# ------------------------------------------ FLANA_TELE_BOT ------------------------------------------ #
|
||||
# ---------------------------------------------------------------------------------------------------- #
|
||||
class FlanaTeleBot(TelegramBot, FlanaBot):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
api_id=os.environ['TELEGRAM_API_ID'],
|
||||
api_hash=os.environ['TELEGRAM_API_HASH'],
|
||||
bot_session=os.environ['TELEGRAM_BOT_SESSION'],
|
||||
user_session=os.environ['TELEGRAM_USER_SESSION']
|
||||
)
|
||||
self.whitelist_ids = []
|
||||
|
||||
# ----------------------------------------------------------- #
|
||||
# -------------------- PROTECTED METHODS -------------------- #
|
||||
# ----------------------------------------------------------- #
|
||||
def _add_handlers(self):
|
||||
super()._add_handlers()
|
||||
self.register_button(self._on_button_press)
|
||||
|
||||
@return_if_first_empty(exclude_self_types='FlanaTeleBot', globals_=globals())
|
||||
async def _create_chat_from_telegram_chat(self, telegram_chat: multibot_constants.TELEGRAM_CHAT) -> Chat | None:
|
||||
return Chat.from_event_component(await super()._create_chat_from_telegram_chat(telegram_chat))
|
||||
|
||||
@return_if_first_empty(exclude_self_types='FlanaTeleBot', globals_=globals())
|
||||
async def _create_bot_message_from_telegram_bot_message(self, original_message: multibot_constants.TELEGRAM_MESSAGE, message: Message, content: Any = None) -> Message | None:
|
||||
return Message.from_event_component(await super()._create_bot_message_from_telegram_bot_message(original_message, message))
|
||||
|
||||
@return_if_first_empty(exclude_self_types='FlanaTeleBot', globals_=globals())
|
||||
async def _create_user_from_telegram_user(self, original_user: multibot_constants.TELEGRAM_USER, group_id: int = None) -> User | None:
|
||||
return User.from_event_component(await super()._create_user_from_telegram_user(original_user, group_id))
|
||||
|
||||
@user_client
|
||||
async def _get_contacts_ids(self) -> list[int]:
|
||||
async with self.user_client:
|
||||
contacts_data = await self.user_client(telethon.tl.functions.contacts.GetContactsRequest(hash=0))
|
||||
|
||||
return [contact.user_id for contact in contacts_data.contacts]
|
||||
|
||||
@user_client
|
||||
async def _update_whitelist(self):
|
||||
self.whitelist_ids = [self.owner_id, self.bot_id] + await self._get_contacts_ids()
|
||||
|
||||
# ---------------------------------------------- #
|
||||
# HANDLERS #
|
||||
# ---------------------------------------------- #
|
||||
@whitelisted_event
|
||||
async def _on_button_press(self, message: Message):
|
||||
await message.original_event.answer()
|
||||
|
||||
match message.button_text:
|
||||
case WeatherEmoji.ZOOM_IN.value:
|
||||
message.weather_chart.zoom_in()
|
||||
case WeatherEmoji.ZOOM_OUT.value:
|
||||
message.weather_chart.zoom_out()
|
||||
case WeatherEmoji.LEFT.value:
|
||||
message.weather_chart.move_left()
|
||||
case WeatherEmoji.RIGHT.value:
|
||||
message.weather_chart.move_right()
|
||||
case WeatherEmoji.PRECIPITATION_VOLUME.value:
|
||||
message.weather_chart.trace_metadatas['rain_volume'].show = not message.weather_chart.trace_metadatas['rain_volume'].show
|
||||
message.weather_chart.trace_metadatas['snow_volume'].show = not message.weather_chart.trace_metadatas['snow_volume'].show
|
||||
case emoji if emoji in WeatherEmoji.values:
|
||||
trace_metadata_name = WeatherEmoji(emoji).name.lower()
|
||||
message.weather_chart.trace_metadatas[trace_metadata_name].show = not message.weather_chart.trace_metadatas[trace_metadata_name].show
|
||||
|
||||
message.weather_chart.apply_zoom()
|
||||
message.weather_chart.draw()
|
||||
message.save()
|
||||
|
||||
image_bytes = message.weather_chart.to_image()
|
||||
file = await self._prepare_media_to_send(Media(image_bytes, MediaType.IMAGE))
|
||||
|
||||
try:
|
||||
await message.original_object.edit(file=file)
|
||||
except telethon.errors.rpcerrorlist.MessageNotModifiedError:
|
||||
pass
|
||||
|
||||
@whitelisted_event
|
||||
async def _on_inline_query_raw(self, message: Message):
|
||||
await super()._on_new_message_raw(message)
|
||||
|
||||
@whitelisted_event
|
||||
async def _on_new_message_raw(self, message: Message):
|
||||
await super()._on_new_message_raw(message)
|
||||
|
||||
async def _on_ready(self):
|
||||
await super()._on_ready()
|
||||
await self._update_whitelist()
|
||||
|
||||
# -------------------------------------------------------- #
|
||||
# -------------------- PUBLIC METHODS -------------------- #
|
||||
# -------------------------------------------------------- #
|
||||
100
flanabot/constants.py
Normal file
100
flanabot/constants.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import datetime
|
||||
|
||||
import flanautils
|
||||
|
||||
AUTO_WEATHER_EVERY = datetime.timedelta(hours=6)
|
||||
CHECK_MESSAGE_EVERY_SECONDS = datetime.timedelta(days=1).total_seconds()
|
||||
CHECK_MUTES_EVERY_SECONDS = datetime.timedelta(hours=1).total_seconds()
|
||||
CHECK_PUNISHMENTS_EVERY_SECONDS = datetime.timedelta(hours=1).total_seconds()
|
||||
HEAT_PERIOD_SECONDS = datetime.timedelta(minutes=15).total_seconds()
|
||||
MAX_PLACE_QUERY_LENGTH = 50
|
||||
PUNISHMENT_INCREMENT_EXPONENT = 6
|
||||
PUNISHMENTS_RESET = datetime.timedelta(weeks=6 * flanautils.WEEKS_IN_A_MONTH)
|
||||
RECOVERY_DELETED_MESSAGE_BEFORE = datetime.timedelta(hours=1)
|
||||
TIME_THRESHOLD_TO_MANUAL_UNMUTE = datetime.timedelta(days=3)
|
||||
TIME_THRESHOLD_TO_MANUAL_UNPUNISH = datetime.timedelta(days=3)
|
||||
|
||||
BAD_PHRASES = (
|
||||
'Cállate ya anda.',
|
||||
'¿Quién te ha preguntado?',
|
||||
'¿Tú eres así o te dan apagones cerebrales?',
|
||||
'Ante la duda mi dedo corazón te saluda.',
|
||||
'Enjoy cancer brain.',
|
||||
'Calla noob.',
|
||||
'Hablas tanta mierda que tu culo tiene envidia de tu boca.',
|
||||
'jAJjajAJjajAJjajAJajJAJajJA',
|
||||
'enjoy xd',
|
||||
'Reported.',
|
||||
'Baneito pa ti en breve.',
|
||||
'Despídete de tu cuenta.',
|
||||
'Flanagan es más guapo que tú.',
|
||||
'jajaj',
|
||||
'xd',
|
||||
'Hay un concurso de hostias y tienes todas las papeletas.',
|
||||
'¿Por qué no te callas?',
|
||||
'Das penilla.',
|
||||
'Deberían hacerte la táctica del C4.',
|
||||
'Te voy romper las pelotas.',
|
||||
'Más tonto y no naces.',
|
||||
'Eres más tonto que peinar bombillas.',
|
||||
'Eres más tonto que pellizcar cristales.',
|
||||
'Eres más malo que pegarle a un padre.'
|
||||
)
|
||||
|
||||
BYE_PHRASES = ('Adiós.', 'adieu', 'adio', 'adioh', 'adios', 'adió', 'adiós', 'agur', 'bye', 'byyeeee', 'chao',
|
||||
'hasta la vista', 'hasta luego', 'hasta nunca', ' hasta pronto', 'hasta la próxima',
|
||||
'nos vemos', 'taluego')
|
||||
|
||||
HELLO_PHRASES = ('alo', 'aloh', 'buenas', 'Hola.', 'hello', 'hey', 'hi', 'hola', 'holaaaa', 'holaaaaaaa', 'ola',
|
||||
'ola k ase', 'pa ti mi cola', 'saludos')
|
||||
|
||||
KEYWORDS = {
|
||||
'activate': ('activa', 'activar', 'activate', 'deja', 'dejale', 'devuelve', 'devuelvele', 'enable', 'encender',
|
||||
'enciende', 'habilita', 'habilitar'),
|
||||
'bye': ('adieu', 'adio', 'adiooooo', 'adios', 'agur', 'buenas', 'bye', 'cama', 'chao', 'dias', 'farewell',
|
||||
'goodbye', 'hasta', 'luego', 'noches', 'pronto', 'taluego', 'taluegorl', 'tenga', 'vemos', 'vista', 'voy'),
|
||||
'change': ('alter', 'alternar', 'alternate', 'cambiar', 'change', 'default', 'defecto', 'edit', 'editar',
|
||||
'exchange', 'modificar', 'modify', 'permutar', 'predeterminado', 'shift', 'swap', 'switch', 'turn',
|
||||
'vary'),
|
||||
'config': ('ajustar', 'ajuste', 'ajustes', 'automatico', 'automatic', 'config', 'configs', 'configuracion',
|
||||
'configuration', 'default', 'defecto', 'setting', 'settings'),
|
||||
'covid_chart': ('case', 'caso', 'contagiado', 'contagio', 'corona', 'coronavirus', 'covid', 'covid19', 'death',
|
||||
'disease', 'enfermedad', 'enfermos', 'fallecido', 'incidencia', 'jacovid', 'mascarilla', 'muerte',
|
||||
'muerto', 'pandemia', 'sick', 'virus'),
|
||||
'currency_chart': ('argentina', 'bitcoin', 'cardano', 'cripto', 'crypto', 'criptodivisa', 'cryptodivisa',
|
||||
'cryptomoneda', 'cryptocurrency', 'currency', 'dinero', 'divisa', 'ethereum', 'inversion',
|
||||
'moneda', 'pasta'),
|
||||
'date': ('ayer', 'de', 'domingo', 'fin', 'finde', 'friday', 'hoy', 'jueves', 'lunes', 'martes', 'mañana',
|
||||
'miercoles', 'monday', 'pasado', 'sabado', 'saturday', 'semana', 'sunday', 'thursday', 'today', 'tomorrow',
|
||||
'tuesday', 'viernes', 'wednesday', 'week', 'weekend', 'yesterday'),
|
||||
'deactivate': ('apaga', 'apagar', 'deactivate', 'deactivate', 'desactivar', 'deshabilita', 'deshabilitar',
|
||||
'disable', 'forbids', 'prohibe', 'quita', 'remove', 'return'),
|
||||
'hello': ('alo', 'aloh', 'buenas', 'dias', 'hello', 'hey', 'hi', 'hola', 'holaaaaaa', 'ola', 'saludos', 'tardes'),
|
||||
'help': ('ayuda', 'help'),
|
||||
'mute': ('calla', 'calle', 'cierra', 'close', 'mute', 'mutea', 'mutealo', 'noise', 'ruido', 'shut', 'silence',
|
||||
'silencia'),
|
||||
'negate': ('no', 'ocurra', 'ocurre'),
|
||||
'permission': ('permiso', 'permission'),
|
||||
'punish': ('acaba', 'aprende', 'ataca', 'atalo', 'azota', 'boss', 'castiga', 'castigo', 'condena', 'controla',
|
||||
'destroy', 'destroza', 'duro', 'ejecuta', 'enseña', 'escarmiento', 'execute', 'finish', 'fuck', 'fusila',
|
||||
'hell', 'humos', 'infierno', 'jefe', 'jode', 'learn', 'leccion', 'lesson', 'manda', 'purgatorio',
|
||||
'sancion', 'shoot', 'teach', 'termina', 'whip'),
|
||||
'reset': ('recover', 'recovery', 'recupera', 'reinicia', 'reset', 'resetea', 'restart'),
|
||||
'scraping': ('api', 'aqui', 'busca', 'contenido', 'content', 'descarga', 'descargar', 'download', 'envia', 'habia',
|
||||
'media', 'redes', 'scrap', 'scraping', 'search', 'send', 'social', 'sociales', 'tenia', 'video',
|
||||
'videos'),
|
||||
'show': ('actual', 'enseña', 'estado', 'how', 'is', 'muestra', 'show', 'como'),
|
||||
'song_info': ('aqui', 'cancion', 'data', 'datos', 'info', 'informacion', 'information', 'llama', 'media', 'name',
|
||||
'nombre', 'sonaba', 'sonando', 'song', 'sono', 'sound', 'suena', 'title', 'titulo',
|
||||
'video'),
|
||||
'sound': ('hablar', 'hable', 'micro', 'microfono', 'microphone', 'sonido', 'sound', 'talk', 'volumen'),
|
||||
'thanks': ('gracia', 'gracias', 'grasia', 'grasias', 'grax', 'thank', 'thanks', 'ty'),
|
||||
'unmute': ('desilencia', 'desmutea', 'desmutealo', 'unmute'),
|
||||
'unpunish': ('absolve', 'forgive', 'innocent', 'inocente', 'perdona', 'spare'),
|
||||
'weather_chart': ('atmosfera', 'atmosferico', 'calle', 'calor', 'caloret', 'clima', 'climatologia', 'cloud',
|
||||
'cloudless', 'cloudy', 'cold', 'congelar', 'congelado', 'denbora', 'despejado', 'diluvio', 'frio',
|
||||
'frost', 'hielo', 'humedad', 'llover', 'llueva', 'llueve', 'lluvia', 'nevada', 'nieva', 'nieve',
|
||||
'nube', 'nubes', 'nublado', 'meteorologia', 'rain', 'snow', 'snowfall', 'snowstorm', 'sol',
|
||||
'solano', 'storm', 'sun', 'temperatura', 'tempo', 'tiempo', 'tormenta', 've', 'ventisca',
|
||||
'weather', 'wetter')
|
||||
}
|
||||
6
flanabot/exceptions.py
Normal file
6
flanabot/exceptions.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class BadRoleError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UserDisconnectedError(Exception):
|
||||
pass
|
||||
18
flanabot/main.py
Normal file
18
flanabot/main.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import asyncio
|
||||
|
||||
from flanabot.bots.flana_disc_bot import FlanaDiscBot
|
||||
from flanabot.bots.flana_tele_bot import FlanaTeleBot
|
||||
|
||||
|
||||
async def main():
|
||||
flana_disc_bot = FlanaDiscBot()
|
||||
flana_tele_bot = FlanaTeleBot()
|
||||
|
||||
await asyncio.gather(
|
||||
# flana_disc_bot.start(),
|
||||
flana_tele_bot.start(),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
5
flanabot/models/__init__.py
Normal file
5
flanabot/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from flanabot.models.chat import *
|
||||
from flanabot.models.message import *
|
||||
from flanabot.models.punishments import *
|
||||
from flanabot.models.user import *
|
||||
from flanabot.models.weather_chart import *
|
||||
21
flanabot/models/chat.py
Normal file
21
flanabot/models/chat.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from multibot import Chat as MultiBotChat, EventComponent, T
|
||||
|
||||
DEFAULT_CONFIG = {'auto_clear': False,
|
||||
'auto_covid_chart': True,
|
||||
'auto_currency_chart': True,
|
||||
'auto_delete_original': True,
|
||||
'auto_scraping': True,
|
||||
'auto_weather_chart': True}
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class Chat(MultiBotChat):
|
||||
config: dict[str, bool] = field(default_factory=lambda: DEFAULT_CONFIG)
|
||||
|
||||
@classmethod
|
||||
def from_event_component(cls, event_component: EventComponent) -> T:
|
||||
chat = super().from_event_component(event_component)
|
||||
chat.config = DEFAULT_CONFIG
|
||||
return chat
|
||||
39
flanabot/models/message.py
Normal file
39
flanabot/models/message.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations # todo0 remove in 3.11
|
||||
|
||||
import datetime
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterable
|
||||
|
||||
from flanautils import Media, OrderedSet
|
||||
from multibot import EventComponent, constants as multibot_constants, db
|
||||
|
||||
from flanabot.models.chat import Chat
|
||||
from flanabot.models.user import User
|
||||
from flanabot.models.weather_chart import WeatherChart
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class Message(EventComponent):
|
||||
collection = db.message
|
||||
_unique_keys = ('id', 'author')
|
||||
_nullable_unique_keys = ('id', 'author')
|
||||
|
||||
id: int | str = None
|
||||
author: User = None
|
||||
text: str = None
|
||||
button_text: str = None
|
||||
mentions: Iterable[User] = field(default_factory=list)
|
||||
chat: Chat = None
|
||||
replied_message: Message = None
|
||||
weather_chart: WeatherChart = None
|
||||
last_update: datetime.datetime = None
|
||||
song_infos: OrderedSet[Media] = field(default_factory=OrderedSet)
|
||||
is_inline: bool = None
|
||||
contents: list = field(default_factory=list)
|
||||
is_deleted: bool = False
|
||||
original_object: multibot_constants.ORIGINAL_MESSAGE = None
|
||||
original_event: multibot_constants.MESSAGE_EVENT = None
|
||||
|
||||
def save(self, pull_exclude: Iterable[str] = (), pull_database_priority=False, references=True):
|
||||
self.last_update = datetime.datetime.now()
|
||||
super().save(pull_exclude, pull_database_priority, references)
|
||||
23
flanabot/models/punishments.py
Normal file
23
flanabot/models/punishments.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
from flanautils import FlanaBase, MongoBase
|
||||
from multibot.models.database import db
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class PunishmentBase(MongoBase, FlanaBase):
|
||||
user_id: int = None
|
||||
group_id: int = None
|
||||
until: datetime.datetime = None
|
||||
is_active: bool = True
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class Punishment(PunishmentBase):
|
||||
collection = db.punishment
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class Mute(PunishmentBase):
|
||||
collection = db.mute
|
||||
30
flanabot/models/user.py
Normal file
30
flanabot/models/user.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from multibot import User as MultiBotUser, constants as multibot_constants, db
|
||||
|
||||
from flanabot.models.punishments import Mute, Punishment
|
||||
|
||||
|
||||
@dataclass(eq=False)
|
||||
class User(MultiBotUser):
|
||||
collection = db.user
|
||||
_unique_keys = 'id'
|
||||
|
||||
id: int = None
|
||||
name: str = None
|
||||
is_admin: bool = None
|
||||
original_object: multibot_constants.ORIGINAL_USER = None
|
||||
|
||||
def is_muted_on(self, group_id: int):
|
||||
return group_id in self.muted_on
|
||||
|
||||
def is_punished_on(self, group_id: int):
|
||||
return group_id in self.punished_on
|
||||
|
||||
@property
|
||||
def muted_on(self):
|
||||
return {mute.group_id for mute in Mute.find({'user_id': self.id, 'is_active': True})}
|
||||
|
||||
@property
|
||||
def punished_on(self):
|
||||
return {punishment for punishment in Punishment.find({'user_id': self.id, 'is_active': True})}
|
||||
125
flanabot/models/weather_chart.py
Normal file
125
flanabot/models/weather_chart.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import datetime
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from flanaapis import InstantWeather, Place
|
||||
from flanautils import DateChart, FlanaEnum
|
||||
|
||||
|
||||
class Direction(FlanaEnum):
|
||||
LEFT = -1
|
||||
RIGHT = 1
|
||||
|
||||
|
||||
@dataclass(unsafe_hash=True)
|
||||
class WeatherChart(DateChart):
|
||||
current_weather: InstantWeather = None
|
||||
day_weathers: list = field(default_factory=list)
|
||||
timezone: datetime.timezone = None
|
||||
place: Place = None
|
||||
day_separator_width: float = 1
|
||||
zoom_level: int = 1
|
||||
view_position: datetime.datetime = None
|
||||
|
||||
def __post_init__(self):
|
||||
super().__post_init__()
|
||||
self.legend = self._legend
|
||||
self.x_data = [instant_weather.date_time for day_weather in self.day_weathers for instant_weather in day_weather.instant_weathers]
|
||||
for trace_metadata in self.trace_metadatas.values():
|
||||
y_data = []
|
||||
for day_weather in self.day_weathers:
|
||||
for instant_weather in day_weather.instant_weathers:
|
||||
y_data.append(getattr(instant_weather, trace_metadata.name))
|
||||
self.all_y_data.append(y_data)
|
||||
|
||||
def add_lines(self):
|
||||
super().add_lines()
|
||||
|
||||
try:
|
||||
first_dt = self.day_weathers[0].instant_weathers[0].date_time
|
||||
last_dt = self.day_weathers[-1].instant_weathers[-1].date_time
|
||||
except IndexError:
|
||||
return
|
||||
|
||||
now = datetime.datetime.now(self.timezone)
|
||||
|
||||
if self.show_now_vertical_line and first_dt <= now <= last_dt:
|
||||
self.figure.add_vline(x=now, line_width=1, line_dash='dot')
|
||||
self.figure.add_annotation(text="Ahora", yref="paper", x=now, y=0.01, showarrow=False)
|
||||
|
||||
for day_weather in self.day_weathers:
|
||||
date_time = datetime.datetime(year=day_weather.date.year, month=day_weather.date.month, day=day_weather.date.day, tzinfo=self.timezone)
|
||||
if first_dt <= date_time <= last_dt:
|
||||
self.figure.add_vline(x=date_time, line_width=self.day_separator_width)
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
# noinspection PyUnboundLocalVariable
|
||||
def apply_zoom(self):
|
||||
self.clear()
|
||||
match self.zoom_level:
|
||||
case 0:
|
||||
self.xaxis = {
|
||||
'tickformat': '%A %-d\n%B\n%Y',
|
||||
'dtick': 24 * 60 * 60 * 1000,
|
||||
'ticklabelmode': 'period',
|
||||
'tickangle': None
|
||||
}
|
||||
self.day_separator_width = 1
|
||||
case _:
|
||||
match self.zoom_level:
|
||||
case 1:
|
||||
start_date = self.view_position - datetime.timedelta(days=3)
|
||||
end_date = self.view_position + datetime.timedelta(days=3)
|
||||
self.xaxis = {'tickformat': '%A %-d\n%B\n%Y', 'dtick': 24 * 60 * 60 * 1000, 'ticklabelmode': 'period'}
|
||||
self.day_separator_width = 1
|
||||
case 2:
|
||||
start_date = self.view_position - datetime.timedelta(days=1)
|
||||
end_date = self.view_position + datetime.timedelta(days=2)
|
||||
self.xaxis = {'tickformat': '%-H\n%A %-d', 'dtick': 6 * 60 * 60 * 1000}
|
||||
self.day_separator_width = 2
|
||||
case 3:
|
||||
start_date = self.view_position - datetime.timedelta(days=1)
|
||||
end_date = self.view_position + datetime.timedelta(days=1)
|
||||
self.xaxis = {'tickformat': '%-H\n%A %-d', 'dtick': 4 * 60 * 60 * 1000}
|
||||
self.day_separator_width = 2
|
||||
case 4:
|
||||
start_date = self.view_position
|
||||
end_date = self.view_position + datetime.timedelta(days=1)
|
||||
self.xaxis = {'tickformat': '%-H\n%A %-d', 'dtick': 2 * 60 * 60 * 1000}
|
||||
self.day_separator_width = 2
|
||||
case 5:
|
||||
start_date = self.view_position
|
||||
end_date = self.view_position
|
||||
self.xaxis = {'tickformat': '%-H\n%A %-d', 'dtick': 1 * 60 * 60 * 1000}
|
||||
self.day_separator_width = 2
|
||||
|
||||
self.xaxis = {
|
||||
'range': (
|
||||
(start_date - datetime.timedelta(days=1)).replace(hour=23),
|
||||
(end_date + datetime.timedelta(days=1)).replace(hour=0)
|
||||
),
|
||||
'tickangle': 0
|
||||
}
|
||||
|
||||
def move_left(self):
|
||||
if self.zoom_level > 0 and self.x_data[0] <= self.view_position - datetime.timedelta(days=1):
|
||||
self.move_view_position(Direction.LEFT)
|
||||
|
||||
def move_right(self):
|
||||
if self.zoom_level > 0 and self.view_position + datetime.timedelta(days=1) <= self.x_data[-1]:
|
||||
self.move_view_position(Direction.RIGHT)
|
||||
|
||||
def move_view_position(self, direction: Direction):
|
||||
match self.zoom_level:
|
||||
case 0:
|
||||
return
|
||||
case _:
|
||||
time_delta = datetime.timedelta(days=1)
|
||||
self.view_position += time_delta * direction.value
|
||||
|
||||
def zoom_in(self):
|
||||
if self.zoom_level < 5:
|
||||
self.zoom_level += 1
|
||||
|
||||
def zoom_out(self):
|
||||
if 0 < self.zoom_level:
|
||||
self.zoom_level -= 1
|
||||
BIN
flanabot/resources/mucho_texto.png
Normal file
BIN
flanabot/resources/mucho_texto.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
290
tests/unit/test_parse_callbacks.py
Normal file
290
tests/unit/test_parse_callbacks.py
Normal file
@@ -0,0 +1,290 @@
|
||||
import unittest
|
||||
from typing import Iterable
|
||||
|
||||
from flanabot import constants
|
||||
import flanautils
|
||||
from flanabot.bots.flana_bots.flana_tele_bot import FlanaTeleBot
|
||||
|
||||
|
||||
class TestParseCallbacks(unittest.TestCase):
|
||||
def _test_no_always_callbacks(self, phrases: Iterable[str], callback: callable):
|
||||
for i, phrase in enumerate(phrases):
|
||||
with self.subTest(phrase):
|
||||
callbacks = [registered_callback.callback for registered_callback in self.flana_tele_bot._parse_callbacks(phrase, constants.RATIO_REWARD_EXPONENT, constants.KEYWORDS_LENGHT_PENALTY, constants.MINIMUM_RATIO_TO_MATCH)
|
||||
if not registered_callback.always]
|
||||
self.assertEqual(1, len(callbacks))
|
||||
self.assertEqual(callback, callbacks[0], f'\n\nExpected: {callback.__name__}\nActual: {callbacks[0].__name__}')
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.flana_tele_bot = FlanaTeleBot()
|
||||
|
||||
def test_on_bye(self):
|
||||
phrases = ['adios', 'taluego', 'adiooo', 'hasta la proxima', 'nos vemos', 'hasta la vista', 'hasta pronto']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_bye)
|
||||
|
||||
def test_on_config_list_show(self):
|
||||
phrases = [
|
||||
'flanabot ajustes',
|
||||
'Flanabot ajustes',
|
||||
'Flanabot qué puedo ajustar?',
|
||||
'flanabot ayuda'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_config_list_show)
|
||||
|
||||
def test_on_covid_chart(self):
|
||||
phrases = [
|
||||
'cuantos contagios',
|
||||
'casos',
|
||||
'enfermos',
|
||||
'muerte',
|
||||
'pandemia',
|
||||
'enfermedad',
|
||||
'fallecidos',
|
||||
'mascarillas',
|
||||
'virus',
|
||||
'covid-19',
|
||||
'como va el covid',
|
||||
'lo peta el corona'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_covid_chart)
|
||||
|
||||
def test_on_currency_chart(self):
|
||||
phrases = [
|
||||
'como van esos dineros',
|
||||
'critodivisa',
|
||||
'esas cryptos',
|
||||
'inversion',
|
||||
'moneda',
|
||||
'mas caro en argentina?',
|
||||
'el puto bitcoin',
|
||||
'divisa'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_currency_chart)
|
||||
|
||||
def test_on_currency_chart_config_activate(self):
|
||||
phrases = ['activa el bitcoin automatico']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_currency_chart_config_activate)
|
||||
|
||||
def test_on_currency_chart_config_change(self):
|
||||
phrases = ['cambia la config del bitcoin automatico']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_currency_chart_config_change)
|
||||
|
||||
def test_on_currency_chart_config_deactivate(self):
|
||||
phrases = ['desactiva el bitcoin automatico']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_currency_chart_config_deactivate)
|
||||
|
||||
def test_on_currency_chart_config_show(self):
|
||||
phrases = ['enseña el bitcoin automatico', 'como esta el bitcoin automatico', 'flanabot ajustes bitcoin']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_currency_chart_config_show)
|
||||
|
||||
def test_on_delete(self):
|
||||
phrases = ['borra ese mensaje', 'borra ese mensaje puto', 'borra', 'borra el mensaje', 'borra eso', 'borres']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_delete)
|
||||
|
||||
def test_on_delete_original_config_activate(self):
|
||||
phrases = [
|
||||
'activa el borrado automatico',
|
||||
'flanabot pon el auto delete activado',
|
||||
'flanabot activa el autodelete'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_delete_original_config_activate)
|
||||
|
||||
def test_on_delete_original_config_change(self):
|
||||
phrases = ['cambia la config del borrado automatico']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_delete_original_config_change)
|
||||
|
||||
def test_on_delete_original_config_deactivate(self):
|
||||
phrases = [
|
||||
'desactiva el borrado automatico',
|
||||
'flanabot pon el auto delete desactivado',
|
||||
'flanabot desactiva el autodelete'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_delete_original_config_deactivate)
|
||||
|
||||
def test_on_delete_original_config_show(self):
|
||||
phrases = ['enseña el borrado automatico', 'como esta el borrado automatico', 'flanabot ajustes delete']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_delete_original_config_show)
|
||||
|
||||
def test_on_hello(self):
|
||||
phrases = ['hola', 'hello', 'buenos dias', 'holaaaaaa', 'hi', 'holaaaaa', 'saludos', 'ola k ase']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_hello)
|
||||
|
||||
def test_on_mute(self):
|
||||
phrases = [
|
||||
'silencia',
|
||||
'silencia al pavo ese',
|
||||
'calla a ese pesao',
|
||||
'haz que se calle',
|
||||
'quitale el microfono a ese',
|
||||
'quitale el micro',
|
||||
'quitale el sonido',
|
||||
'mutealo',
|
||||
'mutea',
|
||||
'mutea a ese'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_mute)
|
||||
|
||||
def test_on_new_message(self):
|
||||
for i in range(10):
|
||||
phrase = flanautils.random_string(0, 30, n_spaces=20)
|
||||
with self.subTest(phrase):
|
||||
callbacks = [registered_callback.callback for registered_callback in self.flana_tele_bot._parse_callbacks(phrase)]
|
||||
self.assertIn(self.flana_tele_bot._on_new_message, callbacks, f'\n\nExpected: {self.flana_tele_bot._on_new_message.__name__} in {callbacks}')
|
||||
|
||||
def test_on_new_message_default(self):
|
||||
phrases = [
|
||||
'asdqwergf',
|
||||
'ytk8',
|
||||
'htr',
|
||||
'hmj',
|
||||
'aaaaaaa'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_new_message_default)
|
||||
|
||||
def test_on_no_delete_original(self):
|
||||
phrases = [
|
||||
'no obrres',
|
||||
'no borres el original',
|
||||
'no borres',
|
||||
'no borres el oringal',
|
||||
'no oringial',
|
||||
'Alberto, [30/11/2021 5:59]\nno borres el original',
|
||||
'no borrres el original',
|
||||
'no borra ese mensaje',
|
||||
'no borres el original joder'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_no_delete_original)
|
||||
|
||||
def test_on_punish(self):
|
||||
phrases = [
|
||||
'acabo con el',
|
||||
'acaba con el',
|
||||
'destrozalo',
|
||||
'ataca',
|
||||
'acaba',
|
||||
'acaba con',
|
||||
'termina con el',
|
||||
'acabaq con su sufri,iento',
|
||||
'acaba con ese apvo',
|
||||
'castigalo',
|
||||
'castiga a',
|
||||
'castiga',
|
||||
'banealo',
|
||||
'banea',
|
||||
'enseña quien manda'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_punish)
|
||||
|
||||
def test_on_scraping(self):
|
||||
phrases = [
|
||||
'scraping',
|
||||
'descarga lo que hay ahi',
|
||||
'descarga lo que hubiera ahi',
|
||||
'que habia ahi?',
|
||||
'que habia ahi',
|
||||
'que media habia',
|
||||
'descarga el video',
|
||||
'descarga la media',
|
||||
'descarga',
|
||||
'busca',
|
||||
'busca y descarga',
|
||||
'descarga el contenido'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_scraping)
|
||||
|
||||
def test_on_scraping_config_activate(self):
|
||||
phrases = ['activa el scraping automatico', 'activa el scraping']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_scraping_config_activate)
|
||||
|
||||
def test_on_scraping_config_change(self):
|
||||
phrases = ['cambia la config del scraping']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_scraping_config_change)
|
||||
|
||||
def test_on_scraping_config_deactivate(self):
|
||||
phrases = ['desactiva el scraping automatico']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_scraping_config_deactivate)
|
||||
|
||||
def test_on_scraping_config_show(self):
|
||||
phrases = ['enseña el scraping automatico', 'como esta el scraping automatico', 'flanabot ajustes scraping']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_scraping_config_show)
|
||||
|
||||
def test_on_song_info(self):
|
||||
phrases = [
|
||||
'que sonaba ahi',
|
||||
'suena ahi',
|
||||
'que suena',
|
||||
'nombre de la cancion',
|
||||
'nombre cancion',
|
||||
'que cancion suena ahi',
|
||||
'sonaba',
|
||||
'informacion de la cancion',
|
||||
'info de la cancion',
|
||||
'titulo',
|
||||
'nombre',
|
||||
'titulo de la cancion',
|
||||
'como se llama esa cancion',
|
||||
'como se llama',
|
||||
'como se llama la cancion',
|
||||
'la cancion que suena en el video',
|
||||
'suena en el video',
|
||||
'suena'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_song_info)
|
||||
|
||||
def test_on_unmute(self):
|
||||
phrases = [
|
||||
'desmutealo',
|
||||
'quitale el mute',
|
||||
'devuelvele el sonido',
|
||||
'quitale el silencio',
|
||||
'desilencialo',
|
||||
'dejale hablar',
|
||||
'unmute'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_unmute)
|
||||
|
||||
def test_on_unpunish(self):
|
||||
phrases = [
|
||||
'perdonalo',
|
||||
'perdona a',
|
||||
'illo quitale a @flanagan el castigo',
|
||||
'quita castigo',
|
||||
'devuelve los permisos',
|
||||
'desbanea'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_unpunish)
|
||||
|
||||
def test_on_weather_chart(self):
|
||||
phrases = [
|
||||
'que calor',
|
||||
'llovera',
|
||||
'que lluvia ni que',
|
||||
'que probabilidad hay de que llueva',
|
||||
'que tiempo hara',
|
||||
'solano',
|
||||
'sol',
|
||||
'temperatura',
|
||||
'humedad',
|
||||
'que tiempo hace en malaga',
|
||||
'que tiempo hace en calle larios',
|
||||
'tiempo rusia',
|
||||
'hara mucho calor en egipto este fin de semana?',
|
||||
'pfff no ve que frio ahi en oviedo este finde'
|
||||
]
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_weather_chart)
|
||||
|
||||
def test_on_weather_chart_config_activate(self):
|
||||
phrases = ['activa el tiempo automatico', 'activa el tiempo']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_weather_chart_config_activate)
|
||||
|
||||
def test_on_weather_chart_config_change(self):
|
||||
phrases = ['cambia la config del tiempo']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_weather_chart_config_change)
|
||||
|
||||
def test_on_weather_chart_config_deactivate(self):
|
||||
phrases = ['desactiva el tiempo automatico']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_weather_chart_config_deactivate)
|
||||
|
||||
def test_on_weather_chart_config_show(self):
|
||||
phrases = ['enseña el tiempo automatico', 'como esta el tiempo automatico', 'flanabot ajustes tiempo']
|
||||
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_weather_chart_config_show)
|
||||
Reference in New Issue
Block a user