Files
AutoTigrinho/main.py
T
marco fa2be1a13c feat(config): add support for multiple session periods
Replace single time range configuration with support for up to 10
configurable session periods. Rename INTERVALO_CLIQUE_ESPERA to
INTERVALO_CLIQUE_GLOBAL for clarity. Update dentro_horario function
to accept list of period tuples instead of individual start/end times.

BREAKING CHANGE: HORA_INICIO and HORA_FIM environment variables are
replaced by PERIODO_{n}_INICIO and PERIODO_{n}_FIM variables (n=1-10).
INTERVALO_CLIQUE_ESPERA is renamed to INTERVALO_CLIQUE_GLOBAL.
2026-09-09 13:28:20 -03:00

361 lines
12 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"))
INTERVALO_CLIQUE_GLOBAL = int(os.getenv("INTERVALO_CLIQUE_GLOBAL", "900"))
MAXIMO_FALHAS = int(os.getenv("MAXIMO_FALHAS", "5"))
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")
PERIODOS_SESSAO = []
def _carregar_periodos():
periodos = []
for i in range(1, 11):
ini = os.getenv(f"PERIODO_{i}_INICIO", "").strip()
fim = os.getenv(f"PERIODO_{i}_FIM", "").strip()
if ini and fim:
periodos.append((ini, fim))
return periodos
PERIODOS_SESSAO = _carregar_periodos()
def dentro_horario(periodos: list) -> bool:
agora = datetime.now().time()
for ini, fim in periodos:
hi = datetime.strptime(ini, "%H:%M").time()
hf = datetime.strptime(fim, "%H:%M").time()
if hi <= agora <= hf:
return True
return False
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_CYAN, "Clique global de espera", f"{INTERVALO_CLIQUE_GLOBAL}s"),
(C_MAG, "Máximo de falhas", str(MAXIMO_FALHAS)),
(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()
if PERIODOS_SESSAO:
print(C_WHITE + BOLD + center(" 📅 Períodos de sessão", C_TITLE) + RST)
print()
for idx, (ini, fim) in enumerate(PERIODOS_SESSAO, 1):
print(C_WHITE + f" {idx}. " + C_CYAN + f"{ini} → {fim}" + RST)
else:
print(C_RED + center(" ⚠ Nenhum período configurado!", C_RED) + 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 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(PERIODOS_SESSAO):
log(
f"[Sessão {numero_sessao}] Fora de todos os períodos configurados ({agora.strftime('%H:%M:%S')}).",
C_YELLOW,
)
log(f"[Espera] Dormindo {INTERVALO_CLIQUE_GLOBAL}s até o próximo clique global...", C_GRAY)
time.sleep(INTERVALO_CLIQUE_GLOBAL)
pos_global = encontrar_botao(IMAGEM_BOTAO, CONFIANCA)
if pos_global:
gx, gy = pos_global
log(
f"[Espera] Clicando globalmente em ({gx}, {gy})...",
C_GREEN,
)
pyautogui.click(gx, gy)
else:
log("[Espera] Botão não encontrado no clique global.", C_RED)
falhas_consecutivas = 0
cliques_na_sessao = 0
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} | Total: {total_cliques}",
C_RED,
)
log(
f"[Sessão {numero_sessao}] Re-tentando em {INTERVALO_MINIMO_CLIQUE}s...",
C_YELLOW,
)
time.sleep(INTERVALO_MINIMO_CLIQUE)
continue
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,
)
tempo_restante = INTERVALO_ENTRE_SESSOES
while tempo_restante > 0:
tempo_ate_clique = min(tempo_restante, INTERVALO_CLIQUE_GLOBAL)
log(
f"[Espera] Dormindo {tempo_ate_clique}s (restam {tempo_restante}s)...",
C_GRAY,
)
time.sleep(tempo_ate_clique)
tempo_restante -= tempo_ate_clique
if tempo_restante <= 0:
break
log(
f"[Espera] Executando clique de keep-alive (restam {tempo_restante}s)...",
C_CYAN,
)
pos_espera = encontrar_botao(IMAGEM_BOTAO, CONFIANCA)
if pos_espera:
px, py = pos_espera
log(
f"[Espera] Clicando em ({px}, {py})...",
C_GREEN,
)
pyautogui.click(px, py)
else:
log("[Espera] Botão não encontrado no clique de keep-alive.", C_RED)
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()