Add SandwichReminder-AutoOrder feature for automated sandwich ordering and enhance SandwichReminder logic

This commit is contained in:
Arne Moerman
2026-05-08 16:00:22 +02:00
parent c26898d4d2
commit 2592c0145f
5 changed files with 372 additions and 57 deletions
@@ -0,0 +1,149 @@
$FeatureMeta = @{
Name = 'SandwichReminder-AutoOrder'
Description = 'Personal automation settings for sandwich auto-order browser flow'
Settings = @(
@{
Key = 'baseUrl'
Label = 'Base URL'
Type = 'string'
Default = 'https://ylos-kitchen.unipage.eu'
Description = 'Base URL of the ordering website'
},
@{
Key = 'itemId'
Label = 'Item ID'
Type = 'string'
Default = 'dmwmo3'
Description = 'Item ID for direct item modal URL (default = Smoske)'
},
@{
Key = 'windowTitleHint'
Label = 'Browser Window Title Hint'
Type = 'string'
Default = "YLO'S Kitchen"
Description = 'Used to bring browser window to foreground before sending keys'
},
@{
Key = 'defaultItemRemark'
Label = 'Default Item Remark'
Type = 'string'
Default = 'zonder tomaat aub'
Description = 'For next step (checkout automation)'
},
@{
Key = 'defaultOrderRemark'
Label = 'Default Order Remark'
Type = 'string'
Default = 'levering in Sioen Bistro'
Description = 'For next step (checkout automation)'
},
@{
Key = 'initialDelayMs'
Label = 'Initial Delay (ms)'
Type = 'int'
Default = 2500
Description = 'Wait time after opening browser before key automation starts'
},
@{
Key = 'activationTimeoutMs'
Label = 'Activation Timeout (ms)'
Type = 'int'
Default = 10000
Description = 'How long to retry foregrounding the browser window'
},
@{
Key = 'stepDelayMs'
Label = 'Key Step Delay (ms)'
Type = 'int'
Default = 120
Description = 'Delay between keyboard steps'
},
@{
Key = 'optionToggleKey'
Label = 'Option Toggle Key'
Type = 'string'
Default = 'SPACE'
Description = 'Key used to toggle option checkbox: SPACE or ENTER'
},
@{
Key = 'calibrationOnly'
Label = 'Calibration Only'
Type = 'bool'
Default = $false
Description = 'If enabled, only tabs through the modal and stops (no clicks/add)'
},
@{
Key = 'calibrationTabs'
Label = 'Calibration Tabs'
Type = 'int'
Default = 0
Description = 'Number of tabs to send in calibration mode before stopping'
},
@{
Key = 'tabsToOption1'
Label = 'Tabs To Option 1'
Type = 'int'
Default = 7
Description = 'Tab count from modal start to first option checkbox'
},
@{
Key = 'tabsBetweenOptions'
Label = 'Tabs Between Options'
Type = 'int'
Default = 2
Description = 'Tab count from first checkbox to second checkbox'
},
@{
Key = 'tabsToAddButton'
Label = 'Tabs To Add Button'
Type = 'int'
Default = 6
Description = 'Tab count from second checkbox to add-to-cart button'
},
@{
Key = 'openCartPopupAfterAdd'
Label = 'Open Cart Popup After Add'
Type = 'bool'
Default = $true
Description = 'Open mini-cart after adding item'
},
@{
Key = 'refreshBeforeCart'
Label = 'Refresh Before Cart'
Type = 'bool'
Default = $true
Description = 'Refresh the page after add-to-cart to reset focus to page start before tabbing to mini-cart'
},
@{
Key = 'postRefreshDelayMs'
Label = 'Post-Refresh Delay (ms)'
Type = 'int'
Default = 2500
Description = 'Wait time after F5 refresh before tabbing to mini-cart'
},
@{
Key = 'tabsToCartButton'
Label = 'Tabs To Cart Button'
Type = 'int'
Default = 5
Description = 'Tab count from page start (after refresh) to mini-cart button'
},
@{
Key = 'tabsToConfirmButton'
Label = 'Tabs To Confirm Button'
Type = 'int'
Default = 3
Description = 'Tab count from mini-cart button to confirm order button (opens delivery popup)'
}
)
}
function Invoke-Feature {
param(
[hashtable]$Config,
[hashtable]$State
)
if (-not $State) { $State = @{} }
return $State
}
+15 -2
View File
@@ -118,8 +118,21 @@ function Invoke-Feature {
if ($decision -eq 'order') {
try {
Start-Process $Config['url']
Write-Log -Level Info -Message "Opened sandwich order URL: $($Config['url'])" -Feature 'SandwichReminder'
$rootConfig = Get-Config
$autoOrderConfig = $null
if ($rootConfig.features.ContainsKey('SandwichReminder-AutoOrder')) {
$autoOrderConfig = $rootConfig.features['SandwichReminder-AutoOrder']
}
if ($autoOrderConfig -and $autoOrderConfig['enabled']) {
Write-Log -Level Info -Message 'SandwichReminder-AutoOrder is enabled; running auto-order flow.' -Feature 'SandwichReminder'
[void](Invoke-SandwichAutoOrderFlow -AutoOrderConfig $autoOrderConfig -FallbackUrl $Config['url'])
}
else {
Start-Process $Config['url']
Write-Log -Level Info -Message "Opened sandwich order URL: $($Config['url'])" -Feature 'SandwichReminder'
}
}
catch {
Write-Log -Level Error -Message "Failed to open sandwich order URL '$($Config['url'])': $_" -Feature 'SandwichReminder'
+184
View File
@@ -0,0 +1,184 @@
# SandwichAutoOrder.ps1 — best-effort browser keyboard automation for personal sandwich flow.
function Invoke-AppActivateBestEffort {
param(
[Parameter(Mandatory)]$Shell,
[Parameter(Mandatory)][string[]]$TitleCandidates,
[int]$TimeoutMs = 10000,
[int]$RetryDelayMs = 300
)
$deadline = (Get-Date).AddMilliseconds($TimeoutMs)
$candidates = @($TitleCandidates | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
do {
foreach ($candidate in $candidates) {
if ($Shell.AppActivate($candidate)) {
return $true
}
}
Start-Sleep -Milliseconds $RetryDelayMs
} while ((Get-Date) -lt $deadline)
return $false
}
function Invoke-SandwichAutoOrderFlow {
[CmdletBinding()]
param(
[Parameter(Mandatory)][hashtable]$AutoOrderConfig,
[Parameter(Mandatory)][string]$FallbackUrl,
[string]$FeatureName = 'SandwichReminder-AutoOrder'
)
$baseUrl = [string]$AutoOrderConfig['baseUrl']
if ([string]::IsNullOrWhiteSpace($baseUrl)) { $baseUrl = 'https://ylos-kitchen.unipage.eu' }
$itemId = [string]$AutoOrderConfig['itemId']
if ([string]::IsNullOrWhiteSpace($itemId)) { $itemId = 'dmwmo3' }
$windowTitleHint = [string]$AutoOrderConfig['windowTitleHint']
if ([string]::IsNullOrWhiteSpace($windowTitleHint)) { $windowTitleHint = "YLO'S Kitchen" }
$initialDelayMs = [int]$AutoOrderConfig['initialDelayMs']
if ($initialDelayMs -le 0) { $initialDelayMs = 2500 }
$stepDelayMs = [int]$AutoOrderConfig['stepDelayMs']
if ($stepDelayMs -le 0) { $stepDelayMs = 120 }
$toggleKeySetting = [string]$AutoOrderConfig['optionToggleKey']
if ([string]::IsNullOrWhiteSpace($toggleKeySetting)) { $toggleKeySetting = 'SPACE' }
$toggleKey = if ($toggleKeySetting.ToUpperInvariant() -eq 'ENTER') { '{ENTER}' } else { ' ' }
$calibrationOnly = [bool]$AutoOrderConfig['calibrationOnly']
$calibrationTabs = [int]$AutoOrderConfig['calibrationTabs']
if ($calibrationTabs -lt 0) { $calibrationTabs = 0 }
$tabsToOption1 = [int]$AutoOrderConfig['tabsToOption1']
if ($tabsToOption1 -lt 0) { $tabsToOption1 = 3 }
$tabsBetweenOptions = [int]$AutoOrderConfig['tabsBetweenOptions']
if ($tabsBetweenOptions -lt 0) { $tabsBetweenOptions = 2 }
$tabsToAddButton = [int]$AutoOrderConfig['tabsToAddButton']
if ($tabsToAddButton -lt 0) { $tabsToAddButton = 4 }
$openCartPopup = [bool]$AutoOrderConfig['openCartPopupAfterAdd']
$refreshBeforeCart = [bool]$AutoOrderConfig['refreshBeforeCart']
$postRefreshDelayMs = [int]$AutoOrderConfig['postRefreshDelayMs']
if ($postRefreshDelayMs -le 0) { $postRefreshDelayMs = 2500 }
$tabsToCartButton = [int]$AutoOrderConfig['tabsToCartButton']
if ($tabsToCartButton -le 0) { $tabsToCartButton = 5 }
$tabsToConfirmButton = [int]$AutoOrderConfig['tabsToConfirmButton']
if ($tabsToConfirmButton -le 0) { $tabsToConfirmButton = 3 }
$activationTimeoutMs = [int]$AutoOrderConfig['activationTimeoutMs']
if ($activationTimeoutMs -le 0) { $activationTimeoutMs = 10000 }
$itemUrl = ($baseUrl.TrimEnd('/') + '/item/' + $itemId)
try {
Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue | Out-Null
Start-Process $itemUrl
Write-Log -Level Info -Message "Opened item modal URL for auto-order: $itemUrl" -Feature $FeatureName
Start-Sleep -Milliseconds $initialDelayMs
$shell = New-Object -ComObject WScript.Shell
$hostHint = $null
try { $hostHint = ([uri]$baseUrl).Host } catch {}
$activated = Invoke-AppActivateBestEffort -Shell $shell -TitleCandidates @(
'Bestel online bij YLO''S Kitchen Pittem',
'Bestel online bij YLO',
'Bestel online bij',
$windowTitleHint,
($windowTitleHint -replace "'", ''),
$hostHint,
'ylos-kitchen',
'unipage'
) -TimeoutMs $activationTimeoutMs -RetryDelayMs 300
if (-not $activated) {
Write-Log -Level Warn -Message "Could not foreground browser window (title hint '$windowTitleHint'). Skipping key automation to avoid acting on the wrong window." -Feature $FeatureName
return $false
}
if ($calibrationOnly) {
for ($i = 0; $i -lt $calibrationTabs; $i++) {
$shell.SendKeys('{TAB}')
Start-Sleep -Milliseconds $stepDelayMs
}
Write-Log -Level Info -Message ("Calibration mode completed: sent {0} TAB keys and stopped." -f $calibrationTabs) -Feature $FeatureName
return $true
}
# Modal flow keyboard navigation (best-effort; tab indexes are configurable).
for ($i = 0; $i -lt $tabsToOption1; $i++) {
$shell.SendKeys('{TAB}')
Start-Sleep -Milliseconds $stepDelayMs
}
$shell.SendKeys($toggleKey)
Start-Sleep -Milliseconds $stepDelayMs
for ($i = 0; $i -lt $tabsBetweenOptions; $i++) {
$shell.SendKeys('{TAB}')
Start-Sleep -Milliseconds $stepDelayMs
}
$shell.SendKeys($toggleKey)
Start-Sleep -Milliseconds $stepDelayMs
for ($i = 0; $i -lt $tabsToAddButton; $i++) {
$shell.SendKeys('{TAB}')
Start-Sleep -Milliseconds $stepDelayMs
}
$shell.SendKeys('{ENTER}')
Start-Sleep -Milliseconds 350
if ($openCartPopup) {
if ($refreshBeforeCart) {
$shell.SendKeys('{F5}')
Write-Log -Level Info -Message 'Refreshing page to reset focus before tabbing to mini-cart.' -Feature $FeatureName
Start-Sleep -Milliseconds $postRefreshDelayMs
$activated = Invoke-AppActivateBestEffort -Shell $shell -TitleCandidates @(
'Bestel online bij YLO''S Kitchen Pittem',
'Bestel online bij YLO',
'Bestel online bij',
$windowTitleHint,
($windowTitleHint -replace "'", ''),
$hostHint,
'ylos-kitchen',
'unipage'
) -TimeoutMs $activationTimeoutMs -RetryDelayMs 300
if (-not $activated) {
Write-Log -Level Warn -Message 'Could not re-foreground browser after refresh. Skipping cart popup.' -Feature $FeatureName
return $false
}
}
for ($i = 0; $i -lt $tabsToCartButton; $i++) {
$shell.SendKeys('{TAB}')
Start-Sleep -Milliseconds $stepDelayMs
}
$shell.SendKeys('{ENTER}')
Start-Sleep -Milliseconds $stepDelayMs
for ($i = 0; $i -lt $tabsToConfirmButton; $i++) {
$shell.SendKeys('{TAB}')
Start-Sleep -Milliseconds $stepDelayMs
}
$shell.SendKeys('{ENTER}')
}
Write-Log -Level Info -Message 'Auto-order flow executed up to add-to-cart and cart popup open (best-effort).' -Feature $FeatureName
return $true
}
catch {
Write-Log -Level Error -Message "Auto-order flow failed: $_" -Feature $FeatureName
return $false
}
}
+1
View File
@@ -11,6 +11,7 @@ $script:InternalRoot = $PSScriptRoot # runner.ps1 lives in internal\
. (Join-Path $InternalRoot 'lib\NetworkUtils.ps1')
. (Join-Path $InternalRoot 'lib\ToastHelper.ps1')
. (Join-Path $InternalRoot 'lib\PromptHelper.ps1')
. (Join-Path $InternalRoot 'lib\SandwichAutoOrder.ps1')
. (Join-Path $InternalRoot 'lib\Config.ps1')
Write-Log -Level Info -Message '────── Runner started ──────' -Feature 'Runner'