From d2b05ec1c9bf727af3593c8482fffe526a7e89d1 Mon Sep 17 00:00:00 2001 From: Daniel Moraes Date: Sat, 21 Mar 2026 22:45:10 -0300 Subject: [PATCH] =?UTF-8?q?Subida=20da=20vers=C3=A3o=20est=C3=A1vel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .BD_manager_Mysql.py.swn | Bin 0 -> 16384 bytes .BD_manager_Mysql.py.swo | Bin 0 -> 16384 bytes .gitignore | 207 ++++++++++++++ .idea/.gitignore | 8 + .idea/V1.iml | 18 ++ .idea/checkstyle-idea.xml | 16 ++ .idea/inspectionProfiles/Project_Default.xml | 7 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + BD_manager.py | 175 ++++++++++++ BD_manager_Mysql.py | 229 +++++++++++++++ LICENSE | 21 ++ Lembrator.py | 117 ++++++++ Limpar_Sessoes.py | 35 +++ Main_Receiver-Twilio01.py | 260 ++++++++++++++++++ Main_Receiver-Whatsapp.py | 241 ++++++++++++++++ Main_Receiver_Telegram.py | 232 ++++++++++++++++ Painel_Prestador_Mysql.py | 240 ++++++++++++++++ README.md | 88 ++++++ requirements.txt | Bin 0 -> 1648 bytes 21 files changed, 1914 insertions(+) create mode 100644 .BD_manager_Mysql.py.swn create mode 100644 .BD_manager_Mysql.py.swo create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/V1.iml create mode 100644 .idea/checkstyle-idea.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 BD_manager.py create mode 100644 BD_manager_Mysql.py create mode 100644 LICENSE create mode 100644 Lembrator.py create mode 100644 Limpar_Sessoes.py create mode 100644 Main_Receiver-Twilio01.py create mode 100644 Main_Receiver-Whatsapp.py create mode 100644 Main_Receiver_Telegram.py create mode 100644 Painel_Prestador_Mysql.py create mode 100644 requirements.txt diff --git a/.BD_manager_Mysql.py.swn b/.BD_manager_Mysql.py.swn new file mode 100644 index 0000000000000000000000000000000000000000..552afc80e71b549a2eb7ff90ffef4a7e1c2893ff GIT binary patch literal 16384 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 I4U`G~1MiO_$N&HU literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7faf40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,207 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$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 + +# 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 test / 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 + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.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/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/V1.iml b/.idea/V1.iml new file mode 100644 index 0000000..f936df0 --- /dev/null +++ b/.idea/V1.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml new file mode 100644 index 0000000..d7a6c2c --- /dev/null +++ b/.idea/checkstyle-idea.xml @@ -0,0 +1,16 @@ + + + + 10.24.0 + JavaOnly + true + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..9c69411 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c43501b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/BD_manager.py b/BD_manager.py new file mode 100644 index 0000000..03e9cc8 --- /dev/null +++ b/BD_manager.py @@ -0,0 +1,175 @@ +import sqlite3 +from typing import Optional, Dict, List, Any + +db_path = "agenda.db" + +def inserir_acompanhamento(chat_id: int, status: str, nome: str) -> None: + """ + Insere um registro na tabela usuarios. + + :param db_path: Caminho do banco de dados SQLite + :param chat_id: ID do chat (ex: Telegram) + :param status: Status do usuário + """ + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + query = """ + INSERT INTO acompanhamento (chat_id, status, nome) + VALUES (?, ?, ?) + """ + + cursor.execute(query, (chat_id, status, nome)) + conn.commit() + conn.close() + + + + +def atualizar_acompanhamento( + chat_id: int, + campo: str, + information: str +) -> bool: + """ + Atualiza o status do último registro (maior id) de um chat_id. + + :param db_path: Caminho do banco SQLite + :param chat_id: Chat ID a ser atualizado + :param novo_status: Novo status + :return: True se atualizou, False se não encontrou registro + """ + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Localiza o último registro do chat_id + cursor.execute( + """ + SELECT id + FROM acompanhamento + WHERE chat_id = ? + ORDER BY id DESC + LIMIT 1 + """, + (chat_id,) + ) + + row = cursor.fetchone() + + if row is None: + conn.close() + return False + + ultimo_id = row[0] + + # Atualiza apenas o registro mais recente + cursor.execute( + f"UPDATE acompanhamento SET {campo} = ? WHERE id = ?" +, + (information, ultimo_id) + ) + + conn.commit() + conn.close() + return True + +#atualizar_acompanhamento(614413127, "nome","zézé") + + +def buscar_ultimo_chat( + chat_id: int +) -> Optional[Dict[str, object]]: + """ + Retorna o último registro (maior id) de um chat_id. + + :param db_path: Caminho do banco SQLite + :param chat_id: Chat ID a ser consultado + :return: Dicionário com os dados ou None se não encontrar + """ + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + cursor.execute( + """ + SELECT id, chat_id, status, nome, data_event, time_event, horarios_disponiveis + FROM acompanhamento + WHERE chat_id = ? + ORDER BY id DESC + LIMIT 1 + """, + (chat_id,) + ) + + row = cursor.fetchone() + conn.close() + + if row is None: + return None + + return { + "id": row["id"], + "chat_id": row["chat_id"], + "status": row["status"], + "nome": row["nome"], + "data_event": row["data_event"], + "time_event": row["time_event"], + "horarios_disponiveis": row["horarios_disponiveis"], + + } + +#print(buscar_ultimo_chat(614413127)['horarios_disponiveis']) + +def inserir_evento(event_date, start_time, end_time, title, description, chat_id, name, created_by) -> None: + """ + Insere um registro na tabela usuarios. + + :param db_path: Caminho do banco de dados SQLite + :param chat_id: ID do chat (ex: Telegram) + :param status: Status do usuário + """ + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + query = """ + INSERT INTO events (event_date, start_time, end_time, title, description, chat_id, name, created_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """ + + cursor.execute(query, (event_date, start_time, end_time, title, description, chat_id, name, created_by)) + conn.commit() + conn.close() + +#--- Query agenda +def listar_agenda(campo: str, parametro: Any) -> List[Dict[str, Any]]: + colunas_permitidas = {"id","periodo", "data", "nome", "status"} # ajuste conforme sua tabela + + if campo not in colunas_permitidas: + raise ValueError("Campo inválido para consulta.") + + try: + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + query = f"SELECT * FROM agenda WHERE {campo} = ?;" + cursor.execute(query, (parametro,)) + + rows = cursor.fetchall() + return [dict(row) for row in rows] + + except sqlite3.Error as e: + print(f"Erro na consulta: {e}") + return [] + + finally: + if conn: + conn.close() + + +'''a = listar_agenda("periodo","manhã") +print(a) +print(a[1]['horario']) + +for z in a: + print(z['horario'])''' \ No newline at end of file diff --git a/BD_manager_Mysql.py b/BD_manager_Mysql.py new file mode 100644 index 0000000..09ca10e --- /dev/null +++ b/BD_manager_Mysql.py @@ -0,0 +1,229 @@ +import mysql.connector +from mysql.connector import Error +from typing import Optional, Dict, List, Dict, Any +from datetime import datetime + + + + +DB_CONFIG = { + "host": "mysql-1ee2345c-glauberroberto-0e4e.h.aivencloud.com", + "user": "avnadmin", + "password": "AVNS_rmoyFuLW827cdgCMPwh", + "database": "agenda", + "port": 27341 +} + +def get_connection(): + return mysql.connector.connect(**DB_CONFIG) + +def inserir_acompanhamento(chat_id: int, status: str, nome: str) -> None: + conn = get_connection() + cursor = conn.cursor() + + query = """ + INSERT INTO acompanhamento (chat_id, status, nome) + VALUES (%s, %s, %s) + """ + + cursor.execute(query, (chat_id, status, nome)) + conn.commit() + + cursor.close() + conn.close() + +def atualizar_acompanhamento(chat_id: int, campo: str, information: str) -> bool: + campos_permitidos = {"status", "nome", "data_event", "time_event"} + + if campo not in campos_permitidos: + raise ValueError("Campo não permitido") + + conn = get_connection() + cursor = conn.cursor() + + cursor.execute( + """ + SELECT id + FROM acompanhamento + WHERE chat_id = %s + ORDER BY id DESC + LIMIT 1 + """, + (chat_id,) + ) + + row = cursor.fetchone() + + if row is None: + cursor.close() + conn.close() + return False + + ultimo_id = row[0] + + query = f"UPDATE acompanhamento SET {campo} = %s WHERE id = %s" + cursor.execute(query, (information, ultimo_id)) + + conn.commit() + cursor.close() + conn.close() + return True + +def buscar_ultimo_chat(chat_id: int) -> Optional[Dict[str, object]]: + conn = get_connection() + cursor = conn.cursor(dictionary=True) + + cursor.execute( + """ + SELECT id, chat_id, status, nome, data_event, time_event + FROM acompanhamento + WHERE chat_id = %s + ORDER BY id DESC + LIMIT 1 + """, + (chat_id,) + ) + + row = cursor.fetchone() + + cursor.close() + conn.close() + + return row + + +def get_events(): + conn = get_connection() + cur = conn.cursor(dictionary=True) + cur.execute(""" + SELECT * FROM events + """) + events = cur.fetchall() + cur.close() + conn.close() + return events + +def update_events(id, campo, informacao): + try: + # Conexão com o banco de dados + conn = get_connection() + cursor = conn.cursor() + + cursor = conn.cursor() + + # Query de atualização + sql = f""" + UPDATE events + SET {campo} = {informacao} + WHERE id = {id}; + """ + + cursor.execute(sql) + conn.commit() # Confirma a transação + + print(f"{cursor.rowcount} registros atualizados.") + + except mysql.connector.Error as erro: + print(f"Erro ao atualizar: {erro}") + finally: + if conn.is_connected(): + cursor.close() + conn.close() + print("Conexão encerrada.") +#update_events(18, "avisos","1") + + + + + +def inserir_evento( + event_date, + start_time, + end_time, + title, + description, + chat_id, + name, + created_by +) -> None: + conn = get_connection() + cursor = conn.cursor() + + query = """ + INSERT INTO events + (event_date, start_time, end_time, title, description, chat_id, name, created_by) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + """ + + cursor.execute( + query, + (event_date, start_time, end_time, title, description, chat_id, name, created_by) + ) + + conn.commit() + cursor.close() + conn.close() + + + +#--- Query agenda +def listar_agenda(campo: str, parametro: Any) -> List[Dict[str, Any]]: + colunas_permitidas = {"id", "periodo", "data", "nome", "status"} # ajuste conforme sua tabela + + if campo not in colunas_permitidas: + raise ValueError("Campo inválido para consulta.") + + try: + conn = get_connection() + + cursor = conn.cursor(dictionary=True) + + query = f"SELECT * FROM agenda WHERE {campo} = %s AND disponibilidade = 'sim';" + cursor.execute(query, (parametro,)) + + rows = cursor.fetchall() + return rows + + except mysql.connector.Error as e: + print(f"Erro na consulta: {e}") + return [] + + finally: + if conn: + conn.close() + + + + + + +def atualizar_agenda(id, campo, informacao): + try: + # Conexão com o banco de dados + conn = get_connection() + cursor = conn.cursor() + + cursor = conn.cursor() + + # Query de atualização + sql = f""" + UPDATE agenda + SET {campo} = {informacao} + WHERE id = {id}; + """ + + cursor.execute(sql) + conn.commit() # Confirma a transação + + print(f"{cursor.rowcount} registros atualizados.") + + except mysql.connector.Error as erro: + print(f"Erro ao atualizar: {erro}") + finally: + if conn.is_connected(): + cursor.close() + conn.close() + print("Conexão encerrada.") + + +#atualizar_agenda(6, "disponibilidade", 7) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2f807a6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Glauberrf + +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. diff --git a/Lembrator.py b/Lembrator.py new file mode 100644 index 0000000..24e5568 --- /dev/null +++ b/Lembrator.py @@ -0,0 +1,117 @@ +from BD_manager_Mysql import get_events +from datetime import datetime +from time import sleep +import asyncio +from telegram import Bot +import os +from twilio.rest import Client +from dotenv import load_dotenv +from BD_manager_Mysql import update_events + + + +# ========================== +# Enviar mensagem WhatsApp +# ========================== +def enviar_whatsapp(numero_destino, mensagem): + + # Carrega variáveis do arquivo .env + load_dotenv() + + # Credenciais Twilio + ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID") + AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN") + TWILIO_WHATSAPP_NUMBER = os.getenv("TWILIO_WHATSAPP_NUMBER") + + + + + # Cria cliente Twilio + client = Client(ACCOUNT_SID, AUTH_TOKEN) + + numero_destino = numero_destino.replace("whatsapp:", "") + message = client.messages.create( + from_=TWILIO_WHATSAPP_NUMBER, + body=mensagem, + to=f"whatsapp:{numero_destino}" + ) + + return message.sid + + +TOKEN = "8402367863:AAGoEBHvoK7YRdTLXCBqaZ-PVQlFp_1V3zI" + +def enviar_mensagem_telegram(chat_id, mensagem): + async def enviar_mensagem(chat_id, mensagem): + bot = Bot(token=TOKEN) + await bot.send_message(chat_id=chat_id, text=mensagem) + + # chamada da função + asyncio.run(enviar_mensagem(chat_id, mensagem)) + + +while True: + sleep(3600) # Espera 10 segundos antes de verificar novamente + events = get_events() + + for event in events: + #print(f"ID: {event['id']}, Date: {event['event_date']}, Start: {event['start_time']}, End: {event['end_time']}, Title: {event['title']}, Description: {event['description']}, Chat ID: {event['chat_id']}, Name: {event['name']}, Created By: {event['created_by']}") + #print(event['event_date'],event['start_time']) + id_envent = event['id'] + event_date = event['event_date'] + start_time = event['start_time'] + name = event['name'] + chat_id = event['chat_id'] + avisos = event['avisos'] + created_by = event['created_by'] + + # Juntar data + hora em um único datetime + evento_datetime = datetime.strptime( + f"{event_date} {start_time}", + "%Y-%m-%d %H:%M:%S" + ) + + agora = datetime.now() # horário atual + + diferenca = evento_datetime - agora + horas_faltando = diferenca.total_seconds() / 3600 + + #quando faltar 24 horas um aviso será enviado + if(horas_faltando <= 24 and horas_faltando > 0 and avisos == "0"): + + print(f"Evento em {evento_datetime} - faltam {horas_faltando:.2f} horas") + if(created_by == "Telegram"): + mensagem = f"Olá {name}, voce tem um horario agendado dia {event_date} as {start_time}" + update_events(id_envent, "avisos", "1") + enviar_mensagem_telegram(str(chat_id), mensagem) + if(created_by == "Whatsapp"): + mensagem = f"Olá {name}, voce tem um horario agendado dia {event_date} as {start_time}" + update_events(id_envent, "avisos", "1") + enviar_whatsapp(chat_id, mensagem) + + #quando faltar 12 horas um aviso será enviado + if(horas_faltando <= 12 and horas_faltando > 0 and avisos == "1"): + + print(f"Evento em {evento_datetime} - faltam {horas_faltando:.2f} horas") + if(created_by == "Telegram"): + mensagem = f"Olá {name}, voce tem um horario agendado dia {event_date} as {start_time}" + update_events(id_envent, "avisos", "2") + enviar_mensagem_telegram(str(chat_id), mensagem) + if(created_by == "Whatsapp"): + mensagem = f"Olá {name}, voce tem um horario agendado dia {event_date} as {start_time}" + update_events(id_envent, "avisos", "2") + enviar_whatsapp(chat_id, mensagem) + + + #quando faltar 3 horas um aviso será enviado + if(horas_faltando <= 3 and horas_faltando > 0 and avisos == "2"): + + print(f"Evento em {evento_datetime} - faltam {horas_faltando:.2f} horas") + if(created_by == "Telegram"): + mensagem = f"Olá {name}, voce tem um horario agendado dia {event_date} as {start_time}" + update_events(id_envent, "avisos", "3") + enviar_mensagem_telegram(str(chat_id), mensagem) + if(created_by == "Whatsapp"): + mensagem = f"Olá {name}, voce tem um horario agendado dia {event_date} as {start_time}" + update_events(id_envent, "avisos", "3") + enviar_whatsapp(chat_id, mensagem) diff --git a/Limpar_Sessoes.py b/Limpar_Sessoes.py new file mode 100644 index 0000000..6feb07e --- /dev/null +++ b/Limpar_Sessoes.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +import os +from datetime import datetime, timedelta +from BD_manager_Mysql import limpar_sessoes_expiradas + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +LOG_FILE = os.path.join(SCRIPT_DIR, "log_limpeza.txt") + +def log_msg(msg): + """Escreve a mensagem no arquivo de log e imprime no console.""" + try: + with open(LOG_FILE, "a") as f: + f.write(f"{msg}\n") + print(msg) + except Exception as e: + print(f"Erro ao escrever no log: {e}") + +def limpar_registros_expirados(): + agora = datetime.now() + limite = agora - timedelta(minutes=2) + + try: + ids_removidos = limpar_sessoes_expiradas(limite) + + if ids_removidos: + ids_str = ",".join(map(str, ids_removidos)) + log_msg(f"[{agora.strftime('%Y-%m-%d %H:%M:%S')}] 🗑️ Removidos IDs: {ids_str}") + else: + pass + + except Exception as e: + log_msg(f"[{agora.strftime('%Y-%m-%d %H:%M:%S')}] ❌ Erro: {e}") + +if __name__ == "__main__": + limpar_registros_expirados() \ No newline at end of file diff --git a/Main_Receiver-Twilio01.py b/Main_Receiver-Twilio01.py new file mode 100644 index 0000000..728442b --- /dev/null +++ b/Main_Receiver-Twilio01.py @@ -0,0 +1,260 @@ +import os +from flask import Flask, request, jsonify +from twilio.rest import Client +from twilio.twiml.messaging_response import MessagingResponse +from dotenv import load_dotenv + +from datetime import datetime + +### +from BD_manager_Mysql import inserir_acompanhamento, buscar_ultimo_chat, atualizar_acompanhamento, inserir_evento, listar_agenda, atualizar_agenda + +### + +load_dotenv() + +app = Flask(__name__) + +# Credenciais +ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID") +AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN") +TWILIO_WHATSAPP_NUMBER = os.getenv("TWILIO_WHATSAPP_NUMBER") + +client = Client(ACCOUNT_SID, AUTH_TOKEN) + + +#Funções de fluxo +def MostrarPeriodos(chat_id, name, status): + # + + if(status == "10" or status == "null"): + inserir_acompanhamento(chat_id, "2", name) + + else: + atualizar_acompanhamento(chat_id, "status", "2") + + + resposta = f"Qual periodo você gostaria de agendar ?\n1-Manhã\n2-Tarde\n3-Noite" + return resposta + +def MostrarHorarios(texto_recebido, chat_id): + print("Entrou na função de consultar os horarios") + z = "" + try: + if(texto_recebido == "1"): + horarios = listar_agenda("periodo","manhã") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + elif(texto_recebido == "2"): + horarios = listar_agenda("periodo","tarde") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + elif(texto_recebido == "3"): + horarios = listar_agenda("periodo","noite") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + + + + else: + resposta = "Opção inválida, por favor digite um periodo válido\n1 - manhã\n2 - tarde\n3 - noite" + return resposta + except ValueError: + resposta = f"Você não digitou uma opção válida" + return resposta + +############### + + +# ========================== +# WEBHOOK (receber mensagem) +# ========================== +@app.route("/webhook", methods=["POST"]) +def webhook(): + texto_recebido = request.form.get("Body", "").strip().lower() + name = request.form.get("ProfileName") + sender = request.form.get("From") + + print(f"Mensagem recebida de {name}: {sender}: {texto_recebido}") + + chat_id = sender + + response = MessagingResponse() + resposta = response.message() + + + + print(f"Mensagem recebida de {chat_id}: {texto_recebido}") + + # Remove sufixo "@c.us" do número + #numero_remetente = numero_remetente.replace("@c.us", "") + chat_id = chat_id.replace("@c.us", "") + + try: + status = buscar_ultimo_chat(chat_id)['status'] + except: + status = "null" + + print("Status: ", status) + + #Começo do atendimento (Fluxo iniciado) + '''if(texto_recebido.lower() == "oi" or texto_recebido.lower() == "sim"): + resposta = f"Olá! você gostaria de agendar um horário?\n Digite\n1-SIM\n2-NÃO" + inserir_acompanhamento(chat_id, "1", name)''' + if(texto_recebido == "2" and status == "10") : + resposta.body("Sem problemas, qualquer coisa estou aqui") + print("ChatID: ", chat_id) + print("Status: ", status) + print("Status type: ", type(status)) + + + + + + #Mostrar periodos Manhã, tarde e Noite + elif(texto_recebido == "1" and status == "10" or texto_recebido == "1" and status == "null") : + print("Mostrar os periodos manhã, tarde e noite") + resposta.body(MostrarPeriodos(chat_id, name, status)) + + '''atualizar_acompanhamento(chat_id, "status", "2") + resposta = f"Qual periodo você gostaria de agendar ?\n1-Manhã\n2-Tarde\n3-Noite"''' + + + ##mostrar horarios + elif(status == "2") : + print("mostrar os horarios disponiveis") + resposta.body(MostrarHorarios(texto_recebido, chat_id)) + + #Seleção dos horarios disponiveis + elif(texto_recebido != "0" and status == "3") : + + try: + #converter data + id = texto_recebido + data_horario_agenda = listar_agenda("id",id) + + print("Data Agenda: ",data_horario_agenda) + print("Data agenda data: ",data_horario_agenda[0]['data']) + + #sqlite + #data_convertida = datetime.strptime(data_horario_agenda[0]['data'], "%d/%m/%Y").strftime("%Y-%m-%d") + #mysql + data_convertida = data_horario_agenda[0]['data'].strftime("%Y-%m-%d") + datetime.strptime(data_convertida, "%Y-%m-%d") + + atualizar_acompanhamento(chat_id, "data_event", data_convertida) + + atualizar_acompanhamento(chat_id, "time_event", data_horario_agenda[0]['horario']) + + atualizar_acompanhamento(chat_id, "status", "4") + + #atualizar a disponibilidade da agenda para não aparecer depois da data ser agendada + atualizar_agenda(int(texto_recebido), "disponibilidade", "'nao'") + + resposta.body(f"Você gostaria de adicionar algum comentários ?\n1 - Sim\n2 -Não") + + + except ValueError: + resposta.body(f"A data que você digitou não está no formato correto.\nDigite a data no seguinte formato dd/mm/yyyy") + except IndexError: + resposta.body("Nenhum evento encontrado para esse ID, por favor, selecione uma das datas que lhe enviei.") + + #Adicionar comentário caso a resposta seja sim para adicionar + elif(texto_recebido == "1" and status == "4"): + atualizar_acompanhamento(chat_id, "status", "5") + resposta.body("Por favor, escreva o seu comentário") + + #Capturando a mensagem para ser inserida no banco + elif(status == "5"): + atualizar_acompanhamento(chat_id, "status", "10") + data_agendada = buscar_ultimo_chat(chat_id)["data_event"] + #data_agendada = datetime.strptime(data_agendada, "%Y-%m-%d") + data_agendada_formatada = data_agendada.strftime("%d/%m/%Y") + + horario_agendado = buscar_ultimo_chat(chat_id)["time_event"] + + inserir_evento(buscar_ultimo_chat(chat_id)["data_event"],buscar_ultimo_chat(chat_id)["time_event"],"00:30:00","Padão Titulo",texto_recebido,chat_id,name,"Whatsapp") + resposta.body(f"Então agendamos para {data_agendada_formatada} as {horario_agendado} !\nObrigado !") + + #Caso a resposta de inserir uma mensagem seja "Não" + elif(texto_recebido == "2" and status == "4"): + atualizar_acompanhamento(chat_id, "status", "10") + data_agendada = buscar_ultimo_chat(chat_id)["data_event"] + #data_agendada = datetime.strptime(data_agendada, "%Y-%m-%d") + data_agendada_formatada = data_agendada.strftime("%d/%m/%Y") + + horario_agendado = buscar_ultimo_chat(chat_id)["time_event"] + + inserir_evento(buscar_ultimo_chat(chat_id)["data_event"],buscar_ultimo_chat(chat_id)["time_event"],"00:30:00","Padão Titulo",texto_recebido,chat_id,name,"Whatsapp") + resposta.body(f"Então agendamos para {data_agendada_formatada} as {horario_agendado} !\nObrigado !") + + + #voltar para priodo + elif(texto_recebido == "0" and status == "3"): + + atualizar_acompanhamento(chat_id, "status", "2") + resposta.body(MostrarPeriodos(chat_id, name, status)) + + + + + #Tratamento de mensagem inválida ao bot + elif(texto_recebido.lower() == "quem é você?" or texto_recebido.lower() == "quem e você?"): + resposta.body(f"Eu sou um Bot de agendamento!") + #await update.message.reply_text(resposta) + #resposta = f"Você escreveu: {texto_recebido}" + + elif(texto_recebido.lower() == "2" and status == "null" or texto_recebido.lower() == "2" and status == "10"): + resposta.body(f"Então tudo bem !\nSe precisar é só me chamar.") + + else: + resposta.body(f"Olá, você gostaria de agendar um horario ?\n1 - Sim\n2 - Não") + + return str(response) + + +# ========================== +# ENVIO ATIVO (via API REST) +# ========================== +@app.route("/send", methods=["POST"]) +def send_message(): + data = request.json + to_number = data.get("to") + message_text = data.get("message") + + try: + message = client.messages.create( + from_=TWILIO_WHATSAPP_NUMBER, + body=message_text, + to=f"whatsapp:{to_number}" + ) + + return jsonify({ + "status": "success", + "sid": message.sid + }), 200 + + except Exception as e: + return jsonify({ + "status": "error", + "message": str(e) + }), 500 + + +if __name__ == "__main__": + app.run(port=5000, debug=True) diff --git a/Main_Receiver-Whatsapp.py b/Main_Receiver-Whatsapp.py new file mode 100644 index 0000000..bb61ee8 --- /dev/null +++ b/Main_Receiver-Whatsapp.py @@ -0,0 +1,241 @@ + +from flask import Flask, request, jsonify +import requests +from datetime import datetime + +### +from BD_manager_Mysql import inserir_acompanhamento, buscar_ultimo_chat, atualizar_acompanhamento, inserir_evento, listar_agenda, atualizar_agenda + +### + + +#Funções de fluxo +def MostrarPeriodos(chat_id, name, status): + # + + if(status == "10" or status == "null"): + inserir_acompanhamento(chat_id, "2", name) + + else: + atualizar_acompanhamento(chat_id, "status", "2") + + + resposta = f"Qual periodo você gostaria de agendar ?\n1-Manhã\n2-Tarde\n3-Noite" + return resposta + +def MostrarHorarios(texto_recebido, chat_id): + print("Entrou na função de consultar os horarios") + z = "" + try: + if(texto_recebido == "1"): + horarios = listar_agenda("periodo","manhã") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + elif(texto_recebido == "2"): + horarios = listar_agenda("periodo","tarde") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + elif(texto_recebido == "3"): + horarios = listar_agenda("periodo","noite") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + + + + else: + resposta = "Opção inválida, por favor digite um periodo válido\n1 - manhã\n2 - tarde\n3 - noite" + return resposta + except ValueError: + resposta = f"Você não digitou uma opção válida" + return resposta + +############### + + + +app = Flask(__name__) + +# 🔧 Configurações da sua instância UltraMsg +INSTANCE_ID = "" +TOKEN = "" + +# URL da API para enviar mensagens +API_URL = f"https://api.ultramsg.com/{INSTANCE_ID}/messages/chat" + +@app.route('/webhook', methods=['POST']) +def webhook(): + payload = request.json + print("🔵 DADOS RECEBIDOS:") + print(payload) + + # Extrai a mensagem de dentro de 'data' + data = payload.get("data", {}) + texto_recebido = data.get("body", "") + #numero_remetente = data.get("from", "") + chat_id = data.get("from", "") + name = data.get("pushname", "") + + resposta = "" + + print(f"Mensagem recebida de {chat_id}: {texto_recebido}") + + # Remove sufixo "@c.us" do número + #numero_remetente = numero_remetente.replace("@c.us", "") + chat_id = chat_id.replace("@c.us", "") + + try: + status = buscar_ultimo_chat(chat_id)['status'] + except: + status = "null" + + print("Status: ", status) + + #Começo do atendimento (Fluxo iniciado) + '''if(texto_recebido.lower() == "oi" or texto_recebido.lower() == "sim"): + resposta = f"Olá! você gostaria de agendar um horário?\n Digite\n1-SIM\n2-NÃO" + inserir_acompanhamento(chat_id, "1", name)''' + if(texto_recebido == "2" and status == "10") : + resposta = "Sem problemas, qualquer coisa estou aqui" + print("ChatID: ", chat_id) + print("Status: ", status) + print("Status type: ", type(status)) + + + + + + #Mostrar periodos Manhã, tarde e Noite + elif(texto_recebido == "1" and status == "10" or texto_recebido == "1" and status == "null") : + print("Mostrar os periodos manhã, tarde e noite") + resposta = MostrarPeriodos(chat_id, name, status) + + '''atualizar_acompanhamento(chat_id, "status", "2") + resposta = f"Qual periodo você gostaria de agendar ?\n1-Manhã\n2-Tarde\n3-Noite"''' + + + ##mostrar horarios + elif(status == "2") : + print("mostrar os horarios disponiveis") + resposta = MostrarHorarios(texto_recebido, chat_id) + + #Seleção dos horarios disponiveis + elif(texto_recebido != "0" and status == "3") : + + try: + #converter data + id = texto_recebido + data_horario_agenda = listar_agenda("id",id) + + print("Data Agenda: ",data_horario_agenda) + print("Data agenda data: ",data_horario_agenda[0]['data']) + + #sqlite + #data_convertida = datetime.strptime(data_horario_agenda[0]['data'], "%d/%m/%Y").strftime("%Y-%m-%d") + #mysql + data_convertida = data_horario_agenda[0]['data'].strftime("%Y-%m-%d") + datetime.strptime(data_convertida, "%Y-%m-%d") + + atualizar_acompanhamento(chat_id, "data_event", data_convertida) + + atualizar_acompanhamento(chat_id, "time_event", data_horario_agenda[0]['horario']) + + atualizar_acompanhamento(chat_id, "status", "4") + + #atualizar a disponibilidade da agenda para não aparecer depois da data ser agendada + atualizar_agenda(int(texto_recebido), "disponibilidade", "1") + + resposta = f"Você gostaria de adicionar algum comentários ?\n1 - Sim\n2 -Não" + + + except ValueError: + resposta = f"A data que você digitou não está no formato correto.\nDigite a data no seguinte formato dd/mm/yyyy" + except IndexError: + resposta = "Nenhum evento encontrado para esse ID, por favor, selecione uma das datas que lhe enviei." + + #Adicionar comentário caso a resposta seja sim para adicionar + elif(texto_recebido == "1" and status == "4"): + atualizar_acompanhamento(chat_id, "status", "5") + resposta = "Por favor, escreva o seu comentário" + + #Capturando a mensagem para ser inserida no banco + elif(status == "5"): + atualizar_acompanhamento(chat_id, "status", "10") + data_agendada = buscar_ultimo_chat(chat_id)["data_event"] + #data_agendada = datetime.strptime(data_agendada, "%Y-%m-%d") + data_agendada_formatada = data_agendada.strftime("%d/%m/%Y") + + horario_agendado = buscar_ultimo_chat(chat_id)["time_event"] + + inserir_evento(buscar_ultimo_chat(chat_id)["data_event"],buscar_ultimo_chat(chat_id)["time_event"],"00:30:00","Padão Titulo",texto_recebido,chat_id,name,"Telegram") + resposta = f"Então agendamos para {data_agendada_formatada} as {horario_agendado} !\nObrigado !" + + #Caso a resposta de inserir uma mensagem seja "Não" + elif(texto_recebido == "2" and status == "4"): + atualizar_acompanhamento(chat_id, "status", "10") + data_agendada = buscar_ultimo_chat(chat_id)["data_event"] + #data_agendada = datetime.strptime(data_agendada, "%Y-%m-%d") + data_agendada_formatada = data_agendada.strftime("%d/%m/%Y") + + horario_agendado = buscar_ultimo_chat(chat_id)["time_event"] + + inserir_evento(buscar_ultimo_chat(chat_id)["data_event"],buscar_ultimo_chat(chat_id)["time_event"],"00:30:00","Padão Titulo",texto_recebido,chat_id,name,"Telegram") + resposta = f"Então agendamos para {data_agendada_formatada} as {horario_agendado} !\nObrigado !" + + + #voltar para priodo + elif(texto_recebido == "0" and status == "3"): + + atualizar_acompanhamento(chat_id, "status", "2") + resposta = MostrarPeriodos(chat_id, name, status) + + + + + #Tratamento de mensagem inválida ao bot + elif(texto_recebido.lower() == "quem é você?" or texto_recebido.lower() == "quem e você?"): + resposta = f"Eu sou um Bot de agendamento!" + #await update.message.reply_text(resposta) + #resposta = f"Você escreveu: {texto_recebido}" + + elif(texto_recebido.lower() == "2" and status == "null" or texto_recebido.lower() == "2" and status == "10"): + resposta = f"Então tudo bem !\nSe precisar é só me chamar." + + else: + resposta = f"Olá, você gostaria de agendar um horario ?\n1 - Sim\n2 - Não" + + + + # Envia a resposta + API_URL = f"https://api.ultramsg.com/{INSTANCE_ID}/messages/chat" + payload_resposta = { + "token": TOKEN, + #"to": numero_remetente, + "to": chat_id, + "body": resposta + } + + r = requests.post(API_URL, data=payload_resposta) + print("🟢 Enviando resposta:", resposta) + print("🟡 Status da API:", r.status_code) + print("🔴 Resposta da API:", r.text) + + return jsonify({"status": "mensagem processada"}), 200 + + +if __name__ == '__main__': + app.run(port=5000) diff --git a/Main_Receiver_Telegram.py b/Main_Receiver_Telegram.py new file mode 100644 index 0000000..a2c9e8b --- /dev/null +++ b/Main_Receiver_Telegram.py @@ -0,0 +1,232 @@ +from telegram import Update +from telegram.ext import ( + ApplicationBuilder, + ContextTypes, + MessageHandler, + CommandHandler, + filters +) +from datetime import datetime + +import json + + +#from BD_manager import inserir_acompanhamento, buscar_ultimo_chat, atualizar_acompanhamento, inserir_evento, listar_agenda + +from BD_manager_Mysql import inserir_acompanhamento, buscar_ultimo_chat, atualizar_acompanhamento, inserir_evento, listar_agenda, atualizar_agenda + +TOKEN = "8402367863:AAGoEBHvoK7YRdTLXCBqaZ-PVQlFp_1V3zI" +#TOKEN = st.secrets["api"]["token"] + + +#Funções de fluxo +def MostrarPeriodos(chat_id, name, status): + # + + if(status == "10" or status == "null"): + inserir_acompanhamento(chat_id, "2", name) + + else: + atualizar_acompanhamento(chat_id, "status", "2") + + + resposta = f"Qual periodo você gostaria de agendar ?\n1-Manhã\n2-Tarde\n3-Noite" + return resposta + +def MostrarHorarios(texto_recebido, chat_id): + print("Entrou na função de consultar os horarios") + z = "" + try: + if(texto_recebido == "1"): + horarios = listar_agenda("periodo","manhã") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + elif(texto_recebido == "2"): + horarios = listar_agenda("periodo","tarde") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + elif(texto_recebido == "3"): + horarios = listar_agenda("periodo","noite") + resposta = horarios + for horario in horarios: + z = z + str(horario['id'])+" - "+horario['data'].strftime("%d/%m/%Y")+" as "+str(horario['horario'])+"\n" + resposta = f"Qual horario você gostaria de agendar ?\n"+z+"\n0 - Voltar" + atualizar_acompanhamento(chat_id, "status", "3") + return resposta + + + + + else: + resposta = "Opção inválida, por favor digite um periodo válido\n1 - manhã\n2 - tarde\n3 - noite" + return resposta + except ValueError: + resposta = f"Você não digitou uma opção válida" + return resposta + +############### + +# Comando /start +async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): + await update.message.reply_text( + "Olá! Eu sou um bot de agendamento da Malu.\nComo vai ?" + + + ) + +# Recebe mensagens de texto +async def receber_mensagem(update: Update, context: ContextTypes.DEFAULT_TYPE): + texto_recebido = update.message.text + chat_id = update.effective_chat.id + name = update.effective_chat.full_name + print(f"{chat_id=}, {name=}") + + print(f"{texto_recebido=}") + + try: + status = buscar_ultimo_chat(chat_id)['status'] + except: + status = "null" + + print("Status: ", status) + + #Começo do atendimento (Fluxo iniciado) + '''if(texto_recebido.lower() == "oi" or texto_recebido.lower() == "sim"): + resposta = f"Olá! você gostaria de agendar um horário?\n Digite\n1-SIM\n2-NÃO" + inserir_acompanhamento(chat_id, "1", name)''' + if(texto_recebido == "2" and status == "10") : + resposta = "Sem problemas, qualquer coisa estou aqui" + print("ChatID: ", chat_id) + print("Status: ", status) + print("Status type: ", type(status)) + + + + + + #Mostrar periodos Manhã, tarde e Noite + elif(texto_recebido == "1" and status == "10" or texto_recebido == "1" and status == "null") : + print("Mostrar os periodos manhã, tarde e noite") + resposta = MostrarPeriodos(chat_id, name, status) + + '''atualizar_acompanhamento(chat_id, "status", "2") + resposta = f"Qual periodo você gostaria de agendar ?\n1-Manhã\n2-Tarde\n3-Noite"''' + + + ##mostrar horarios + elif(status == "2") : + print("mostrar os horarios disponiveis") + resposta = MostrarHorarios(texto_recebido, chat_id) + + #Seleção dos horarios disponiveis + elif(texto_recebido != "0" and status == "3") : + + try: + #converter data + id = texto_recebido + data_horario_agenda = listar_agenda("id",id) + + print("Data Agenda: ",data_horario_agenda) + print("Data agenda data: ",data_horario_agenda[0]['data']) + + #sqlite + #data_convertida = datetime.strptime(data_horario_agenda[0]['data'], "%d/%m/%Y").strftime("%Y-%m-%d") + #mysql + data_convertida = data_horario_agenda[0]['data'].strftime("%Y-%m-%d") + datetime.strptime(data_convertida, "%Y-%m-%d") + + atualizar_acompanhamento(chat_id, "data_event", data_convertida) + + atualizar_acompanhamento(chat_id, "time_event", data_horario_agenda[0]['horario']) + + atualizar_acompanhamento(chat_id, "status", "4") + + #atualizar a disponibilidade da agenda para não aparecer depois da data ser agendada + atualizar_agenda(int(texto_recebido), "disponibilidade", "'nao'") + + resposta = f"Você gostaria de adicionar algum comentários ?\n1 - Sim\n2 -Não" + + + except ValueError: + resposta = f"A data que você digitou não está no formato correto.\nDigite a data no seguinte formato dd/mm/yyyy" + except IndexError: + resposta = "Nenhum evento encontrado para esse ID, por favor, selecione uma das datas que lhe enviei." + + #Adicionar comentário caso a resposta seja sim para adicionar + elif(texto_recebido == "1" and status == "4"): + atualizar_acompanhamento(chat_id, "status", "5") + resposta = "Por favor, escreva o seu comentário" + + #Capturando a mensagem para ser inserida no banco + elif(status == "5"): + atualizar_acompanhamento(chat_id, "status", "10") + data_agendada = buscar_ultimo_chat(chat_id)["data_event"] + #data_agendada = datetime.strptime(data_agendada, "%Y-%m-%d") + data_agendada_formatada = data_agendada.strftime("%d/%m/%Y") + + horario_agendado = buscar_ultimo_chat(chat_id)["time_event"] + + inserir_evento(buscar_ultimo_chat(chat_id)["data_event"],buscar_ultimo_chat(chat_id)["time_event"],"00:30:00","Padão Titulo",texto_recebido,chat_id,name,"Telegram") + resposta = f"Então agendamos para {data_agendada_formatada} as {horario_agendado} !\nObrigado !" + + #Caso a resposta de inserir uma mensagem seja "Não" + elif(texto_recebido == "2" and status == "4"): + atualizar_acompanhamento(chat_id, "status", "10") + data_agendada = buscar_ultimo_chat(chat_id)["data_event"] + #data_agendada = datetime.strptime(data_agendada, "%Y-%m-%d") + data_agendada_formatada = data_agendada.strftime("%d/%m/%Y") + + horario_agendado = buscar_ultimo_chat(chat_id)["time_event"] + + inserir_evento(buscar_ultimo_chat(chat_id)["data_event"],buscar_ultimo_chat(chat_id)["time_event"],"00:30:00","Padão Titulo",texto_recebido,chat_id,name,"Telegram") + resposta = f"Então agendamos para {data_agendada_formatada} as {horario_agendado} !\nObrigado !" + + + #voltar para priodo + elif(texto_recebido == "0" and status == "3"): + + atualizar_acompanhamento(chat_id, "status", "2") + resposta = MostrarPeriodos(chat_id, name, status) + + + + + #Tratamento de mensagem inválida ao bot + elif(texto_recebido.lower() == "quem é você?" or texto_recebido.lower() == "quem e você?"): + resposta = f"Eu sou um Bot de agendamento!" + #await update.message.reply_text(resposta) + #resposta = f"Você escreveu: {texto_recebido}" + elif(texto_recebido.lower() == "2" and status == "null" or texto_recebido.lower() == "2" and status == "10"): + #elif(texto_recebido.lower() == "2" and buscar_ultimo_chat(chat_id)['status'] == "1"): + resposta = f"Então tudo bem !\nSe precisar é só me chamar." + + else: + resposta = f"Olá, você gostaria de agendar um horario ?\n1 - Sim\n2 - Não" + await update.message.reply_text(resposta) + +def main(): + app = ApplicationBuilder().token(TOKEN).build() + + # Comandos + app.add_handler(CommandHandler("start", start)) + + # Mensagens de texto + app.add_handler( + MessageHandler(filters.TEXT & ~filters.COMMAND, receber_mensagem) + ) + + print("Bot em execução...") + app.run_polling() + +if __name__ == "__main__": + main() diff --git a/Painel_Prestador_Mysql.py b/Painel_Prestador_Mysql.py new file mode 100644 index 0000000..dc9656d --- /dev/null +++ b/Painel_Prestador_Mysql.py @@ -0,0 +1,240 @@ +import streamlit as st +import mysql.connector +import hashlib +from datetime import date, time, datetime, timedelta +from streamlit_calendar import calendar + +# ========================= +# CONFIG +# ========================= +st.set_page_config(page_title="Agenda Compartilhada", layout="wide") + +DB_CONFIG = { + "host": st.secrets["db"]["host"], + "user": st.secrets["db"]["user"], + "password": st.secrets["db"]["password"], + "database": st.secrets["db"]["database"], + "port": st.secrets["db"]["port"] +} + +# ========================= +# UTILS +# ========================= +def timedelta_to_time(td): + seconds = int(td.total_seconds()) + return time(seconds // 3600, (seconds % 3600) // 60) + +# ========================= +# DATABASE +# ========================= +def get_connection(): + return mysql.connector.connect(**DB_CONFIG) + +# ========================= +# AUTH +# ========================= +def hash_password(password): + return hashlib.sha256(password.encode()).hexdigest() + +def authenticate(username, password): + conn = get_connection() + cur = conn.cursor(dictionary=True) + cur.execute( + "SELECT id FROM users WHERE username=%s AND password_hash=%s", + (username, hash_password(password)) + ) + user = cur.fetchone() + cur.close() + conn.close() + return user is not None + +# ========================= +# EVENTS +# ========================= +def get_events(): + conn = get_connection() + cur = conn.cursor(dictionary=True) + cur.execute("SELECT * FROM events") + rows = cur.fetchall() + cur.close() + conn.close() + return rows + +def add_event(data): + conn = get_connection() + cur = conn.cursor() + cur.execute(""" + INSERT INTO events + (event_date,start_time,end_time,title,description,chat_id,name,created_by) + VALUES (%s,%s,%s,%s,%s,%s,%s,%s) + """, tuple(data.values())) + conn.commit() + cur.close() + conn.close() + +def update_event(event_id, data): + conn = get_connection() + cur = conn.cursor() + cur.execute(""" + UPDATE events SET + event_date=%s,start_time=%s,end_time=%s, + title=%s,description=%s,chat_id=%s,name=%s + WHERE id=%s + """, (*data.values(), event_id)) + conn.commit() + cur.close() + conn.close() + +def delete_event(event_id): + conn = get_connection() + cur = conn.cursor() + cur.execute("DELETE FROM events WHERE id=%s", (event_id,)) + conn.commit() + cur.close() + conn.close() + +# ========================= +# SESSION +# ========================= +st.session_state.setdefault("logged_in", False) +st.session_state.setdefault("mode", "idle") +st.session_state.setdefault("selected_event", None) +st.session_state.setdefault("selected_date", None) + +# ========================= +# LOGIN +# ========================= +if not st.session_state.logged_in: + st.title("🔐 Login") + u = st.text_input("Usuário") + p = st.text_input("Senha", type="password") + if st.button("Entrar"): + if authenticate(u, p): + st.session_state.logged_in = True + st.session_state.username = u + st.rerun() + else: + st.error("Credenciais inválidas") + st.stop() + +# ========================= +# APP +# ========================= +st.title("📅 Agenda Compartilhada") + +col_cal, col_form = st.columns([2, 1]) + +events_db = get_events() + +calendar_events = [] +for ev in events_db: + calendar_events.append({ + "id": ev["id"], + "title": ev["title"], + "start": datetime.combine( + ev["event_date"], + timedelta_to_time(ev["start_time"]) + ).isoformat(), + "end": datetime.combine( + ev["event_date"], + timedelta_to_time(ev["end_time"]) + ).isoformat(), + }) + +# ========================= +# CALENDAR (LEFT) +# ========================= +with col_cal: + cal = calendar( + events=calendar_events, + options={"initialView": "dayGridMonth", "selectable": True}, + custom_css=".fc { font-size: 0.85rem; }" + ) + +# ========================= +# CALENDAR INTERACTIONS +# ========================= +if cal.get("dateClick"): + st.session_state.mode = "new" + st.session_state.selected_date = datetime.fromisoformat( + cal["dateClick"]["date"] + ).date() + +if cal.get("eventClick"): + st.session_state.mode = "edit" + st.session_state.selected_event = next( + e for e in events_db if e["id"] == int(cal["eventClick"]["event"]["id"]) + ) + +# ========================= +# SIDE PANEL (RIGHT) +# ========================= +with col_form: + st.subheader("🛠 Ações") + + # ------------------------- + # NEW EVENT + # ------------------------- + if st.session_state.mode == "new": + st.info(f"Novo evento em {st.session_state.selected_date}") + + with st.form("new_event"): + d = st.date_input("Data", st.session_state.selected_date) + s = st.time_input("Início", time(9, 0)) + e = st.time_input("Fim", time(10, 0)) + t = st.text_input("Título") + desc = st.text_area("Descrição") + chat = st.text_input("Chat ID") + name = st.text_input("Nome") + + if st.form_submit_button("Salvar"): + add_event({ + "event_date": d, + "start_time": s, + "end_time": e, + "title": t, + "description": desc, + "chat_id": chat, + "name": name, + "created_by": st.session_state.username + }) + st.session_state.mode = "idle" + st.rerun() + + # ------------------------- + # EDIT EVENT + # ------------------------- + elif st.session_state.mode == "edit": + ev = st.session_state.selected_event + st.info(f"Editando: {ev['title']}") + + with st.form("edit_event"): + d = st.date_input("Data", ev["event_date"]) + s = st.time_input("Início", timedelta_to_time(ev["start_time"])) + e = st.time_input("Fim", timedelta_to_time(ev["end_time"])) + t = st.text_input("Título", ev["title"]) + desc = st.text_area("Descrição", ev["description"]) + chat = st.text_input("Chat ID", ev["chat_id"]) + name = st.text_input("Nome", ev["name"]) + + if st.form_submit_button("Atualizar"): + update_event(ev["id"], { + "event_date": d, + "start_time": s, + "end_time": e, + "title": t, + "description": desc, + "chat_id": chat, + "name": name + }) + st.session_state.mode = "idle" + st.rerun() + + if st.form_submit_button("Excluir"): + delete_event(ev["id"]) + st.session_state.mode = "idle" + st.rerun() + + else: + st.write("⬅️ Clique em um dia ou evento no calendário") + diff --git a/README.md b/README.md index e69de29..15a9f5a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,88 @@ +# Sistema de Agendamento Multi-Canal + +Este projeto é um sistema completo de agendamento que permite aos clientes marcarem horários de forma automatizada via WhatsApp (integrações Twilio e UltraMsg) e Telegram. O sistema inclui também um painel administrativo web para gestão da agenda e um serviço de lembretes automáticos. + +## 🚀 Funcionalidades + +* **Agendamento via Chatbot:** Fluxo automatizado onde o cliente escolhe o período (Manhã/Tarde/Noite) e o horário disponível. +* **Múltiplos Canais de Atendimento:** + * **WhatsApp:** Suporte via API oficial (Twilio) e não-oficial (UltraMsg). + * **Telegram:** Bot nativo para agendamentos. +* **Painel Administrativo (Dashboard):** Interface visual desenvolvida em Streamlit para que o prestador de serviço possa: + * Visualizar agendamentos em formato de calendário. + * Criar, editar e excluir eventos manualmente. +* **Lembretes Automáticos (`Lembrator`):** Serviço em segundo plano que notifica os clientes sobre seus horários com antecedência de 24h, 12h e 3h. +* **Banco de Dados:** Persistência de dados utilizando MySQL (com suporte legado/local para SQLite). + +## 📂 Estrutura de Arquivos + +### Principais +* **`Main_Receiver-Twilio01.py`**: Servidor Flask que atua como Webhook para o Twilio (WhatsApp). +* **`Main_Receiver-Whatsapp.py`**: Servidor Flask para integração com a API UltraMsg (WhatsApp). +* **`Main_Receiver_Telegram.py`**: Bot do Telegram que utiliza a biblioteca `python-telegram-bot`. +* **`Painel_Prestador_Mysql.py`**: Aplicação Streamlit para gestão da agenda pelo prestador. +* **`Lembrator.py`**: Script de automação para envio de lembretes. + +### Banco de Dados +* **`BD_manager_Mysql.py`**: Gerenciador de conexão e queries para MySQL (usado em produção). +* **`BD_manager.py`**: Versão SQLite (uso local/teste). + +## 🛠️ Instalação e Configuração + +1. **Instale as dependências:** + ```bash + pip install -r requirements.txt + ``` + +2. **Configuração de Ambiente:** + Crie um arquivo `.env` na raiz do projeto para armazenar suas credenciais sensíveis: + ```env + # Twilio + TWILIO_ACCOUNT_SID=seu_sid + TWILIO_AUTH_TOKEN=seu_token + TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886 + + # Banco de Dados (se não usar st.secrets) + DB_HOST=seu_host + DB_USER=seu_user + DB_PASSWORD=sua_senha + DB_NAME=agenda + DB_PORT=porta + ``` + *Nota: O painel Streamlit utiliza `st.secrets` para configuração do banco.* + +3. **Banco de Dados:** + Certifique-se de que as tabelas `events`, `acompanhamento` e `agenda` estejam criadas no seu banco de dados MySQL conforme esperado pelos scripts `BD_manager_Mysql.py`. + +## ▶️ Como Executar + +### 1. Iniciar os Chatbots +Para ativar o atendimento automático, execute o script correspondente à plataforma desejada: + +* **WhatsApp (Twilio):** + ```bash + python Main_Receiver-Twilio01.py + ``` +* **Telegram:** + ```bash + python Main_Receiver_Telegram.py + ``` + +### 2. Painel Administrativo +Para abrir a interface de gestão: +```bash +streamlit run Painel_Prestador_Mysql.py +``` + +### 3. Serviço de Lembretes +Para iniciar o monitoramento e envio de avisos: +```bash +python Lembrator.py +``` + +## 📦 Dependências Principais +* `Flask`: Servidor web para os webhooks. +* `mysql-connector-python`: Driver de conexão com o MySQL. +* `streamlit` & `streamlit-calendar`: Interface do painel administrativo. +* `twilio`: SDK para envio de mensagens via Twilio. +* `python-telegram-bot`: SDK para o bot do Telegram. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2e72961561dd25c5896e555a2fabfbb31298421c GIT binary patch literal 1648 zcmZvd-EPxR5QO&{iAQl&J58Y%Tq7YNA@KkxO;VDQ+HT?~r4J9xx8oB>fU5N8?C$LB z?3~lzzZS8JAr`TXeSFZ@ZCu55e%{6#owxB*u`XjbI>YsK!)@ukq0u~Bpoey42Xzi36nR+M7aSfD~@!+IeVS;f9VW9easde_HmM?_@THJ2A4TU z?HL?1^`_0SR~;@{1Uql_-8sH1Y>SLmiZ3qEnZWFORqfJEr>rb@sPOB>uFF~OrNcw! zfIUmU*x@i^3Nnq-WE71&B^j$!#eTL^iq4k`RmpTv?lCvCJJ0Ofd8K(eO)^{{7Z+DL zU2B%LRPf!XbOT^-O}M%9+?hMRj_2H2)NV3{quDe+TWLF~>YcwkPesPFc&?Mrw7Z#* zN~gW}t`xTq;9-%r~Ob^q5%DQ0eC4qBM9@<-tPD}3yu29nzfu+%_yzWM3y8Q5uYOaf7xkbY yI!yP`@A-5G2ECN<<^O@VS+-^$A2kU+-fH8sF@u+*`23W$yy>v=E%6;GQ~v=5`R&&L literal 0 HcmV?d00001