Initial commit

This commit is contained in:
AlberLC
2022-01-06 05:18:42 +01:00
commit 99853339c2
18 changed files with 1921 additions and 0 deletions

161
.gitignore vendored Normal file
View 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
View 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
View 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
View 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)))

View 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

View 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
View 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
View File

@@ -0,0 +1,6 @@
class BadRoleError(Exception):
pass
class UserDisconnectedError(Exception):
pass

18
flanabot/main.py Normal file
View 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())

View 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
View 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

View 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)

View 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
View 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})}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
requirements.txt Normal file

Binary file not shown.

View 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)