a digital entity named phi that roams bsky phi.zzstoatzz.io
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

add pause/resume API for remote control via iOS Shortcuts

POST /api/control/pause and /api/control/resume toggle notification
processing. when paused, the poller keeps running but skips processing
and doesn't mark notifications as read — they accumulate and get
processed on resume. authenticated via CONTROL_TOKEN bearer token.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz c05897b9 83eea801

+43 -1
+5
src/bot/config.py
··· 80 80 default=10, description="The interval for polling for notifications" 81 81 ) 82 82 83 + # Control API 84 + control_token: str | None = Field( 85 + default=None, description="Bearer token for /api/control endpoints" 86 + ) 87 + 83 88 # Debug mode 84 89 debug: bool = Field(default=True, description="Whether to run in debug mode") 85 90
+31 -1
src/bot/main.py
··· 150 150 @app.get("/health") 151 151 async def health(): 152 152 """Health check endpoint.""" 153 - return {"status": "healthy", "polling_active": bot_status.polling_active} 153 + return {"status": "healthy", "polling_active": bot_status.polling_active, "paused": bot_status.paused} 154 + 155 + 156 + def _check_control_token(request: Request): 157 + """Validate bearer token for control endpoints.""" 158 + if not settings.control_token: 159 + return JSONResponse({"error": "control token not configured"}, status_code=503) 160 + auth = request.headers.get("authorization", "") 161 + if auth != f"Bearer {settings.control_token}": 162 + return JSONResponse({"error": "unauthorized"}, status_code=401) 163 + return None 164 + 165 + 166 + @app.post("/api/control/pause") 167 + async def pause(request: Request): 168 + """Pause notification processing. Unread notifications accumulate until resumed.""" 169 + if err := _check_control_token(request): 170 + return err 171 + bot_status.paused = True 172 + logger.info("paused via API") 173 + return {"paused": True} 174 + 175 + 176 + @app.post("/api/control/resume") 177 + async def resume(request: Request): 178 + """Resume notification processing. Queued notifications will be processed on next poll.""" 179 + if err := _check_control_token(request): 180 + return err 181 + bot_status.paused = False 182 + logger.info("resumed via API") 183 + return {"paused": False} 154 184 155 185 156 186 @app.get("/status", response_class=HTMLResponse)
+6
src/bot/services/notification_poller.py
··· 77 77 elif unread: 78 78 logger.info(f"{len(unread)} new notifications") 79 79 80 + # When paused, don't process or mark as read — notifications accumulate 81 + if bot_status.paused: 82 + if unread: 83 + logger.debug(f"paused, skipping {len(unread)} unread notifications") 84 + return 85 + 80 86 processed_any = False 81 87 82 88 # Process notifications from oldest to newest
+1
src/bot/status.py
··· 23 23 last_response_time: datetime | None = None 24 24 ai_enabled: bool = False 25 25 polling_active: bool = False 26 + paused: bool = False 26 27 27 28 @property 28 29 def uptime_seconds(self) -> float: