Files
marco 77a735743a 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
2026-09-07 12:33:42 -03:00

306 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Sistema de cliques automáticos baseado em reconhecimento de imagem.
Clique no botão especificado por 'botaoparaclicar.png' a cada X segundos,
apenas dentro do horário configurado no .env.
"""
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
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"))
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")
pyautogui.FAILSAFE = True
RST = "\033[0m"
BOLD = "\033[1m"
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)
try:
with open(LOG_FILE, "a", encoding="utf-8") as f:
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:
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):
if not os.path.isfile(imagem_path):
log(f"ERRO: Imagem '{imagem_path}' não encontrada.", C_RED)
return None
screenshot = pyautogui.screenshot()
screenshot_np = np.array(screenshot)
screenshot_cv = cv2.cvtColor(screenshot_np, cv2.COLOR_RGB2BGR)
template = cv2.imread(imagem_path, cv2.IMREAD_UNCHANGED)
if template is None:
log(f"ERRO: Não foi possível carregar '{imagem_path}'.", C_RED)
return None
if template.shape[2] == 4:
template_bgr = cv2.cvtColor(template, cv2.COLOR_BGRA2BGR)
alpha = template[:, :, 3]
mascara = alpha.astype(np.uint8)
else:
template_bgr = template
mascara = None
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, 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} | Pos: ({centro_x}, {centro_y})",
C_GREEN,
)
return (centro_x, centro_y)
else:
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:
banner()
painel_config()
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')}).",
C_YELLOW,
)
log(f"Pausando até {HORA_INICIO}...", C_GRAY)
falhas_consecutivas = 0
cliques_na_sessao = 0
time.sleep(30)
continue
if 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
posicao = encontrar_botao(IMAGEM_BOTAO, CONFIANCA)
if posicao:
x, y = posicao
log(
f"[Sessão {numero_sessao}] Clicando em ({x}, {y})...",
C_GREEN,
)
pyautogui.click(x, y)
total_cliques += 1
cliques_na_sessao += 1
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. "
f"Falhas: {falhas_consecutivas}/{MAXIMO_FALHAS} | Total: {total_cliques}",
C_RED,
)
if falhas_consecutivas >= MAXIMO_FALHAS:
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)
if cliques_na_sessao >= MAXIMO_CLIQUES_POR_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...",
C_CYAN,
)
time.sleep(intervalo)
except KeyboardInterrupt:
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__":
main()