From 7bce5cabc9476cb01f2e05d6f3bbe1fbedbffad8 Mon Sep 17 00:00:00 2001 From: Kelly Thomas Reardon Date: Sat, 30 May 2026 19:54:46 -0500 Subject: [PATCH] first attempt at a webhook consumer --- Dockerfile | 12 +++- README.md | 7 --- Start-DoneTickConsumer.ps1 | 123 +++++++++++++++++++++++++++++++++++++ crontab | 2 + main.sh | 13 ++++ 5 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 Start-DoneTickConsumer.ps1 create mode 100644 crontab create mode 100644 main.sh diff --git a/Dockerfile b/Dockerfile index 737b04d..a66ba9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,22 @@ FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04 USER root RUN apt-get update \ - && apt-get install -y --no-install-recommends tzdata \ + && apt-get install -y --no-install-recommends tzdata wget \ + && wget -q https://github.com/aptible/supercronic/releases/latest/download/supercronic-linux-amd64 -O /usr/local/bin/supercronic \ + && chmod +x /usr/local/bin/supercronic \ && apt-get dist-upgrade -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* + USER 1000:1000 WORKDIR /data +COPY ["crontab", "/data/"] +COPY ["main.sh", "/data/"] COPY ["Start-DoneTickNotifier.ps1", "/data/"] +COPY ["Start-DoneTickConsumer.ps1", "/data/"] -ENTRYPOINT ["pwsh", "-Command", "/data/Start-DoneTickNotifier.ps1"] +RUN chmod +x /data/main.sh + +ENTRYPOINT ["main.sh"] diff --git a/README.md b/README.md index 432aa04..7504e6c 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,6 @@ A small PowerShell notifier for [Donetick](https://github.com/donetick/donetick) chores. -> [!IMPORTANT] -> **AI Assistance Disclosure** -> -> OpenAI Codex was used to help create this README and the Gitea pipeline files in this repository. -> -> All released application code is written by the repository owner. - The notifier checks the Donetick external API for chores, groups tasks that are overdue or due today, and sends summary notifications through an Apprise-compatible webhook. ## What it does diff --git a/Start-DoneTickConsumer.ps1 b/Start-DoneTickConsumer.ps1 new file mode 100644 index 0000000..56d7c71 --- /dev/null +++ b/Start-DoneTickConsumer.ps1 @@ -0,0 +1,123 @@ +[string] $ListenUrl = "http://localhost:8080/" + +function Write-JsonResponse +{ + param( + [Parameter(Mandatory=$true)] + [System.Net.HttpListenerResponse] + $Response, + + [Parameter(Mandatory=$true)] + [int] + $StatusCode, + + [Parameter(Mandatory=$true)] + [hashtable] + $Body + ) + + $json = $Body | ConvertTo-Json -Depth 10 + $bytes = [System.Text.Encoding]::UTF8.GetBytes($json) + + $Response.StatusCode = $StatusCode + $Response.ContentType = "application/json" + $Response.ContentEncoding = [System.Text.Encoding]::UTF8 + $Response.ContentLength64 = $bytes.Length + $Response.OutputStream.Write($bytes, 0, $bytes.Length) + $Response.OutputStream.Close() +} + +function Read-RequestBody +{ + param( + [Parameter(Mandatory=$true)] + [System.Net.HttpListenerRequest] + $Request + ) + + $reader = [System.IO.StreamReader]::new($Request.InputStream, $Request.ContentEncoding) + try { + return $reader.ReadToEnd() + } + finally { + $reader.Close() + } +} + +function Receive-Webhook +{ + param( + [Parameter(Mandatory=$true)] + [System.Net.HttpListenerContext] + $Context + ) + + $request = $Context.Request + $response = $Context.Response + + if ($request.HttpMethod -ne "POST") { + Write-JsonResponse -Response $response -StatusCode 405 -Body @{ + ok = $false + error = "Only POST requests are supported." + } + return + } + + $rawBody = Read-RequestBody -Request $request + $payload = $null + + try { + if ($rawBody) { + $payload = $rawBody | ConvertFrom-Json + } + } + catch { + Write-JsonResponse -Response $response -StatusCode 400 -Body @{ + ok = $false + error = "Request body was not valid JSON." + } + return + } + + Write-Host "Webhook received at $(Get-Date -Format o)" + Write-Host "From: $($request.RemoteEndPoint)" + Write-Host "Path: $($request.Url.AbsolutePath)" + + if ($payload) { + Write-Host "Payload:" + Write-Host ($payload | ConvertTo-Json -Depth 20) + } + else { + Write-Host "Payload: " + } + + # Add your real webhook handling here. + # Example: inspect $payload.event, save data, or call another API. + + Write-JsonResponse -Response $response -StatusCode 200 -Body @{ + ok = $true + message = "Webhook accepted." + receivedAt = (Get-Date -Format o) + } +} + +$listener = [System.Net.HttpListener]::new() +$listener.Prefixes.Add($ListenUrl) + +try { + $listener.Start() + Write-Host "Webhook consumer listening on $ListenUrl" + Write-Host "Press Ctrl+C to stop." + + while ($listener.IsListening) { + $context = $listener.GetContext() + Receive-Webhook -Context $context + } +} +finally { + if ($listener.IsListening) { + $listener.Stop() + } + + $listener.Close() +} diff --git a/crontab b/crontab new file mode 100644 index 0000000..4c6e11a --- /dev/null +++ b/crontab @@ -0,0 +1,2 @@ +# Default schedule (overridden at runtime by JOB_SCHEDULE env var) +0 * * * * pwsh /data/Start-DoneTickNotifier.ps1 \ No newline at end of file diff --git a/main.sh b/main.sh new file mode 100644 index 0000000..c0226c2 --- /dev/null +++ b/main.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +# Build the crontab dynamically from the env variable +echo "${JOB_SCHEDULE} pwsh /data/Start-DoneTineNotifier.ps1" > /tmp/crontab-runtime + +echo "Cron schedule set to: ${JOB_SCHEDULE}" + +# Start supercronic in background using the generated crontab +supercronic /tmp/crontab-runtime & + +# Start the HTTP listener in foreground +exec pwsh /data/Start-DoneTickConsumer.ps1 \ No newline at end of file