625569485f
Adds `fn doctor` read-only diagnostic command with subcommands artefacts, services, sync, uses-functions, unused, and --json flag for agents. Each subcommand wraps a registry function in functions/infra/. New functions: - artefact_doctor, services_status, pc_locations_drift, audit_uses_functions, find_unused_functions (Go diagnostics) - backup_sqlite_db, rotate_backups, wait_for_http, wait_for_port, port_kill, tail_journal, pre_commit_hook_install (bash utilities) - notify_telegram (Go HTTP) - backup_all pipeline (tag launcher) Plus prior session leftovers (scan_secrets_in_dirty, append_diary_entry, git utilities, http_session_cookie_middleware, compile/full-git pipelines). Fixes pc_locations_drift filepath.Join bug with absolute dir_path. Documents fn doctor in CLAUDE.md, .claude/rules/fn_doctor.md (rule 23), docs/architecture.md, CHANGELOG.md (2026-05-07), and diary entry. First fn doctor uses-functions run found drift in 7/12 apps (deuda para sincronizar app.md con imports reales). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.1 KiB
Go
83 lines
2.1 KiB
Go
package infra
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
var validParseModes = map[string]bool{
|
|
"": true,
|
|
"Markdown": true,
|
|
"MarkdownV2": true,
|
|
"HTML": true,
|
|
}
|
|
|
|
// NotifyTelegram envía un mensaje a un chat de Telegram via Bot API.
|
|
// botToken: token del bot sin prefijo "bot". chatID: ID numérico o @channelname.
|
|
// parseMode: "" (plain), "Markdown", "MarkdownV2" o "HTML".
|
|
// Textos superiores a 4096 chars se truncan a 4093 + "...".
|
|
func NotifyTelegram(botToken string, chatID string, text string, parseMode string) error {
|
|
if !validParseModes[parseMode] {
|
|
return fmt.Errorf("notify_telegram: invalid parseMode %q (must be \"\", \"Markdown\", \"MarkdownV2\" or \"HTML\")", parseMode)
|
|
}
|
|
|
|
const maxLen = 4096
|
|
if len(text) > maxLen {
|
|
text = text[:4093] + "..."
|
|
}
|
|
|
|
payload := map[string]any{
|
|
"chat_id": chatID,
|
|
"text": text,
|
|
}
|
|
if parseMode != "" {
|
|
payload["parse_mode"] = parseMode
|
|
}
|
|
|
|
data, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("notify_telegram: marshal payload: %w", err)
|
|
}
|
|
|
|
url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", botToken)
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
|
|
if err != nil {
|
|
return fmt.Errorf("notify_telegram: build request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("notify_telegram: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("notify_telegram: read response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("notify_telegram: telegram api: status=%d body=%s", resp.StatusCode, body)
|
|
}
|
|
|
|
var result struct {
|
|
OK bool `json:"ok"`
|
|
Description string `json:"description"`
|
|
}
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return fmt.Errorf("notify_telegram: parse response: %w", err)
|
|
}
|
|
if !result.OK {
|
|
return fmt.Errorf("notify_telegram: telegram: %s", result.Description)
|
|
}
|
|
|
|
return nil
|
|
}
|