This commit is contained in:
m 2020-12-20 17:49:35 +01:00
parent 997986d892
commit b674928043
8 changed files with 9695 additions and 79 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ app/main_temp.py
database/people* database/people*
database/task* database/task*
.mypy_cache .mypy_cache
logs/**
secrets/**

View File

@ -41,7 +41,6 @@ def insert_task_in_db(*, db: sqlite3.Connection = task_db,
#TODO check existing #TODO check existing
insert_tuple = (task_type, place, message.text, message.message_id)
task_insertion = f"""INSERT INTO {task_table} task_insertion = f"""INSERT INTO {task_table}
(type, place, creation_message, creation_message_id, creation_chat_id (type, place, creation_message, creation_message_id, creation_chat_id
) )
@ -140,3 +139,7 @@ def list_places(db=task_db) -> list[str]:
FROM places_group_id FROM places_group_id
""").fetchall() """).fetchall()
return [place[0] for place in place_tuples] return [place[0] for place in place_tuples]
def dump(dbs) -> str:
raise NotImplementedError

View File

@ -1,33 +1,32 @@
"""business logic and database""" """business logic and database"""
from typing import Union, Optional from collections import defaultdict # , namedtuple
from collections import defaultdict #, namedtuple
from telegram import Update, Message, MessageEntity, User, Chat, ChatMember
from telegram.ext import CallbackContext
import database_interface as db
from sqlite3 import Error as DBError from sqlite3 import Error as DBError
from typing import Optional, Union
from telegram import Chat, ChatMember, Message, MessageEntity, Update, User
from telegram.ext import CallbackContext
import database_interface as db
InternaldictType = dict[MessageEntity, str] InternaldictType = dict[MessageEntity, str]
ClassifiedEntitiesdictType = dict[str, list[InternaldictType]] ClassifiedEntitiesdictType = dict[str, list[InternaldictType]]
def classify_entities(entities: InternaldictType) -> ClassifiedEntitiesdictType: # def classify_entities(entities: InternaldictType) -> ClassifiedEntitiesdictType:
"""classify entities by type and returns a dictionary with types as keys""" # """classify entities by type and returns a dictionary with types as keys"""
internaldict: InternaldictType = InternaldictType() # internaldict: InternaldictType = InternaldictType()
internaldictkeys = ("entity", "text") # internaldictkeys = ("entity", "text")
entities_bytype: ClassifiedEntitiesdictType = defaultdict(list) # entities_bytype: ClassifiedEntitiesdictType = defaultdict(list)
for entity, text in entities.items(): # for entity, text in entities.items():
internaldict = dict(zip(internaldictkeys, # internaldict = dict(zip(internaldictkeys,
[entity, text])) # [entity, text]))
entities_bytype[entity.type].append(internaldict) # entities_bytype[entity.type].append(internaldict)
return entities_bytype # return entities_bytype
def is_group(chat: Chat) -> bool: def is_group(chat: Chat) -> bool:
@ -63,7 +62,7 @@ def person_id_of(telegram_id: int) -> int:
def insert_task(update: Update) -> int: def insert_task(update: Update) -> int: # TODO refactor for gettin externally the command, the message, the place and the participants?
message = update.message message = update.message
if not is_group(update.effective_chat): if not is_group(update.effective_chat):
@ -71,6 +70,7 @@ def insert_task(update: Update) -> int:
# split command and determine participants, task type and place # split command and determine participants, task type and place
entities_bytype = classify_entities(message.parse_entities()) # TODO include captions to images etc"or message.parse_caption_entities())" entities_bytype = classify_entities(message.parse_entities()) # TODO include captions to images etc"or message.parse_caption_entities())"
task_kind: str = entities_bytype[MessageEntity.BOT_COMMAND][0]["text"][1:] task_kind: str = entities_bytype[MessageEntity.BOT_COMMAND][0]["text"][1:]
chat_id: int = update.effective_chat.id #NOTE TBD permit use of a h/cashtag for signaling that the task is in either place? chat_id: int = update.effective_chat.id #NOTE TBD permit use of a h/cashtag for signaling that the task is in either place?
@ -83,9 +83,11 @@ def insert_task(update: Update) -> int:
# except AttributeError: # the user does not exists # except AttributeError: # the user does not exists
# pass # pass
participants: list[User] = get_participants(entities_bytype) participants: list[User] = get_participants(entities_bytype)
print(entities_bytype)
participants_id: list[int] = get_ids_of(participants) participants_id: list[int] = get_ids_of(participants)
participants_id_unique: list[int] = remove_duplicates(participants_id) participants_id_unique: list[int] = remove_duplicates(participants_id)
print(f"participants: {[participant.first_name for participant in participants]}")
print(f"id: {participants_id}")
# insert in db # insert in db
inserted_task_id: int = db.insert_task_in_db(task_type=task_kind, chat_id=chat_id, inserted_task_id: int = db.insert_task_in_db(task_type=task_kind, chat_id=chat_id,
participants=participants_id_unique, message=message) participants=participants_id_unique, message=message)
@ -97,19 +99,43 @@ def remove_duplicates(in_list: Union[list, tuple]) -> list: # to export
def get_participants(entities_bytype: ClassifiedEntitiesdictType) -> list[User]: def get_participants(entities_bytype: ClassifiedEntitiesdictType) -> list[User]:
"""get unique users form a list of classified""" """get real, unique users form a list of classified"""
participants: list = [] participants: list = []
participants.append(entities_bytype[MessageEntity.MENTION]) participants.extend(entities_bytype[MessageEntity.MENTION])
participants.append(entities_bytype[MessageEntity.TEXT_MENTION]) participants.extend(entities_bytype[MessageEntity.TEXT_MENTION])
print(f"mentions: {entities_bytype[MessageEntity.MENTION]}")
users = map(lambda d_entity_text: d_entity_text["entity"].user , participants) print(f"text_mentions: {entities_bytype[MessageEntity.TEXT_MENTION]}")
unique_users = remove_duplicates(users) users = [ participant["entity"].user for participant in participants]
#users = map(lambda d_entity_text: d_entity_text["entity"].user , participants)
real_users = [user for user in users if user and not user.is_bot] # remove inexistent and bot users
unique_users = remove_duplicates([user for user in real_users if user])
return unique_users return unique_users
def get_ids_of(users: list[User]) -> list[int]: def get_mentions(message: Message) -> dict[MessageEntity, str]:
return list(map(lambda user: user.id)) """get all mentions and textmentions in the text and the caption.
returns a dictionary of the same type of Message.parse_entities() type"""
# mix of parse_caption_entities, parse_entities and MENTION, TEXT_MENTION
# done like this and not with [MessageEntity.MENTION, MessageEntity.TEXT_MENTION] because I no longer trust the parse function
# caption
mentions_caption_at = message.parse_caption_entities(MessageEntity.MENTION)
mentions_caption_textlike = message.parse_caption_entities(MessageEntity.TEXT_MENTION)
mentions_caption = mentions_caption_at | mentions_caption_textlike
# text message
mentions_textmessage_at = message.parse_entities(MessageEntity.MENTION)
mentions_textmessage_textlike = message.parse_entities(MessageEntity.TEXT_MENTION)
mentions_textmessage = mentions_textmessage_at | mentions_textmessage_textlike
mentions = mentions_text | mentions_at
return mentions
def get_ids_of(user_list: list[User]) -> list[int]:
return list(map(lambda user: user.id, user_list))
def get_administrators_id(chat: Chat) -> list[int]: #util def get_administrators_id(chat: Chat) -> list[int]: #util
@ -124,7 +150,8 @@ def is_admin(user: Union[User, int], chat: Chat) -> bool:
test = user in get_administrators_id(chat) test = user in get_administrators_id(chat)
return test return test
def dump_db(): def dump_db(dbs: list[str]) -> str:
#return db.dump(dbs)
raise NotImplementedError raise NotImplementedError

View File

@ -1,8 +1,10 @@
"""Collection of all text commands (commands that starts with / ). """Collection of all text commands (commands that starts with / ).
This module is the only message interface: other modules should not communicate with the telegram user.
Decorate a function with @command for marking it as a text command. Decorate a function with @command for marking it as a text command.
Functions here should be only for user interaction (messages etc). Functions here should be only for user interaction (messages etc).
""" """
from pprint import pprint
from sqlite3 import Error as DBError from sqlite3 import Error as DBError
from typing import Union, Optional from typing import Union, Optional
@ -18,6 +20,8 @@ import helpers as h
# TODO make a conf file # TODO make a conf file
DEVELOPMENT = True DEVELOPMENT = True
# decorator functions # decorator functions
__command_dict__ = dict() __command_dict__ = dict()
@ -35,22 +39,6 @@ def command(func, command_name: str = ""):
return func return func
# def command(command_name: str = ""):
# def inner(function):
# if not command_name:
# command_name = function.__name__
# global __command_dict__
# __command_dict__[command_name] = function
# @wraps(function)
# def wrapper(*args, **kwargs):
# function(*args, **kwargs)
# return wrapper
# return inner
def get_commands() -> dict: def get_commands() -> dict:
return __command_dict__ return __command_dict__
@ -173,11 +161,21 @@ def test(update: Update, context: CallbackContext) -> None:
print(reply or "None") print(reply or "None")
update.message.reply_text(reply or "None") update.message.reply_text(reply or "None")
@command @command
@admin_only @admin_only
@dev_only
@private_only
def dump(update: Update, context: CallbackContext) -> None: def dump(update: Update, context: CallbackContext) -> None:
"""dumps db""" """dumps db"""
reply_text = h.dump_db() msg_text = update.message.text
dbs_to_dump = []
if "task" in msg_text:
dbs_to_dump.append("task")
if "people" in msg_text:
dbs_to_dump.append("people")
reply_text = h.dump_db(dbs_to_dump)
update.message.reply_text(reply_text) update.message.reply_text(reply_text)
@ -232,35 +230,40 @@ def help(update: Update, context: CallbackContext) -> None:
## group task commands ## group task commands
@command @command
def insert_task(update: Update, context: CallbackContext, task_type: str) -> None: def register_task(update: Update, context: CallbackContext, task_type: str) -> None:
task_id: int = 0 message = update.message
reply_text = "" mentions = h.get_mentions(message)
try: participants = h.get_participants(h.classify_entities(mentions))
print(f"{participants=}")
thanks = "\nThanks to " + ", ".join(user.mention_html() for user in participants)
if participants:
task_id = h.insert_task(update) task_id = h.insert_task(update)
reply_text = f"OK, {task_type} task n.{task_id} registered." success_text = f"OK, {task_type} task n.{task_id} registered."
except Exception as e: reply_text = success_text + thanks
reply_text = "Task not inserted. Nothing done." + str(e) else:
update.message.reply_text(reply_text) reply_text = "Task not inserted. Nothing Done."
update.message.reply_html(reply_text)
@command @command
def assistance(update: Update, context: CallbackContext) -> None: def assistance(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "assistance") register_task(update, context, "assistance")
@command @command
def construction(update: Update, context: CallbackContext) -> None: def construction(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "construction") register_task(update, context, "construction")
@command @command
def use(update: Update, context: CallbackContext) -> None: def use(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "use") register_task(update, context, "use")
@command @command
def cleaning(update: Update, context: CallbackContext) -> None: def cleaning(update: Update, context: CallbackContext) -> None:
insert_task(update, context, "cleaning") register_task(update, context, "cleaning")
@command #dev @command #dev
@ -278,29 +281,13 @@ def register_group(update: Update, context: CallbackContext) -> None:
message = update.message message = update.message
place = h.place_name_from_message(messagedef insert_task_command_wrapper(message: Message, command_name: str) -> None: place = h.place_name_from_message(message)
# """creates the reply text for the insertion command"""
# task_type: str = command_name
# try: #TODO logging
# h.insert_task(message)
# except ValueError: #not a group
# reply_text = "This command must be used only in group chats. Nothing done."
# except DBError: # db error
# reply_text = "Database error, please retry."
# except: # other
# reply_text = "Unknown error, please retry."
# else: # success
# reply_text = f"{task_type.title()} task inserted." #TODO private message to users?
# #TODO user mentions?
)
# check if it is a new place, in this case put a wanring #TBD # check if it is a new place, in this case put a wanring #TBD
existing_places = h.list_places() existing_places = h.list_places()
if place not in existing_places: if place not in existing_places:
warning = dedent(f"""Warning: the place name you inserted, '{place}', does not exists yet. warning = f"Warning: the place name you inserted, '{place}', does not exists yet. \
The places currently registered were {existing_places}. The places currently registered were {existing_places}. \
Inserting it anyway... \n""" ) Inserting it anyway... \n"
success = h.register_group_as_place(message) success = h.register_group_as_place(message)
@ -308,7 +295,7 @@ def register_group(update: Update, context: CallbackContext) -> None:
if success: if success:
reply_text = f"Current group registered as {place} group " reply_text = f"Current group registered as {place} group "
else: else:
reply_text = "Failed to register. Nothing done" reply_text = "Not registered. Nothing done"
reply_text = warning + reply_text reply_text = warning + reply_text
message.reply_to_message(reply_text) message.reply_to_message(reply_text)

File diff suppressed because it is too large Load Diff