2 Commits

Author SHA1 Message Date
AlberLC
697ba9e89e Add polls 2022-06-22 09:18:42 +02:00
AlberLC
09df75ea0b Update buttons logic (add ButtonsInfo) and add choose and dice functionalities 2022-06-19 13:34:12 +02:00
5 changed files with 154 additions and 26 deletions

View File

@@ -3,10 +3,11 @@ __all__ = ['FlanaBot']
import asyncio import asyncio
import datetime import datetime
import random import random
import re
import time as time_module import time as time_module
from abc import ABC from abc import ABC
from asyncio import Future from asyncio import Future
from typing import Iterable from typing import Iterable, Sequence
import flanaapis.geolocation.functions import flanaapis.geolocation.functions
import flanaapis.weather.functions import flanaapis.weather.functions
@@ -15,7 +16,7 @@ import plotly.graph_objects
import pymongo import pymongo
from flanaapis import InstagramLoginError, MediaNotFoundError, Place, PlaceNotFoundError, WeatherEmoji, instagram, tiktok, twitter 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 flanautils import Media, MediaType, NotFoundError, OrderedSet, Source, TimeUnits, TraceMetadata, return_if_first_empty
from multibot import Action, BadRoleError, BotAction, MultiBot, SendError, User, admin, bot_mentioned, constants as multibot_constants, group, ignore_self_message, inline, reply from multibot import Action, BadRoleError, BotAction, ButtonsGroup, MultiBot, SendError, User, admin, bot_mentioned, constants as multibot_constants, group, ignore_self_message, inline, reply
from flanabot import constants from flanabot import constants
from flanabot.models import Chat, Message, Punishment, WeatherChart from flanabot.models import Chat, Message, Punishment, WeatherChart
@@ -37,6 +38,10 @@ class FlanaBot(MultiBot, ABC):
self.register(self._on_bye, multibot_constants.KEYWORDS['bye']) self.register(self._on_bye, multibot_constants.KEYWORDS['bye'])
self.register(self._on_choose, constants.KEYWORDS['choose'])
self.register(self._on_choose, constants.KEYWORDS['random'])
self.register(self._on_choose, (constants.KEYWORDS['choose'], constants.KEYWORDS['random']))
self.register(self._on_config_list_show, multibot_constants.KEYWORDS['config']) self.register(self._on_config_list_show, multibot_constants.KEYWORDS['config'])
self.register(self._on_config_list_show, (multibot_constants.KEYWORDS['show'], multibot_constants.KEYWORDS['config'])) self.register(self._on_config_list_show, (multibot_constants.KEYWORDS['show'], multibot_constants.KEYWORDS['config']))
@@ -79,6 +84,8 @@ class FlanaBot(MultiBot, ABC):
self.register(self._on_delete_original_config_show, (multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], multibot_constants.KEYWORDS['config'])) self.register(self._on_delete_original_config_show, (multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], multibot_constants.KEYWORDS['config']))
self.register(self._on_delete_original_config_show, (multibot_constants.KEYWORDS['show'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], multibot_constants.KEYWORDS['config'])) self.register(self._on_delete_original_config_show, (multibot_constants.KEYWORDS['show'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'], multibot_constants.KEYWORDS['config']))
self.register(self._on_dice, constants.KEYWORDS['dice'])
self.register(self._on_hello, multibot_constants.KEYWORDS['hello']) self.register(self._on_hello, multibot_constants.KEYWORDS['hello'])
self.register(self._on_new_message_default, default=True) self.register(self._on_new_message_default, default=True)
@@ -88,6 +95,8 @@ class FlanaBot(MultiBot, ABC):
self.register(self._on_no_delete_original, (multibot_constants.KEYWORDS['negate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'])) self.register(self._on_no_delete_original, (multibot_constants.KEYWORDS['negate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
self.register(self._on_no_delete_original, (multibot_constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message'])) self.register(self._on_no_delete_original, (multibot_constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['delete'], multibot_constants.KEYWORDS['message']))
self.register(self._on_poll, constants.KEYWORDS['poll'])
self.register(self._on_punish, constants.KEYWORDS['punish']) self.register(self._on_punish, constants.KEYWORDS['punish'])
self.register(self._on_punish, (multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['unpunish'])) self.register(self._on_punish, (multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['unpunish']))
self.register(self._on_punish, (multibot_constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['permission'])) self.register(self._on_punish, (multibot_constants.KEYWORDS['deactivate'], multibot_constants.KEYWORDS['permission']))
@@ -111,6 +120,11 @@ class FlanaBot(MultiBot, ABC):
self.register(self._on_song_info, constants.KEYWORDS['song_info']) self.register(self._on_song_info, constants.KEYWORDS['song_info'])
self.register(self._on_stop_poll, multibot_constants.KEYWORDS['deactivate'])
self.register(self._on_stop_poll, (multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['poll']))
self.register(self._on_stop_poll, multibot_constants.KEYWORDS['stop'])
self.register(self._on_stop_poll, (multibot_constants.KEYWORDS['stop'], constants.KEYWORDS['poll']))
self.register(self._on_unpunish, constants.KEYWORDS['unpunish']) self.register(self._on_unpunish, constants.KEYWORDS['unpunish'])
self.register(self._on_unpunish, (multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['punish'])) self.register(self._on_unpunish, (multibot_constants.KEYWORDS['deactivate'], constants.KEYWORDS['punish']))
@@ -131,8 +145,9 @@ class FlanaBot(MultiBot, ABC):
self.register(self._on_weather_chart_config_show, (constants.KEYWORDS['weather_chart'], multibot_constants.KEYWORDS['config'])) self.register(self._on_weather_chart_config_show, (constants.KEYWORDS['weather_chart'], multibot_constants.KEYWORDS['config']))
self.register(self._on_weather_chart_config_show, (multibot_constants.KEYWORDS['show'], constants.KEYWORDS['weather_chart'], multibot_constants.KEYWORDS['config'])) self.register(self._on_weather_chart_config_show, (multibot_constants.KEYWORDS['show'], constants.KEYWORDS['weather_chart'], multibot_constants.KEYWORDS['config']))
self.register_button(self._on_config_button_press, list(Chat.DEFAULT_CONFIG.keys())) self.register_button(self._on_config_button_press, ButtonsGroup.CONFIG)
self.register_button(self._on_weather_button_press, WeatherEmoji.values) self.register_button(self._on_poll_button_press, ButtonsGroup.POLL)
self.register_button(self._on_weather_button_press, ButtonsGroup.WEATHER)
async def _change_config(self, config_name: str, message: Message, value: bool = None): async def _change_config(self, config_name: str, message: Message, value: bool = None):
if value is None: if value is None:
@@ -174,18 +189,6 @@ class FlanaBot(MultiBot, ABC):
else: else:
await self.send(f'Castigado durante {TimeUnits(seconds=punishment_seconds).to_words()}.', message) await self.send(f'Castigado durante {TimeUnits(seconds=punishment_seconds).to_words()}.', message)
@staticmethod
def _get_config_buttons(config: dict | Chat | Message) -> list[list[str]]:
match config:
case Chat() as chat:
config = chat.config
case Message() as message:
config = message.chat.config
buttons_texts = [f"{'' if v else ''} {k}" for k, v in config.items()]
# noinspection PyTypeChecker
return flanautils.chunks(buttons_texts, 3)
@return_if_first_empty(exclude_self_types='FlanaBot', globals_=globals()) @return_if_first_empty(exclude_self_types='FlanaBot', globals_=globals())
async def _manage_exceptions(self, exceptions: BaseException | Iterable[BaseException], context: Chat | Message): async def _manage_exceptions(self, exceptions: BaseException | Iterable[BaseException], context: Chat | Message):
if not isinstance(exceptions, Iterable): if not isinstance(exceptions, Iterable):
@@ -296,20 +299,33 @@ class FlanaBot(MultiBot, ABC):
if not message.chat.is_group or self.is_bot_mentioned(message): if not message.chat.is_group or self.is_bot_mentioned(message):
await self.send_bye(message) await self.send_bye(message)
@bot_mentioned
async def _on_choose(self, message: Message):
discarded_words = {*constants.KEYWORDS['choose'], *constants.KEYWORDS['random'], self.name, f'<@{self.id}>'}
if final_words := [word for word in message.text.split() if not flanautils.cartesian_product_string_matching(word.lower(), discarded_words, min_ratio=multibot_constants.PARSE_CALLBACKS_MIN_RATIO_DEFAULT)]:
await self.send(random.choice(final_words), message)
else:
await self.send(random.choice(('¿Que elija el qué?', '¿Y las opciones?', '?', '🤔')), message)
async def _on_config_button_press(self, message: Message): async def _on_config_button_press(self, message: Message):
await self._accept_button_event(message) await self._accept_button_event(message)
if not message.button_pressed_user.is_admin or 'auto_' not in (config := message.button_pressed_text.split()[1]): if not message.buttons_info.presser_user.is_admin:
return return
config = message.buttons_info.pressed_text.split()[1]
message.chat.config[config] = not message.chat.config[config] message.chat.config[config] = not message.chat.config[config]
pressed_button = message.buttons_info[message.buttons_info.pressed_text]
pressed_button.is_checked = not pressed_button.is_checked
pressed_button.text = f"{'' if pressed_button.is_checked else ''} {config}"
await self.edit('<b>Estos son los ajustes del grupo:</b>\n\n', self._get_config_buttons(message), message) await self.edit('<b>Estos son los ajustes del grupo:</b>\n\n', message.buttons_info.buttons, message)
@group @group
@bot_mentioned @bot_mentioned
async def _on_config_list_show(self, message: Message): async def _on_config_list_show(self, message: Message):
await self.send('<b>Estos son los ajustes del grupo:</b>\n\n', self._get_config_buttons(message), message) buttons_texts = [(f"{'' if v else ''} {k}", v) for k, v in message.chat.config.items()]
await self.send('<b>Estos son los ajustes del grupo:</b>\n\n', flanautils.chunks(buttons_texts, 3), message, buttons_key=ButtonsGroup.CONFIG)
async def _on_covid_chart(self, message: Message): # todo2 async def _on_covid_chart(self, message: Message): # todo2
pass pass
@@ -365,6 +381,13 @@ class FlanaBot(MultiBot, ABC):
async def _on_delete_original_config_show(self, message: Message): async def _on_delete_original_config_show(self, message: Message):
await self._show_config('auto_delete_original', message) await self._show_config('auto_delete_original', message)
@bot_mentioned
async def _on_dice(self, message: Message):
if top_number := flanautils.sum_numbers_in_text(message.text):
await self.send(random.randint(1, top_number), message)
else:
await self.send(random.choice(('¿De cuántas caras?', '¿Y el número?', '?', '🤔')), message)
async def _on_hello(self, message: Message): async def _on_hello(self, message: Message):
if not message.chat.is_group or self.is_bot_mentioned(message): if not message.chat.is_group or self.is_bot_mentioned(message):
await self.send_hello(message) await self.send_hello(message)
@@ -410,6 +433,53 @@ class FlanaBot(MultiBot, ABC):
if not await self._on_scraping(message, delete_original=False): if not await self._on_scraping(message, delete_original=False):
await self._on_recover_message(message) await self._on_recover_message(message)
def _distribute_poll_buttons(self, texts: Sequence[str]) -> list[list[str]]:
pass
@bot_mentioned
async def _on_poll(self, message: Message):
discarded_words = {*constants.KEYWORDS['poll'], self.name.lower(), f'<@{self.id}>'}
if final_options := [option.title() for option in message.text.split() if not flanautils.cartesian_product_string_matching(option.lower(), discarded_words, min_ratio=multibot_constants.PARSE_CALLBACKS_MIN_RATIO_DEFAULT)]:
await self.send('Encuesta en curso...', self._distribute_poll_buttons(final_options), message, buttons_key=ButtonsGroup.POLL, contents={'poll': {'is_active': True, 'votes': {option: [] for option in final_options}}})
else:
await self.send(random.choice(('¿Y las opciones?', '?', '🤔')), message)
async def _on_poll_button_press(self, message: Message):
await self._accept_button_event(message)
if not message.contents['poll']['is_active']:
return
option_name = results[0] if (results := re.findall('(.*?) ➜.+', message.buttons_info.pressed_text)) else message.buttons_info.pressed_text
selected_option_votes = message.contents['poll']['votes'][option_name]
presser_id = message.buttons_info.presser_user.id
presser_name = message.buttons_info.presser_user.name.split('#')[0]
total_votes = sum(len(option_votes) for option_votes in message.contents['poll']['votes'].values())
if [presser_id, presser_name] in selected_option_votes:
selected_option_votes.remove([presser_id, presser_name])
total_votes -= 1
else:
for option_votes in message.contents['poll']['votes'].values():
try:
option_votes.remove([presser_id, presser_name])
except ValueError:
pass
else:
total_votes -= 1
break
selected_option_votes.append((presser_id, presser_name))
total_votes += 1
if total_votes:
buttons = []
for option, option_votes in message.contents['poll']['votes'].items():
percent = f'{round(len(option_votes) / total_votes * 100)}%'
names = f"({', '.join(option_vote[1] for option_vote in option_votes)})" if option_votes else ''
buttons.append(f'{option}{percent} {names}')
else:
buttons = list(message.contents['poll']['votes'].keys())
await self.edit(self._distribute_poll_buttons(buttons), message)
@bot_mentioned @bot_mentioned
@group @group
@admin(send_negative=True) @admin(send_negative=True)
@@ -513,6 +583,46 @@ class FlanaBot(MultiBot, ABC):
elif self.is_bot_mentioned(message) or not message.chat.is_group: 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) await self._manage_exceptions(SendError('No hay información musical en ese mensaje.'), message)
async def _on_stop_poll(self, message: Message):
if poll_message := message.replied_message:
if poll_message.contents.get('poll') is None:
return
elif (
self.is_bot_mentioned(message)
and
flanautils.cartesian_product_string_matching(message.text, constants.KEYWORDS['poll'], min_ratio=multibot_constants.PARSE_CALLBACKS_MIN_RATIO_DEFAULT)
and
(poll_message := Message.find_one({'contents.poll.is_active': True}, sort_keys=(('date', pymongo.DESCENDING),)))
):
poll_message = await self.get_message(poll_message.chat.id, poll_message.id)
else:
return
winners = []
max_votes = 1
for option, votes in poll_message.contents['poll']['votes'].items():
if len(votes) > max_votes:
winners = [option]
max_votes = len(votes)
elif len(votes) == max_votes:
winners.append(option)
match winners:
case [_, _, *_]:
winners = [f'<b>{winner}</b>' for winner in winners]
text = f"Encuesta finalizada. Los ganadores son: {flanautils.join_last_separator(winners, ', ', ' y ')}."
case [winner]:
text = f'Encuesta finalizada. Ganador: <b>{winner}</b>.'
case _:
text = 'Encuesta finalizada.'
poll_message.contents['poll']['is_active'] = False
await self.edit(text, poll_message)
if not message.replied_message:
await self.send(text, reply_to=poll_message)
@group @group
@bot_mentioned @bot_mentioned
@admin(send_negative=True) @admin(send_negative=True)
@@ -523,7 +633,7 @@ class FlanaBot(MultiBot, ABC):
async def _on_weather_button_press(self, message: Message): async def _on_weather_button_press(self, message: Message):
await self._accept_button_event(message) await self._accept_button_event(message)
match message.button_pressed_text: match message.buttons_info.pressed_text:
case WeatherEmoji.ZOOM_IN.value: case WeatherEmoji.ZOOM_IN.value:
message.weather_chart.zoom_in() message.weather_chart.zoom_in()
case WeatherEmoji.ZOOM_OUT.value: case WeatherEmoji.ZOOM_OUT.value:
@@ -676,6 +786,7 @@ class FlanaBot(MultiBot, ABC):
[WeatherEmoji.HUMIDITY.value, WeatherEmoji.PRECIPITATION_PROBABILITY.value, WeatherEmoji.PRECIPITATION_VOLUME.value, WeatherEmoji.PRESSURE.value, WeatherEmoji.WIND_SPEED.value] [WeatherEmoji.HUMIDITY.value, WeatherEmoji.PRECIPITATION_PROBABILITY.value, WeatherEmoji.PRECIPITATION_VOLUME.value, WeatherEmoji.PRESSURE.value, WeatherEmoji.WIND_SPEED.value]
], ],
message, message,
buttons_key=ButtonsGroup.WEATHER,
send_as_file=False send_as_file=False
) )
await self.send_inline_results(message) await self.send_inline_results(message)

View File

@@ -2,9 +2,11 @@ __all__ = ['FlanaDiscBot']
import asyncio import asyncio
import os import os
from typing import Sequence
import discord import discord
from multibot import BadRoleError, DiscordBot, User import flanautils
from multibot import BadRoleError, DiscordBot, User, constants as multibot_constants
from flanabot import constants from flanabot import constants
from flanabot.bots.flana_bot import FlanaBot from flanabot.bots.flana_bot import FlanaBot
@@ -42,6 +44,13 @@ class FlanaDiscBot(DiscordBot, FlanaBot):
super()._add_handlers() super()._add_handlers()
self.client.add_listener(self._on_voice_state_update, 'on_voice_state_update') self.client.add_listener(self._on_voice_state_update, 'on_voice_state_update')
# noinspection PyTypeChecker
def _distribute_poll_buttons(self, texts: Sequence[str]) -> list[list[str]]:
if len(texts) <= multibot_constants.DISCORD_BUTTONS_MAX:
return flanautils.chunks(texts, 1)
else:
return flanautils.chunks(texts, multibot_constants.DISCORD_BUTTONS_MAX)
async def _heat_channel(self, channel: discord.VoiceChannel): async def _heat_channel(self, channel: discord.VoiceChannel):
while True: while True:
await asyncio.sleep(constants.HEAT_PERIOD_SECONDS) await asyncio.sleep(constants.HEAT_PERIOD_SECONDS)

View File

@@ -4,8 +4,9 @@ __all__ = ['whitelisted_event', 'FlanaTeleBot']
import functools import functools
import os import os
from typing import Callable from typing import Callable, Sequence
import flanautils
import telethon.tl.functions import telethon.tl.functions
from multibot import TelegramBot, find_message, user_client from multibot import TelegramBot, find_message, user_client
@@ -46,6 +47,10 @@ class FlanaTeleBot(TelegramBot, FlanaBot):
# ----------------------------------------------------------- # # ----------------------------------------------------------- #
# -------------------- PROTECTED METHODS -------------------- # # -------------------- PROTECTED METHODS -------------------- #
# ----------------------------------------------------------- # # ----------------------------------------------------------- #
def _distribute_poll_buttons(self, texts: Sequence[str]) -> list[list[str]]:
# noinspection PyTypeChecker
return flanautils.chunks(texts, 1)
@user_client @user_client
async def _get_contacts_ids(self) -> list[int]: async def _get_contacts_ids(self) -> list[int]:
async with self.user_client: async with self.user_client:

View File

@@ -85,16 +85,20 @@ INSULTS = (
) )
KEYWORDS = { KEYWORDS = {
'choose': ('choose', 'elige'),
'covid_chart': ('case', 'caso', 'contagiado', 'contagio', 'corona', 'coronavirus', 'covid', 'covid19', 'death', 'covid_chart': ('case', 'caso', 'contagiado', 'contagio', 'corona', 'coronavirus', 'covid', 'covid19', 'death',
'disease', 'enfermedad', 'enfermos', 'fallecido', 'incidencia', 'jacovid', 'mascarilla', 'muerte', 'disease', 'enfermedad', 'enfermos', 'fallecido', 'incidencia', 'jacovid', 'mascarilla', 'muerte',
'muerto', 'pandemia', 'sick', 'virus'), 'muerto', 'pandemia', 'sick', 'virus'),
'currency_chart': ('argentina', 'bitcoin', 'cardano', 'cripto', 'crypto', 'criptodivisa', 'cryptodivisa', 'currency_chart': ('argentina', 'bitcoin', 'cardano', 'cripto', 'crypto', 'criptodivisa', 'cryptodivisa',
'cryptomoneda', 'cryptocurrency', 'currency', 'dinero', 'divisa', 'ethereum', 'inversion', 'cryptomoneda', 'cryptocurrency', 'currency', 'dinero', 'divisa', 'ethereum', 'inversion',
'moneda', 'pasta'), 'moneda', 'pasta'),
'dice': ('dado', 'dice'),
'poll': ('encuesta', 'poll', 'quiz'),
'punish': ('acaba', 'aprende', 'ataca', 'atalo', 'azota', 'boss', 'castiga', 'castigo', 'condena', 'controla', 'punish': ('acaba', 'aprende', 'ataca', 'atalo', 'azota', 'boss', 'castiga', 'castigo', 'condena', 'controla',
'destroy', 'destroza', 'duro', 'ejecuta', 'enseña', 'escarmiento', 'execute', 'finish', 'fuck', 'fusila', 'destroy', 'destroza', 'duro', 'ejecuta', 'enseña', 'escarmiento', 'execute', 'fuck', 'fusila', 'hell',
'hell', 'humos', 'infierno', 'jefe', 'jode', 'learn', 'leccion', 'lesson', 'manda', 'purgatorio', 'humos', 'infierno', 'jefe', 'jode', 'learn', 'leccion', 'lesson', 'manda', 'purgatorio', 'sancion',
'sancion', 'shoot', 'teach', 'termina', 'whip'), 'shoot', 'teach', 'whip'),
'random': ('aleatorio', 'azar', 'random'),
'scraping': ('api', 'aqui', 'busca', 'contenido', 'content', 'descarga', 'descargar', 'download', 'envia', 'habia', 'scraping': ('api', 'aqui', 'busca', 'contenido', 'content', 'descarga', 'descargar', 'download', 'envia', 'habia',
'media', 'redes', 'scrap', 'scraping', 'search', 'send', 'social', 'sociales', 'tenia', 'video', 'media', 'redes', 'scrap', 'scraping', 'search', 'send', 'social', 'sociales', 'tenia', 'video',
'videos'), 'videos'),

View File

@@ -15,7 +15,6 @@ from flanabot.models.weather_chart import WeatherChart
@dataclass(eq=False) @dataclass(eq=False)
class Message(MultiBotMessage): class Message(MultiBotMessage):
author: User = None author: User = None
button_pressed_user: User = None
mentions: Iterable[User] = field(default_factory=list) mentions: Iterable[User] = field(default_factory=list)
chat: Chat = None chat: Chat = None
replied_message: Message = None replied_message: Message = None