Repository navigation
Alertas
English · Português (Brasil)
Um alerta é disparado quando a comparação confirma uma mudança (changed: true) e a
severidade da mudança atinge o severity_min do canal, respeitando o cooldown_s de cada um.
Tipo (type) |
O que faz | Campos específicos |
|---|---|---|
sound |
toca um som local |
file (WAV/MP3/M4A/AAC/OGG/FLAC…; default alert.mp3, empacotado) |
popup |
notificação local (plyer) |
— |
telegram |
envia mensagem (e a imagem do ROI) via bot |
bot_token_env, chat_id, attach_roi
|
log |
grava uma linha JSON em logs/alerts.jsonl
|
path (opcional; vazio = default) |
webhook |
POST/PUT/PATCH JSON para uma URL de webhook (Teams Workflows, Slack, Discord, Mattermost) |
options: (url/url_env, method, headers, payload/payload_raw, timeout_s, verify_tls) |
http_post |
POST JSON para host/IP + porta (ou URL completa) |
options: (url/url_env, scheme, host, port, path, method, headers, payload/payload_raw, verify_tls) |
syslog |
mensagem syslog informacional (host/porta, udp/tcp) — sem imagem |
options: (host, port, protocol, facility, app_name, payload_raw, severity_map, timeout_s) |
ntfy |
publica texto ou o PNG da ROI no tópico ntfy |
options: (server, topic, token_env, title/message, priority_map, tags, attach_roi, timeout_s) |
smtp |
envia e-mail (assunto/corpo, com o PNG da ROI anexado) |
options: (host, port, security, from_addr, to, subject/message, username_env, password_env, attach_roi, timeout_s) |
mqtt |
publica JSON num broker MQTT (extra mqtt) — sem imagem |
options: (host, topic, port, qos, retain, client_id, username_env, password_env, tls, payload/payload_raw, timeout_s) |
Os quatro primeiros mantêm campos planos; os demais (webhook, http_post, syslog, ntfy,
smtp e mqtt) usam um bloco aninhado options:. Todos
aceitam enabled, severity_min, cooldown_s e o id opcional (default type; type#n quando
repetido no mesmo perfil). O id é a chave de cooldown, então dois webhooks não compartilham mais o
cooldown. Exemplo de perfil:
profiles:
default:
alerts:
- { type: "sound", enabled: true, severity_min: 1, cooldown_s: 30, file: "alert.mp3" }
- { type: "popup", enabled: true, severity_min: 1, cooldown_s: 30 }
- { type: "telegram", enabled: true, severity_min: 2, cooldown_s: 60,
bot_token_env: "TELEGRAM_BOT_TOKEN", chat_id: "123456789", attach_roi: true }
- { type: "log", enabled: true, severity_min: 1, cooldown_s: 0 } # opcional
- type: webhook # Teams Workflows / Slack / Discord / Mattermost…
id: teams
severity_min: 2
cooldown_s: 60
options:
url_env: TEAMS_WEBHOOK # segredo fora do YAML
payload: { text: "Mudança em ${target}: ${strategy} sev=${severity}" }Sem YAML (ou YAML v1 sem target correspondente), o app usa os alertas padrão: som + popup +
log (Telegram exige chat_id, então não entra no default).
Cada snippet abaixo é um item da lista alerts: de um perfil (veja o exemplo de perfil acima);
cole em profiles.<nome>.alerts e repita/combine conforme necessário.
- Toda a reprodução passa pela fronteira
platform/audio.py;alerts/não conhecesys.platform, Qt nem miniaudio. -
GUI:
QMediaPlayer(QtMultimedia) criado no thread da GUI — WAV/MP3/M4A/AAC/FLAC/WMA via Media Foundation (Windows), GStreamer (Linux, depende dos plugins instalados) ou AVFoundation (macOS). Tocar de novo interrompe o som anterior. -
CLI/
run(sem aplicação Qt): miniaudio (dependência core) toca WAV/MP3/OGG/FLAC numa thread daemon (nunca bloqueia o loop). Sem AAC/M4A — cai para um player externo se existir, senãobeep. -
Fallback legado:
winsound(stdlib; só WAV) no Windows; no Linux/macOS, player externo em ordem de preferência —paplay,aplay -q,ffplay -nodisp -autoexit -loglevel quiet; no macOS,afplay. -
filepode ser absoluto ou relativo: relativo procura emapp-data/sounds/primeiro, depois nos sons empacotados (screen_watch/assets/sounds, onde mora oalert.mp3default) e por fim no CWD. Arquivo ausente — ou formato sem decoder — cai nobeep()com aviso no log (nunca silêncio nem exceção). - O extra
simpleaudio(pip install -e ".[sound]") segue opcional e não entra nos instaladores (sem wheel confiável para Python 3.13); ele só é tentado para.wav. -
Seletor da GUI (v0.8.0): a linha Som do alerta pré-visualiza qualquer arquivo com
Reproduzir e, depois de Escolher…, pede confirmação e grava o
file:no alertatype: "sound"do perfil ativo noconfig.yaml(escrita atômica, backupconfig.yaml.bak; comentários não são preservados). O diálogo mantém Copiar caminho e abrir YAML / Só abrir o YAML / Fechar como ações secundárias. Config v1 é recusada com mensagem para rodarmigrate-config; quando a seleção atual temoverrides.alerts(que substituem os alertas do perfil), o diálogo avisa que o som não valerá para ela. - Matriz completa de formatos por contexto: notas da v0.6.0.
- type: sound
severity_min: 1
cooldown_s: 30
file: "alert.mp3" # default (empacotado); relativo → app-data/sounds → empacotados → CWD-
plyer.notification(depende do backend nativo de cada SO).
- type: popup
severity_min: 1
cooldown_s: 30- Token nunca no YAML: lido da variável de ambiente indicada em
bot_token_env(TELEGRAM_BOT_TOKENpor default). -
attach_roi: true(default) envia o screenshot do ROI junto (sendPhoto), o que é essencial para validar falsos positivos; comfalse, envia só texto (sendMessage). - Timeout curto (5 s) para não travar o loop.
- Passo a passo completo: Configuração do Telegram — criar o bot, obter o chat id, definir o token, editar o YAML e testar.
- type: telegram
severity_min: 2
cooldown_s: 60
bot_token_env: TELEGRAM_BOT_TOKEN # default; o token fica no ambiente
chat_id: "123456789" # obrigatório
attach_roi: true # default; false = só texto- Uma linha JSON por disparo em
app-data/logs/alerts.jsonl(ou nopathconfigurado). - O registro não tem canal/alvo/caminho de evidência (
ts,strategy,changed,score,threshold,severity,window_handle,absolute_rect,sequence,detail). -
Histórico de alertas (v0.8.0): o botão Histórico… da GUI abre uma tabela sobre esse arquivo
com filtros (data de/até, severidade mínima, modo), tolerante a linhas inválidas. Abrir print
procura o
*_change.pngmais próximo em ±2 s do alerta (best effort; senão informa que não achou) e Abrir pasta de prints abre a pasta efetiva de capturas.
- type: log
severity_min: 1
cooldown_s: 0
path: "" # vazio = app-data/logs/alerts.jsonl- Sem imagem/ROI: o print anexado continua exclusivo do Telegram.
- URL: literal (
url) ou do ambiente (url_env: VAR). Strings deheaders/payloadaceitam${env:VAR}; a URL resolvida e os valores das variáveis nunca aparecem em erros/logs. -
methodéPOST(default),PUTouPATCH; redirects não são seguidos; oContent-Typedefault éapplication/json(sobrescrevível emheaders). - Sucesso = HTTP 2xx; não-2xx vira
alert.http_status(URL redigida) e falha de rede viraalert.http_unreachable. -
Modelo de payload:
payload:(mapping) substitui${campo}só em valores string;payload_raw:(string) envia um corpo que não é objeto. Usar ambos é erro de config. Placeholders:message,strategy,score,threshold,severity,target,timestamp,window_handle,changed,roieenv:VAR.$$escapa$;{/}ficam literais, então o corpo pode ser JSON. -
http_postusascheme(defaulthttp),host,port,pathou umaurlcompleta. -
verify_tls: true(default); comfalse(endpoints internos/autoassinados) um aviso é registrado a cada envio. - Teams: os Incoming Webhooks legados estão sendo descontinuados (prazo 31/03/2026, desligamento maio/2026) — use a URL do Workflows.
- type: webhook # Slack/Discord/Mattermost (campo text)
id: chat
severity_min: 2
cooldown_s: 60
options:
url_env: CHAT_WEBHOOK # ou url: "https://…"; o segredo fica fora do YAML
method: POST # POST (default) | PUT | PATCH
headers: { Authorization: "Bearer ${env:HOOK_TOKEN}" } # opcional
payload: { text: ":rotating_light: ${message} | target=${target} sev=${severity}" }
timeout_s: 5 # default
verify_tls: true # default; false registra aviso a cada envio
- type: webhook # Teams Workflows (Adaptive Card)
id: teams
severity_min: 2
cooldown_s: 60
options:
url_env: TEAMS_WEBHOOK
payload:
type: "message"
attachments:
- contentType: "application/vnd.microsoft.card.adaptive"
content:
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
type: "AdaptiveCard"
version: "1.4"
body:
- type: "TextBlock"
text: "Mudança em ${target}: ${strategy} sev=${severity}"
wrap: true
- type: http_post # host/IP + porta (API interna)
id: erp-api
severity_min: 3
cooldown_s: 120
options:
scheme: http # default
host: 10.0.0.20 # port é obrigatório junto com host
port: 8080
path: /alerta # opcional
headers: { Authorization: "Bearer ${env:ERP_TOKEN}" }
payload: { evento: "mudanca", alvo: "${target}", sev: "${severity}" }-
Informational por default: a severidade real vai no texto via
${severity}; useseverity_map({0: "debug", 3: "error"}) para definir o nível syslog por severidade (0..3). -
protocoléudp(default) outcp;portdefault514;facilitydefaultlocal0;app_namevira a tag do syslog (ident). -
UDP não confirma entrega (fire-and-forget) — prefira
tcpquando a entrega precisa ser confirmada.
- type: syslog
id: siem
severity_min: 1
cooldown_s: 0
options:
host: 10.0.0.9 # obrigatório
port: 514 # default
protocol: udp # udp (default) | tcp
facility: local0 # default
app_name: screen-diff-watcher # tag (ident) do syslog
payload_raw: "${timestamp} ${target} ${strategy} score=${score} sev=${severity}" # default "${message}"
severity_map: { 0: "debug", 3: "error" } # opcional; chaves 0..3
timeout_s: 5 # default- Publica em
server/topic:serverdefaulthttps://ntfy.sh;topicé obrigatório. -
token_envé opcional: vazio = tópico anônimo; com a variável configurada mas ausente no ambiente, o canal registra aviso e pula (como o Telegram). - Headers
TitleePriority(mapa severidade→prioridade 1..5, default 1→3, 2→4, 3→5; severidade 0 também usa 3) eTagsopcionais;title/messagesão templates (default${message}). -
attach_roi: true(defaultfalse) envia o PNG da ROI porPUT(Filename: roi.png, texto no headerMessage); sem anexo o corpo é o texto renderizado. -
timeout_sdefault 5; falhas viramalert.ntfy_status/alert.ntfy_unavailablecom a URL redigida.
- type: ntfy
id: celular
severity_min: 2
cooldown_s: 60
options: { server: "https://ntfy.sh", topic: "meu-topico", token_env: NTFY_TOKEN,
priority_map: { 1: 3, 2: 4, 3: 5 }, tags: ["monitor"],
attach_roi: true, timeout_s: 5 }-
hostefrom_addrsão obrigatórios;toexige uma lista não vazia. -
portdefault 587;securityéstarttls(default),sslounone. -
subject/messagesão templates (subject default[screen-diff-watcher] ${target} sev=${severity}). - Credenciais só por env:
username_env/password_env(defaultsSMTP_USERNAME/SMTP_PASSWORD); o login só acontece quando a variável de usuário existe, e a senha nunca aparece em erros/logs. -
attach_roi: true(default) anexa o PNG da ROI (roi.png);timeout_sdefault 10. - Erros saneados:
alert.smtp_unavailable,alert.smtp_auth_failedealert.smtp_send_failed.
- type: smtp
id: email
severity_min: 3
cooldown_s: 120
options: { host: "smtp.example.com", port: 587, security: starttls,
from_addr: "watch@example.com", to: ["oncall@example.com"],
subject: "[monitor] ${target} sev=${severity}", attach_roi: true }- Extra opcional:
python -m pip install -e ".[mqtt]"(paho-mqtt); não entra nos instaladores. Um canalmqttconfigurado sem o extra falha visível comalert.mqtt_missing_extra— nunca em silêncio. Ofeatures --jsoninforma se o extra está instalado. -
hostetopicsão obrigatórios;portdefault 1883 (8883 quandotls: true). -
qos0/1/2 (default 0),retain(default false) eclient_id(defaultscreen-diff-watcher). - Credenciais por env (
username_env/password_env, defaultsMQTT_USERNAME/MQTT_PASSWORD);tls: trueusa o TLS default do paho. - Payload: mapping
payload(defaulttext/target/severity/strategy/score/threshold/timestamp, serializado em JSON) ou stringpayload_raw, mutuamente exclusivos. -
timeout_sdefault 5; sem imagem. Erros:alert.mqtt_unavailable/alert.mqtt_publish_failed.
- type: mqtt
id: barramento
options: { host: "10.0.0.30", topic: "screen-watch/default", qos: 1, retain: false,
client_id: screen-diff-watcher, username_env: MQTT_USERNAME,
password_env: MQTT_PASSWORD }A severidade (0..3) vem do pipeline de comparação: quando a estratégia não define, o pipeline calcula
compute_severity(score, threshold) — razão score/threshold: >= 3.0 → 3; >= 2.0 → 2;
>= 1.0 → 1; senão 0. Use os severity_min para separar "aviso" de "crítico" (ex.: Telegram só em
severidade 2+).
- O
cooldown_sé por notificador: mesmo com a ROI mudando a cada tick, cada canal dispara no máximo uma vez por janela de cooldown. - Falha em um notificador não impede os demais (cada um tem seu próprio
try). A tentativa é registrada para dar backoff — um Telegram fora do ar não é martelado a cada tick. - Com
rearm: true(default), o baseline avança após um alerta efetivo (ou inútil) — uma mudança sustentada alarma uma vez. Em cooldown ou falha, o baseline é mantido: a mudança pendente alarma quando o cooldown expirar. - Re-arm manual: tray, botão Re-armar baseline da janela ou hotkey
rearm(<ctrl>+<alt>+r).
-
Soneca… (grupo Detecção e alertas, tray e hotkey) usa as durações de
ui.snooze_minutes(default 5/15/30/60 min); Silenciar/Reativar corta os alertas até reativar, e o Ciente reconhece uma escalação (GUI/tray; hotkey opcionalui.hotkeys.acknowledge). - O estado vive no
state.json(alerts_snooze_until,alerts_muted) e é herdado por umrunheadless posterior; soneca expirada é ignorada no start. - Enquanto o gate está ativo, o
dispatchdevolvesuppressed_manualantes de qualquer canal e o baseline é mantido: a mudança pendente alerta quando a soneca expirar ou o silêncio for desfeito. -
Escalação (
defaults.escalation/overrides.escalation;enableddefault false,severity_mindefault 2): com umfiredemseverity >= severity_min, o baseline não avança e o alerta repete na cadência docooldown_sde cada canal até o Ciente, que re-arma o baseline. - A evidência de mudança só é gravada em
fired/failed(e a cada disparo da escalação), em vez de um print por tick enquanto suprimido.
python -m screen_watch test-alert --selection painel # todos os canais (respeita enabled)
python -m screen_watch test-alert --selection painel --list # lista id/tipo/estado/destino
python -m screen_watch test-alert --selection painel --only siem # um destino (modo texto)O test-alert dispara um alerta sintético (severidade 3) com o ROI atual, para conferir cada canal
sem esperar uma mudança real. Sem flags mantém o comportamento anterior (respeita enabled); --list
imprime os alertas e --only ID envia a um destino único, ignorando enabled e avisando quando o
alerta está desligado (--only envia em modo texto, sem capturar a ROI). Na GUI, o botão Testar
alerta… abre a mesma lista e envia em thread de trabalho. Para não duplicar registros, a linha extra no
alerts.jsonl só é gravada quando a configuração não tem um notificador log.
- Configuração — onde os alertas ficam no YAML e nos overrides da seleção
- Evidências — prints gravados junto dos alertas (opcional)
- doc/00 §11 — decisões de design