feat(ui): add terminal formatting and colored output

- Implement ANSI color codes and formatted display functions
- Add banner and configuration panel for startup
- Introduce terminal width configuration option
- Update default timing intervals and confidence threshold
- Add screenshot reference file
This commit is contained in:
marco
2026-09-07 12:33:42 -03:00
parent 995adb4757
commit 77a735743a
5 changed files with 422 additions and 75 deletions
+194 -71
View File
@@ -9,136 +9,231 @@ import os
import sys
import time
import random
import re
import cv2
import numpy as np
import pyautogui
from datetime import datetime
from dotenv import load_dotenv
# Carrega variáveis do .env
load_dotenv()
LARGURA_TERMINAL = int(os.getenv("LARGURA_TERMINAL", "60"))
INTERVALO_MINIMO_CLIQUE = int(os.getenv("INTERVALO_MINIMO_CLIQUE", "10"))
INTERVALO_MAXIMO_CLIQUE = int(os.getenv("INTERVALO_MAXIMO_CLIQUE", "30"))
INTERVALO_PRIMEIRO_CLIQUE = int(os.getenv("INTERVALO_PRIMEIRO_CLIQUE", "10"))
MAXIMO_CLIQUES_POR_SESSAO = int(os.getenv("MAXIMO_CLIQUES_POR_SESSAO", "20"))
INTERVALO_ENTRE_SESSOES = int(os.getenv("INTERVALO_ENTRE_SESSOES", "60"))
MAXIMO_FALHAS = int(os.getenv("MAXIMO_FALHAS", "5"))
HORA_INICIO = os.getenv("HORA_INICIO", "09:00")
HORA_FIM = os.getenv("HORA_FIM", "18:00")
CONFIANCA = float(os.getenv("CONFIANCA", "0.8"))
INTERVALO_ENTRE_SESSOES = int(os.getenv("INTERVALO_ENTRE_SESSOES", "60"))
MAXIMO_FALHAS = int(os.getenv("MAXIMO_FALHAS", "5"))
HORA_INICIO = os.getenv("HORA_INICIO", "09:00")
HORA_FIM = os.getenv("HORA_FIM", "18:00")
CONFIANCA = float(os.getenv("CONFIANCA", "0.8"))
IMAGEM_BOTAO = os.path.join(os.path.dirname(os.path.abspath(__file__)), "botaoparaclicar.png")
LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "log.txt")
LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "log.txt")
pyautogui.FAILSAFE = True
RST = "\033[0m"
BOLD = "\033[1m"
def gerar_intervalo_aleatorio(intervalo_min: int, intervalo_max: int) -> int:
"""Gera um valor aleatório entre intervalo_min e intervalo_max (inclusive)."""
return random.randint(intervalo_min, intervalo_max)
C_TITLE = "\033[96m"
C_GREEN = "\033[92m"
C_YELLOW = "\033[93m"
C_RED = "\033[91m"
C_MAG = "\033[95m"
C_WHITE = "\033[97m"
C_GRAY = "\033[90m"
C_CYAN = "\033[36m"
_RE_ANSI = re.compile(r"\x1b\[[0-9;]*m")
def vis_largura(texto: str) -> int:
sem_ansi = _RE_ANSI.sub("", texto)
emojis = re.findall(
r"[\U0001F300-\U0001FAFF"
r"\U00002702-\U000027B0"
r"\U0000FE0F"
r"\U0001F1E0-\U0001F1FF"
r"\U00002600-\U000026FF"
r"\U00002700-\U000027BF"
r"]+",
sem_ansi,
flags=re.UNICODE,
)
ajuste_emoji = sum((2 - len(e)) for e in emojis)
return len(sem_ansi) + ajuste_emoji
def terminal_width() -> int:
return LARGURA_TERMINAL
def largura() -> int:
return terminal_width()
def center(text: str, pre: str = "", pos: str = "") -> str:
w = largura() - vis_largura(pre) - vis_largura(pos)
return pre + text.center(w) + pos
def banner() -> None:
print()
print(center(" 🤖 AUTO-CLICADOR 🤖 ", C_TITLE + BOLD))
print(center("Sistema de Cliques Automáticos", C_TITLE))
print()
def painel_config() -> None:
itens = [
(C_GRAY, "Largura do terminal", f"{LARGURA_TERMINAL} colunas"),
(C_GREEN, "Intervalo mínimo entre cliques", f"{INTERVALO_MINIMO_CLIQUE}s"),
(C_GREEN, "Intervalo máximo entre cliques", f"{INTERVALO_MAXIMO_CLIQUE}s"),
(C_CYAN, "Espera antes do 1º clique", f"{INTERVALO_PRIMEIRO_CLIQUE}s"),
(C_YELLOW, "Máximo de cliques por sessão", str(MAXIMO_CLIQUES_POR_SESSAO)),
(C_YELLOW, "Intervalo entre sessões", f"{INTERVALO_ENTRE_SESSOES}s"),
(C_MAG, "Máximo de falhas", str(MAXIMO_FALHAS)),
(C_WHITE, "Horário de funcionamento", f"{HORA_INICIO} às {HORA_FIM}"),
(C_GRAY, "Confiança mínima", str(CONFIANCA)),
]
print(center(" ⚙ CONFIGURAÇÃO", C_TITLE + BOLD))
print()
labels_sem_cor = [_RE_ANSI.sub("", f" {label}") for _, label, _ in itens]
max_label_vis = max(vis_largura(l) for l in labels_sem_cor)
largura_label = max(max_label_vis, 1)
for cor, label, valor in itens:
label_raw = f" {label}"
label_vis = vis_largura(label_raw)
padding_label = max(1, largura_label - label_vis + 2)
print(
cor
+ label_raw
+ RST
+ " " * padding_label
+ C_WHITE
+ str(valor)
+ RST
)
print()
def cabecalho_sessao(num: int) -> None:
print()
print(C_MAG + BOLD + f" 🔄 SESSÃO {num} 🔄" + RST)
print()
def rodape_clique() -> None:
w = largura()
inner_w = w - 4
barra_preenchida = max(1, int(inner_w * 0.4))
barra_vazia = inner_w - barra_preenchida
barra = C_GREEN + "█" * barra_preenchida + C_GRAY + "░" * barra_vazia + RST
print(" " + barra)
def log(mensagem: str, cor: str = C_WHITE) -> None:
agora = datetime.now()
ts = agora.strftime("%H:%M:%S")
ts_raw = f"[{ts}] "
ts_color = C_GRAY
linha_terminal = (
" "
+ ts_color + ts_raw + RST
+ cor + mensagem + RST
)
print(linha_terminal)
def log(mensagem: str) -> None:
"""Registra mensagem com timestamp."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
linha = f"[{timestamp}] {mensagem}"
print(linha)
try:
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(linha + "\n")
ts_full = agora.strftime("%Y-%m-%d %H:%M:%S")
f.write(f"[{ts_full}] {_RE_ANSI.sub('', mensagem)}\n")
except Exception:
pass
def dentro_horario(hora_inicio: str, hora_fim: str) -> bool:
"""Verifica se o horário atual está dentro do intervalo configurado."""
agora = datetime.now().time()
hi = datetime.strptime(hora_inicio, "%H:%M").time()
hf = datetime.strptime(hora_fim, "%H:%M").time()
return hi <= agora <= hf
def encontrar_botao(imagem_path: str, confianca: float = 0.8):
"""
Procura a imagem do botão na tela usando template matching.
Retorna as coordenadas (x, y) do centro do botão ou None.
"""
if not os.path.isfile(imagem_path):
log(f"ERRO: Imagem '{imagem_path}' não encontrada.")
log(f"ERRO: Imagem '{imagem_path}' não encontrada.", C_RED)
return None
# Captura screenshot da tela
screenshot = pyautogui.screenshot()
screenshot_np = np.array(screenshot)
screenshot_cv = cv2.cvtColor(screenshot_np, cv2.COLOR_RGB2BGR)
# Carrega template do botão
template = cv2.imread(imagem_path, cv2.IMREAD_UNCHANGED)
if template is None:
log(f"ERRO: Não foi possível carregar a imagem '{imagem_path}'.")
log(f"ERRO: Não foi possível carregar '{imagem_path}'.", C_RED)
return None
# Converte template para BGR se tiver alpha channel
if template.shape[2] == 4:
template_bgr = cv2.cvtColor(template, cv2.COLOR_BGRA2BGR)
# Cria máscara para o canal alpha
alpha = template[:, :, 3]
mascara = alpha.astype(np.uint8)
else:
template_bgr = template
mascara = None
# Template matching
resultado = cv2.matchTemplate(screenshot_cv, template_bgr, cv2.TM_CCOEFF_NORMED, mask=mascara)
_, max_val, _, max_loc = cv2.minMaxLoc(resultado)
if max_val >= confianca:
h, w = template_bgr.shape[:2]
centro_x = max_loc[0] + w // 2
h, w2 = template_bgr.shape[:2]
centro_x = max_loc[0] + w2 // 2
centro_y = max_loc[1] + h // 2
log(f"Botão encontrado! Confiança: {max_val:.2f} | Posição: ({centro_x}, {centro_y})")
log(
f"Botão encontrado! Confiança: {max_val:.2f} | Pos: ({centro_x}, {centro_y})",
C_GREEN,
)
return (centro_x, centro_y)
else:
log(f"Botão NÃO encontrado. Melhor confiança: {max_val:.2f} (necessário >= {confianca})")
log(
f"Botão NÃO encontrado. Confiança: {max_val:.2f} (mín: {confianca})",
C_RED,
)
return None
def gerar_intervalo_aleatorio(intervalo_min: int, intervalo_max: int) -> int:
return random.randint(intervalo_min, intervalo_max)
def main() -> None:
log("=" * 60)
log("Sistema de Cliques Automáticos iniciado")
log(f"Intervalo mínimo entre cliques: {INTERVALO_MINIMO_CLIQUE}s")
log(f"Intervalo máximo entre cliques: {INTERVALO_MAXIMO_CLIQUE}s")
log(f"Aguardar antes do 1º clique: {INTERVALO_PRIMEIRO_CLIQUE}s")
log(f"Máximo de cliques por sessão: {MAXIMO_CLIQUES_POR_SESSAO}")
log(f"Intervalo entre sessões: {INTERVALO_ENTRE_SESSOES}s")
log(f"Máximo de falhas antes de encerrar: {MAXIMO_FALHAS}")
log(f"Horário de funcionamento: {HORA_INICIO} às {HORA_FIM}")
log(f"Confiança mínima: {CONFIANCA}")
log(f"Imagem do botão: {IMAGEM_BOTAO}")
log("=" * 60)
log("Pressione CTRL+C para parar.")
banner()
painel_config()
primeiro_clique = True
falhas_consecutivas = 0
total_cliques = 0
cliques_na_sessao = 0
numero_sessao = 1
print(center(" 🚀 Iniciando sistema... ", C_TITLE + BOLD))
print()
log("Pressione CTRL+C para parar.", C_YELLOW)
print()
primeiro_clique = True
falhas_consecutivas = 0
total_cliques = 0
cliques_na_sessao = 0
numero_sessao = 1
try:
while True:
agora = datetime.now()
if not dentro_horario(HORA_INICIO, HORA_FIM):
log(f"[Sessão {numero_sessao}] Fora do horário permitido ({agora.strftime('%H:%M:%S')}).")
log(f"Pausando até {HORA_INICIO}...")
log(
f"[Sessão {numero_sessao}] Fora do horário permitido ({agora.strftime('%H:%M:%S')}).",
C_YELLOW,
)
log(f"Pausando até {HORA_INICIO}...", C_GRAY)
falhas_consecutivas = 0
cliques_na_sessao = 0
cliques_na_sessao = 0
time.sleep(30)
continue
if primeiro_clique:
log(f"--- Sessão {numero_sessao} iniciada ---")
log(f"Aguardando {INTERVALO_PRIMEIRO_CLIQUE}s antes do primeiro clique...")
cabecalho_sessao(numero_sessao)
log(
f"Aguardando {INTERVALO_PRIMEIRO_CLIQUE}s antes do primeiro clique...",
C_CYAN,
)
time.sleep(INTERVALO_PRIMEIRO_CLIQUE)
primeiro_clique = False
@@ -146,36 +241,64 @@ def main() -> None:
if posicao:
x, y = posicao
log(f"[Sessão {numero_sessao}] Clicando em ({x}, {y})...")
log(
f"[Sessão {numero_sessao}] Clicando em ({x}, {y})...",
C_GREEN,
)
pyautogui.click(x, y)
total_cliques += 1
total_cliques += 1
cliques_na_sessao += 1
log(f"Clique executado com sucesso. Total geral: {total_cliques} | Sessão {numero_sessao}: {cliques_na_sessao}/{MAXIMO_CLIQUES_POR_SESSAO}")
log(
f"Clique #{cliques_na_sessao}/{MAXIMO_CLIQUES_POR_SESSAO} | Total geral: {total_cliques}",
C_GREEN + BOLD,
)
falhas_consecutivas = 0
rodape_clique()
else:
falhas_consecutivas += 1
log(f"[Sessão {numero_sessao}] Botão não encontrado. Falhas consecutivas: {falhas_consecutivas}/{MAXIMO_FALHAS} | Total de cliques: {total_cliques}")
log(
f"[Sessão {numero_sessao}] Botão não encontrado. "
f"Falhas: {falhas_consecutivas}/{MAXIMO_FALHAS} | Total: {total_cliques}",
C_RED,
)
if falhas_consecutivas >= MAXIMO_FALHAS:
log(f"Máximo de falhas ({MAXIMO_FALHAS}) atingido. Encerrando sistema.")
log(f"Total de cliques realizados com sucesso: {total_cliques}")
print()
log(
f"⚠ Máximo de falhas ({MAXIMO_FALHAS}) atingido. Encerrando.",
C_RED + BOLD,
)
log(f"Total de cliques realizados: {total_cliques}", C_YELLOW)
sys.exit(1)
# Verifica se atingiu o máximo de cliques da sessão
if cliques_na_sessao >= MAXIMO_CLIQUES_POR_SESSAO:
log(f"--- Sessão {numero_sessao} finalizada: {cliques_na_sessao} cliques ---")
numero_sessao += 1
cliques_na_sessao = 0
log(f"Aguardando {INTERVALO_ENTRE_SESSOES}s até a próxima sessão (Sessão {numero_sessao})...")
log(
f"--- Sessão {numero_sessao} finalizada: {cliques_na_sessao} cliques ---",
C_MAG + BOLD,
)
numero_sessao += 1
cliques_na_sessao = 0
log(
f"Aguardando {INTERVALO_ENTRE_SESSOES}s até a Sessão {numero_sessao}...",
C_YELLOW,
)
time.sleep(INTERVALO_ENTRE_SESSOES)
primeiro_clique = True
continue
intervalo = gerar_intervalo_aleatorio(INTERVALO_MINIMO_CLIQUE, INTERVALO_MAXIMO_CLIQUE)
log(f"[Sessão {numero_sessao}] Aguardando {intervalo}s até o próximo ciclo...")
intervalo = gerar_intervalo_aleatorio(
INTERVALO_MINIMO_CLIQUE, INTERVALO_MAXIMO_CLIQUE
)
log(
f"[Sessão {numero_sessao}] Aguardando {intervalo}s até o próximo ciclo...",
C_CYAN,
)
time.sleep(intervalo)
except KeyboardInterrupt:
log(f"\nSistema interrompido pelo usuário (CTRL+C). Total de cliques: {total_cliques}")
print()
print(C_RED + BOLD + center(" 🛑 INTERROMPIDO PELO USUÁRIO 🛑 ") + RST)
print()
log(f"Total de cliques realizados: {total_cliques}", C_YELLOW)
if __name__ == "__main__":