194 lines
4.2 KiB
Go
194 lines
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"os/user"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
const appUserModelID = "de.stadthilden.GoSysNotifyAgent"
|
|
|
|
type Notification struct {
|
|
ID int64 `json:"id"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
Title string `json:"title"`
|
|
Message string `json:"message"`
|
|
TargetUser string `json:"target_user"`
|
|
}
|
|
|
|
var (
|
|
serviceURL = "http://127.0.0.1:24000"
|
|
pollInterval = 10 * time.Second
|
|
)
|
|
|
|
func main() {
|
|
|
|
if v := os.Getenv("SERVICE_URL"); v != "" {
|
|
serviceURL = strings.TrimRight(v, "/")
|
|
}
|
|
|
|
// Benutzername bestimmen (z. B. "DOMAIN\\user" oder "user")
|
|
userName := currentUserName()
|
|
log.Printf("Notification-Agent gestartet für Benutzer: %s", userName)
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
|
|
var lastSeenID int64 = 0
|
|
|
|
ticker := time.NewTicker(pollInterval)
|
|
defer ticker.Stop()
|
|
|
|
// beim Start einmal sofort
|
|
if err := pollOnce(ctx, userName, &lastSeenID); err != nil {
|
|
log.Printf("initial poll error: %v", err)
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
log.Println("Agent beendet.")
|
|
return
|
|
case <-ticker.C:
|
|
if err := pollOnce(ctx, userName, &lastSeenID); err != nil {
|
|
log.Printf("poll error: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func currentUserName() string {
|
|
u, err := user.Current()
|
|
if err == nil && u.Username != "" {
|
|
return u.Username
|
|
}
|
|
if u := os.Getenv("USERNAME"); u != "" {
|
|
return u
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
func pollOnce(ctx context.Context, userName string, lastSeenID *int64) error {
|
|
url := fmt.Sprintf("%s/api/notifications?user=%s&since_id=%d",
|
|
serviceURL,
|
|
urlQueryEscape(userName),
|
|
*lastSeenID,
|
|
)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("status %s", resp.Status)
|
|
}
|
|
|
|
var notifs []Notification
|
|
if err := json.NewDecoder(resp.Body).Decode(¬ifs); err != nil {
|
|
return err
|
|
}
|
|
|
|
var maxID = *lastSeenID
|
|
for _, n := range notifs {
|
|
if n.ID > maxID {
|
|
maxID = n.ID
|
|
}
|
|
showErr := showToast(n.Title, n.Message)
|
|
if showErr != nil {
|
|
log.Printf("showToast error: %v", showErr)
|
|
}
|
|
}
|
|
|
|
if maxID > *lastSeenID {
|
|
*lastSeenID = maxID
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func urlQueryEscape(s string) string {
|
|
// minimal, reicht hier
|
|
return strings.ReplaceAll(s, " ", "%20")
|
|
}
|
|
|
|
// === Toast ===
|
|
|
|
func escapeXML(s string) string {
|
|
replacer := strings.NewReplacer(
|
|
`&`, "&",
|
|
`<`, "<",
|
|
`>`, ">",
|
|
`"`, """,
|
|
`'`, "'",
|
|
)
|
|
return replacer.Replace(s)
|
|
}
|
|
|
|
func showToast(title, message string) error {
|
|
title = strings.TrimSpace(title)
|
|
message = strings.TrimSpace(message)
|
|
if title == "" {
|
|
title = "Benachrichtigung"
|
|
}
|
|
if message == "" {
|
|
return nil
|
|
}
|
|
|
|
// XML escapen
|
|
title = escapeXML(title)
|
|
message = escapeXML(message)
|
|
|
|
psScript := `
|
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
|
|
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null
|
|
|
|
# bevorzugt Windows Terminal, sonst Windows PowerShell, sonst erste App
|
|
$appid = (Get-StartApps | Where-Object Name -eq 'Windows Terminal').AppId
|
|
if (-not $appid) {
|
|
$appid = (Get-StartApps | Where-Object Name -eq 'Windows PowerShell').AppId
|
|
}
|
|
if (-not $appid) {
|
|
$appid = (Get-StartApps | Select-Object -First 1).AppId
|
|
}
|
|
|
|
$xmlString = @"
|
|
<toast activationType="foreground">
|
|
<visual>
|
|
<binding template="ToastGeneric">
|
|
<text>` + title + `</text>
|
|
<text>` + message + `</text>
|
|
</binding>
|
|
</visual>
|
|
</toast>
|
|
"@
|
|
|
|
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
|
$xml.LoadXml($xmlString)
|
|
|
|
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appid)
|
|
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
|
|
$notifier.Show($toast)
|
|
`
|
|
|
|
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", psScript)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("powershell toast error: %v, output: %s", err, string(out))
|
|
}
|
|
return nil
|
|
}
|