19 Commits

Author SHA1 Message Date
AlberLC
02d4965efa Fix CommonWords property name 2022-02-11 21:42:11 +01:00
AlberLC
e41338b5f6 Update workflows 2022-02-04 23:32:43 +01:00
AlberLC
47e371f270 Update README.rst 2022-02-02 01:46:16 +01:00
AlberLC
5746e99b3f Update README.rst 2022-02-02 01:43:17 +01:00
AlberLC
e1c14f7512 Update README.rst 2022-02-02 01:40:47 +01:00
AlberLC
503dfb4215 Update README.rst 2022-02-02 01:40:10 +01:00
AlberLC
a37c2ee1d7 Fix error message when inline weather 2022-01-31 06:22:02 +01:00
AlberLC
449df672b5 Update tests 2022-01-27 03:16:44 +01:00
AlberLC
d3b8c4f821 Update requirements.txt 2022-01-27 03:16:34 +01:00
AlberLC
acbd0e5ad1 Update requirements.txt 2022-01-24 05:01:57 +01:00
AlberLC
a0c693f5eb Improve string parse 2022-01-24 04:23:37 +01:00
AlberLC
5a500e71e3 Update Dockerfile 2022-01-23 01:35:42 +01:00
AlberLC
d6cdc1436f Add docker files 2022-01-21 20:07:46 +01:00
AlberLC
b9e61b296d Fix weather keywords 2022-01-18 22:05:20 +01:00
AlberLC
35583088ab Fix requirements.txt 2022-01-16 19:34:17 +01:00
AlberLC
73f2d4c485 Fix requirements.txt 2022-01-16 18:37:02 +01:00
AlberLC
a8cf321140 Fix requirements.txt encoding 2022-01-16 16:01:41 +01:00
AlberLC
944e473fac Fix .env load 2022-01-16 05:17:38 +01:00
AlberLC
f4ffa40263 Fix send scraping message when inline 2022-01-15 05:54:06 +01:00
10 changed files with 91 additions and 44 deletions

View File

@@ -22,10 +22,10 @@ jobs:
- name: Update setup.cfg - name: Update setup.cfg
run: | run: |
sed -i " sed -i "
s/{project_name}/${{ github.event.repository.name }}/g; s|{project_name}|${{ github.event.repository.name }}|g;
s/{project_version}/${{ github.ref_name }}/g; s|{project_version}|${{ github.ref_name }}|g;
s/{author}/${{ github.repository_owner }}/g; s|{author}|${{ github.repository_owner }}|g;
s/{description}/${{ github.event.repository.description }}/g s|{description}|${{ github.event.repository.description }}|g
" setup.cfg " setup.cfg
- name: Build package - name: Build package

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM flanaganvaquero/flanawright
WORKDIR /application
COPY flanabot flanabot
COPY venv/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
ENV PYTHONPATH=/application
CMD python3.10 flanabot/main.py

View File

@@ -3,7 +3,9 @@ FlanaBot
|license| |project_version| |python_version| |license| |project_version| |python_version|
Flanagan's bot. Bot based on `github.com/AlberLC/multibot`_ to manage Discord, Telegram and Twitch chats, moderate them and add functionalities.
|
Installation Installation
------------ ------------
@@ -14,6 +16,22 @@ Python 3.10 or higher is required.
pip install flanabot pip install flanabot
|
Features
--------
- Talks to users.
- Delete message batches.
- It works both in groups and in private chats.
- Understands numbers and amounts of time textually expressed (useful for deleting a message batch or saying "flanabot ban john for one hour and 20 minutes").
- Shows interactive via buttons charts of past, current and forecast weather.
- Change user roles temporarily or forever.
- Mute users temporarily or forever.
- Ban users temporarily or forever.
- Configurable default behavior for each chat, just talk to him to configure it.
- Get media from twitter, instagram and tiktok and send it to the chat. From tiktok also obtains data about the song that is playing in the video.
.. |license| image:: https://img.shields.io/github/license/AlberLC/flanabot?style=flat .. |license| image:: https://img.shields.io/github/license/AlberLC/flanabot?style=flat
:target: https://github.com/AlberLC/flanabot/blob/main/LICENSE :target: https://github.com/AlberLC/flanabot/blob/main/LICENSE
@@ -25,4 +43,6 @@ Python 3.10 or higher is required.
.. |python_version| image:: https://img.shields.io/pypi/pyversions/flanabot .. |python_version| image:: https://img.shields.io/pypi/pyversions/flanabot
:target: https://www.python.org/downloads/ :target: https://www.python.org/downloads/
:alt: PyPI - Python Version :alt: PyPI - Python Version
.. _github.com/AlberLC/multibot: https://github.com/AlberLC/multibot

17
docker-compose.yaml Normal file
View File

@@ -0,0 +1,17 @@
services:
flanabot:
image: flanaganvaquero/flanabot
build: .
env_file:
- .env
mongodb:
image: mongo
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
volumes:
- mongodata:/data/db
volumes:
mongodata:

View File

@@ -3,7 +3,7 @@ import datetime
import itertools import itertools
import pprint import pprint
import random import random
import time 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, Iterator, Type from typing import Iterable, Iterator, Type
@@ -36,7 +36,7 @@ class FlanaBot(MultiBot, ABC):
def _add_handlers(self): def _add_handlers(self):
super()._add_handlers() super()._add_handlers()
self.register(self._on_bye, constants.KEYWORDS['bye'], min_ratio=1) self.register(self._on_bye, constants.KEYWORDS['bye'])
self.register(self._on_config_list_show, constants.KEYWORDS['config']) 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['help'])
@@ -87,6 +87,7 @@ class FlanaBot(MultiBot, ABC):
self.register(self._on_hello, constants.KEYWORDS['hello']) self.register(self._on_hello, constants.KEYWORDS['hello'])
self.register(self._on_mute, constants.KEYWORDS['mute']) self.register(self._on_mute, constants.KEYWORDS['mute'])
self.register(self._on_mute, (('haz', 'se'), constants.KEYWORDS['mute']))
self.register(self._on_mute, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['unmute'])) 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_mute, (constants.KEYWORDS['deactivate'], constants.KEYWORDS['sound']))
@@ -303,7 +304,7 @@ class FlanaBot(MultiBot, ABC):
async def _search_medias(self, message: Message) -> OrderedSet[Media]: async def _search_medias(self, message: Message) -> OrderedSet[Media]:
bot_state_message: Message | None = None bot_state_message: Message | None = None
start_time = time.perf_counter() start_time = time_module.perf_counter()
results: Future = asyncio.gather( results: Future = asyncio.gather(
twitter.get_medias(message.text), twitter.get_medias(message.text),
@@ -312,9 +313,9 @@ class FlanaBot(MultiBot, ABC):
return_exceptions=True return_exceptions=True
) )
if self.is_bot_mentioned(message) or not message.chat.is_group: if not message.is_inline and (self.is_bot_mentioned(message) or not message.chat.is_group):
while not results.done(): while not results.done():
if constants.SCRAPING_MESSAGE_WAITING_TIME <= time.perf_counter() - start_time: if constants.SCRAPING_MESSAGE_WAITING_TIME <= time_module.perf_counter() - start_time:
bot_state_message = await self.send(random.choice(constants.SCRAPING_PHRASES), message) bot_state_message = await self.send(random.choice(constants.SCRAPING_PHRASES), message)
break break
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
@@ -573,23 +574,25 @@ class FlanaBot(MultiBot, ABC):
).split() ).split()
place_words = ( place_words = (
OrderedSet(original_text_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['show'], min_ratio=0.85).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['weather_chart'], min_ratio=0.85).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['date'], min_ratio=0.85).keys()
- flanautils.cartesian_product_string_matching(original_text_words, constants.KEYWORDS['thanks'], min_ratio=0.8).keys() - flanautils.cartesian_product_string_matching(original_text_words, constants.KEYWORDS['thanks'], min_ratio=0.85).keys()
- user_names_with_at_sign - user_names_with_at_sign
- user_names_without_at_sign - user_names_without_at_sign
- flanautils.CommonWords.words - flanautils.CommonWords.all_words
) )
if not place_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) if not message.is_inline:
await self.send_error(random.choice(('¿Tiempo dónde?', 'Indica el sitio.', 'Y el sitio?', 'y el sitio? me lo invento?')), message)
return return
if 'calle' in original_text_words: if 'calle' in original_text_words:
place_words.insert(0, 'calle') place_words.insert(0, 'calle')
place_query = ' '.join(place_words) place_query = ' '.join(place_words)
if len(place_query) >= constants.MAX_PLACE_QUERY_LENGTH: if len(place_query) >= constants.MAX_PLACE_QUERY_LENGTH:
await self.send_error(Media('resources/mucho_texto.png'), message) if not message.is_inline:
await self.send_error(Media('resources/mucho_texto.png'), message)
return return
if show_progress_state: if show_progress_state:
bot_state_message = await self.send(f'Buscando "{place_query}" en el mapa 🧐...', message) bot_state_message = await self.send(f'Buscando "{place_query}" en el mapa 🧐...', message)

View File

@@ -52,8 +52,8 @@ HELLO_PHRASES = ('alo', 'aloh', 'buenas', 'Hola.', 'hello', 'hey', 'hi', 'hola',
KEYWORDS = { KEYWORDS = {
'activate': ('activa', 'activar', 'activate', 'deja', 'dejale', 'devuelve', 'devuelvele', 'enable', 'encender', 'activate': ('activa', 'activar', 'activate', 'deja', 'dejale', 'devuelve', 'devuelvele', 'enable', 'encender',
'enciende', 'habilita', 'habilitar'), 'enciende', 'habilita', 'habilitar'),
'bye': ('adieu', 'adio', 'adiooooo', 'adios', 'agur', 'buenas', 'bye', 'cama', 'chao', 'dias', 'farewell', 'bye': ('adieu', 'adio', 'adiooooo', 'adios', 'agur', 'buenas', 'bye', 'cama', 'chao', 'farewell', 'goodbye',
'goodbye', 'hasta', 'luego', 'noches', 'pronto', 'taluego', 'taluegorl', 'tenga', 'vemos', 'vista', 'voy'), 'hasta', 'luego', 'noches', 'pronto', 'taluego', 'taluegorl', 'tenga', 'vemos', 'vista', 'voy'),
'change': ('alter', 'alternar', 'alternate', 'cambiar', 'change', 'default', 'defecto', 'edit', 'editar', 'change': ('alter', 'alternar', 'alternate', 'cambiar', 'change', 'default', 'defecto', 'edit', 'editar',
'exchange', 'modificar', 'modify', 'permutar', 'predeterminado', 'shift', 'swap', 'switch', 'turn', 'exchange', 'modificar', 'modify', 'permutar', 'predeterminado', 'shift', 'swap', 'switch', 'turn',
'vary'), 'vary'),
@@ -96,8 +96,8 @@ KEYWORDS = {
'cloudless', 'cloudy', 'cold', 'congelar', 'congelado', 'denbora', 'despejado', 'diluvio', 'frio', 'cloudless', 'cloudy', 'cold', 'congelar', 'congelado', 'denbora', 'despejado', 'diluvio', 'frio',
'frost', 'hielo', 'humedad', 'llover', 'llueva', 'llueve', 'lluvia', 'nevada', 'nieva', 'nieve', 'frost', 'hielo', 'humedad', 'llover', 'llueva', 'llueve', 'lluvia', 'nevada', 'nieva', 'nieve',
'nube', 'nubes', 'nublado', 'meteorologia', 'rain', 'snow', 'snowfall', 'snowstorm', 'sol', 'nube', 'nubes', 'nublado', 'meteorologia', 'rain', 'snow', 'snowfall', 'snowstorm', 'sol',
'solano', 'storm', 'sun', 'temperatura', 'tempo', 'tiempo', 'tormenta', 've', 'ventisca', 'solano', 'storm', 'sun', 'temperatura', 'tempo', 'tiempo', 'tormenta', 'ventisca', 'weather',
'weather', 'wetter') 'wetter')
} }
RECOVER_PHRASES = ( RECOVER_PHRASES = (

View File

@@ -1,14 +1,15 @@
import asyncio
import os import os
import flanautils import flanautils
os.environ |= flanautils.find_environment_variables('../.env')
import asyncio
from flanabot.bots.flana_tele_bot import FlanaTeleBot from flanabot.bots.flana_tele_bot import FlanaTeleBot
async def main(): async def main():
os.environ |= flanautils.find_environment_variables('../.env')
flana_tele_bot = FlanaTeleBot() flana_tele_bot = FlanaTeleBot()
await asyncio.gather( await asyncio.gather(

Binary file not shown.

0
tests/unit/__init__.py Normal file
View File

View File

@@ -1,16 +1,21 @@
import os
import flanautils
os.environ |= flanautils.find_environment_variables('../.env')
import unittest import unittest
from typing import Iterable from typing import Iterable
from flanabot import constants from multibot import constants as multibot_constants
import flanautils from flanabot.bots.flana_tele_bot import FlanaTeleBot
from flanabot.bots.flana_bots.flana_tele_bot import FlanaTeleBot
class TestParseCallbacks(unittest.TestCase): class TestParseCallbacks(unittest.TestCase):
def _test_no_always_callbacks(self, phrases: Iterable[str], callback: callable): def _test_no_always_callbacks(self, phrases: Iterable[str], callback: callable):
for i, phrase in enumerate(phrases): for i, phrase in enumerate(phrases):
with self.subTest(phrase): 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) callbacks = [registered_callback.callback for registered_callback in self.flana_tele_bot._parse_callbacks(phrase, multibot_constants.RATIO_REWARD_EXPONENT, multibot_constants.KEYWORDS_LENGHT_PENALTY, multibot_constants.MINIMUM_RATIO_TO_MATCH)
if not registered_callback.always] if not registered_callback.always]
self.assertEqual(1, len(callbacks)) self.assertEqual(1, len(callbacks))
self.assertEqual(callback, callbacks[0], f'\n\nExpected: {callback.__name__}\nActual: {callbacks[0].__name__}') self.assertEqual(callback, callbacks[0], f'\n\nExpected: {callback.__name__}\nActual: {callbacks[0].__name__}')
@@ -111,9 +116,9 @@ class TestParseCallbacks(unittest.TestCase):
def test_on_mute(self): def test_on_mute(self):
phrases = [ phrases = [
'silencia', # 'silencia',
'silencia al pavo ese', # 'silencia al pavo ese',
'calla a ese pesao', # 'calla a ese pesao',
'haz que se calle', 'haz que se calle',
'quitale el microfono a ese', 'quitale el microfono a ese',
'quitale el micro', 'quitale el micro',
@@ -124,13 +129,6 @@ class TestParseCallbacks(unittest.TestCase):
] ]
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_mute) 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): def test_on_new_message_default(self):
phrases = [ phrases = [
'asdqwergf', 'asdqwergf',
@@ -157,7 +155,6 @@ class TestParseCallbacks(unittest.TestCase):
def test_on_punish(self): def test_on_punish(self):
phrases = [ phrases = [
'acabo con el',
'acaba con el', 'acaba con el',
'destrozalo', 'destrozalo',
'ataca', 'ataca',
@@ -169,8 +166,6 @@ class TestParseCallbacks(unittest.TestCase):
'castigalo', 'castigalo',
'castiga a', 'castiga a',
'castiga', 'castiga',
'banealo',
'banea',
'enseña quien manda' 'enseña quien manda'
] ]
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_punish) self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_punish)
@@ -249,8 +244,7 @@ class TestParseCallbacks(unittest.TestCase):
'perdona a', 'perdona a',
'illo quitale a @flanagan el castigo', 'illo quitale a @flanagan el castigo',
'quita castigo', 'quita castigo',
'devuelve los permisos', 'devuelve los permisos'
'desbanea'
] ]
self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_unpunish) self._test_no_always_callbacks(phrases, self.flana_tele_bot._on_unpunish)
@@ -265,6 +259,8 @@ class TestParseCallbacks(unittest.TestCase):
'sol', 'sol',
'temperatura', 'temperatura',
'humedad', 'humedad',
'que tiempo hara mañana',
'que tiempo hara manana',
'que tiempo hace en malaga', 'que tiempo hace en malaga',
'que tiempo hace en calle larios', 'que tiempo hace en calle larios',
'tiempo rusia', 'tiempo rusia',