Merge pull request 'fix/economy-money-safety' (#4) from fix/economy-money-safety into master

Reviewed-on: renkar/tipibot#4
This commit is contained in:
2026-08-19 17:36:16 +00:00
14 changed files with 1889 additions and 1479 deletions

View File

@@ -7,12 +7,15 @@ Pure-cache helpers (get_cache, find_*) remain sync.
"""
import asyncio
import logging
import gspread
from google.oauth2.service_account import Credentials
import config
log = logging.getLogger(__name__)
# Scopes needed: read + write to Sheets
SCOPES = [
"https://www.googleapis.com/auth/spreadsheets",
@@ -69,12 +72,25 @@ def _get_worksheet() -> gspread.Worksheet:
def _ensure_headers(ws: gspread.Worksheet) -> None:
"""If the sheet is empty or missing headers, write them (headers are in row 1)."""
"""Verify the header row matches what we expect.
The production sheet is owner-managed and its header row (row 1) is a
protected range, so the bot must NOT write to it — attempting to do so
raises `APIError [400]: You are trying to edit a protected cell or object`
and aborts the whole refresh. We only log a mismatch so it can be fixed
by hand; column lookups still work as long as the headers we rely on exist.
"""
existing = ws.row_values(1)
if existing != EXPECTED_HEADERS:
for col_idx, header in enumerate(EXPECTED_HEADERS, start=1):
if col_idx > len(existing) or existing[col_idx - 1] != header:
ws.update_cell(1, col_idx, header)
missing = [h for h in EXPECTED_HEADERS if h not in existing]
log.warning(
"Sheet header row does not match EXPECTED_HEADERS "
"(missing/renamed: %s). Expected %s, found %s. "
"Not writing to the (protected) header row; fix it manually if needed.",
missing or "none — order/whitespace differs",
EXPECTED_HEADERS,
existing,
)
def _refresh_sync() -> list[dict]: