Subida da versão estável
This commit is contained in:
BIN
.BD_manager_Mysql.py.swn
Normal file
BIN
.BD_manager_Mysql.py.swn
Normal file
Binary file not shown.
BIN
.BD_manager_Mysql.py.swo
Normal file
BIN
.BD_manager_Mysql.py.swo
Normal file
Binary file not shown.
207
.gitignore
vendored
Normal file
207
.gitignore
vendored
Normal file
@@ -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__/
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -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
|
||||||
18
.idea/V1.iml
generated
Normal file
18
.idea/V1.iml
generated
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="Flask">
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
16
.idea/checkstyle-idea.xml
generated
Normal file
16
.idea/checkstyle-idea.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CheckStyle-IDEA" serialisationVersion="2">
|
||||||
|
<checkstyleVersion>10.24.0</checkstyleVersion>
|
||||||
|
<scanScope>JavaOnly</scanScope>
|
||||||
|
<copyLibs>true</copyLibs>
|
||||||
|
<option name="thirdPartyClasspath" />
|
||||||
|
<option name="activeLocationIds" />
|
||||||
|
<option name="locations">
|
||||||
|
<list>
|
||||||
|
<ConfigurationLocation id="bundled-sun-checks" type="BUNDLED" scope="All" description="Sun Checks">(bundled)</ConfigurationLocation>
|
||||||
|
<ConfigurationLocation id="bundled-google-checks" type="BUNDLED" scope="All" description="Google Checks">(bundled)</ConfigurationLocation>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
7
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/V1.iml" filepath="$PROJECT_DIR$/.idea/V1.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
175
BD_manager.py
Normal file
175
BD_manager.py
Normal file
@@ -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'])'''
|
||||||
229
BD_manager_Mysql.py
Normal file
229
BD_manager_Mysql.py
Normal file
@@ -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)
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
||||||
117
Lembrator.py
Normal file
117
Lembrator.py
Normal file
@@ -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)
|
||||||
35
Limpar_Sessoes.py
Normal file
35
Limpar_Sessoes.py
Normal file
@@ -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()
|
||||||
260
Main_Receiver-Twilio01.py
Normal file
260
Main_Receiver-Twilio01.py
Normal file
@@ -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)
|
||||||
241
Main_Receiver-Whatsapp.py
Normal file
241
Main_Receiver-Whatsapp.py
Normal file
@@ -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)
|
||||||
232
Main_Receiver_Telegram.py
Normal file
232
Main_Receiver_Telegram.py
Normal file
@@ -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()
|
||||||
240
Painel_Prestador_Mysql.py
Normal file
240
Painel_Prestador_Mysql.py
Normal file
@@ -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")
|
||||||
|
|
||||||
88
README.md
88
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.
|
||||||
|
|||||||
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
Reference in New Issue
Block a user