1
0
forked from sass/tipibot

More bug fixes

This commit is contained in:
Rene Arumetsa
2026-05-03 15:11:32 +03:00
parent d65173fbe9
commit 07e7f5e0b2
9 changed files with 295 additions and 90 deletions

View File

@@ -1,4 +1,12 @@
"""Google Sheets integration - read/write member data via gspread."""
"""Google Sheets integration - read/write member data via gspread.
Public network-hitting functions are async and delegate the blocking gspread
work to `asyncio.to_thread` so the discord.py event loop is not stalled
(stalled loops drop gateway heartbeats and can disconnect the bot).
Pure-cache helpers (get_cache, find_*) remain sync.
"""
import asyncio
import gspread
from google.oauth2.service_account import Credentials
@@ -69,11 +77,7 @@ def _ensure_headers(ws: gspread.Worksheet) -> None:
ws.update_cell(1, col_idx, header)
def refresh() -> list[dict]:
"""Pull all rows from the sheet into the in-memory cache.
Returns the cache (list of dicts keyed by header names).
"""
def _refresh_sync() -> list[dict]:
global _cache
ws = _get_worksheet()
_ensure_headers(ws)
@@ -83,6 +87,11 @@ def refresh() -> list[dict]:
return _cache
async def refresh() -> list[dict]:
"""Pull all rows from the sheet into the in-memory cache (non-blocking)."""
return await asyncio.to_thread(_refresh_sync)
def get_cache() -> list[dict]:
"""Return the current in-memory cache without re-querying."""
return _cache
@@ -122,16 +131,12 @@ def _row_index_for_member(discord_id: int | None = None, username: str | None =
return None
def update_cell_for_member(
def _update_cell_for_member_sync(
discord_id: int | None,
username: str | None,
column_name: str,
value: str,
) -> bool:
"""Write a value to a specific column for a member row.
Returns True if the write succeeded.
"""
ws = _worksheet or _get_worksheet()
row_idx = _row_index_for_member(discord_id=discord_id, username=username)
if row_idx is None:
@@ -145,7 +150,6 @@ def update_cell_for_member(
ws.update([[value]], gspread.utils.rowcol_to_a1(row_idx, col_idx),
value_input_option="USER_ENTERED")
# Keep cache in sync
cache_idx = row_idx - 3
if 0 <= cache_idx < len(_cache):
_cache[cache_idx][column_name] = value
@@ -153,8 +157,19 @@ def update_cell_for_member(
return True
def batch_set_synced(updates: list[tuple[int, bool]]) -> None:
"""Batch-write 'Discordis synced?' for multiple members in a single API call."""
async def update_cell_for_member(
discord_id: int | None,
username: str | None,
column_name: str,
value: str,
) -> bool:
"""Write a value to a specific column for a member row (non-blocking)."""
return await asyncio.to_thread(
_update_cell_for_member_sync, discord_id, username, column_name, value
)
def _batch_set_synced_sync(updates: list[tuple[int, bool]]) -> None:
ws = _worksheet or _get_worksheet()
col_idx = EXPECTED_HEADERS.index("Discordis synced?") + 1
cells = []
@@ -170,9 +185,14 @@ def batch_set_synced(updates: list[tuple[int, bool]]) -> None:
ws.update_cells(cells, value_input_option="USER_ENTERED")
def set_user_id(username: str, discord_id: int) -> bool:
async def batch_set_synced(updates: list[tuple[int, bool]]) -> None:
"""Batch-write 'Discordis synced?' for multiple members (non-blocking)."""
await asyncio.to_thread(_batch_set_synced_sync, updates)
async def set_user_id(username: str, discord_id: int) -> bool:
"""Write a Discord user ID for a row matched by Discord username."""
return update_cell_for_member(
return await update_cell_for_member(
discord_id=None,
username=username,
column_name="User ID",
@@ -180,9 +200,9 @@ def set_user_id(username: str, discord_id: int) -> bool:
)
def set_synced(discord_id: int, synced: bool) -> bool:
async def set_synced(discord_id: int, synced: bool) -> bool:
"""Mark a member as synced (TRUE) or not (FALSE)."""
return update_cell_for_member(
return await update_cell_for_member(
discord_id=discord_id,
username=None,
column_name="Discordis synced?",
@@ -190,9 +210,9 @@ def set_synced(discord_id: int, synced: bool) -> bool:
)
def update_username(discord_id: int, new_username: str) -> bool:
async def update_username(discord_id: int, new_username: str) -> bool:
"""Update the Discord column for a member (keeps sheet in sync with Discord)."""
return update_cell_for_member(
return await update_cell_for_member(
discord_id=discord_id,
username=None,
column_name="Discord",
@@ -200,17 +220,17 @@ def update_username(discord_id: int, new_username: str) -> bool:
)
def add_new_member_row(username: str, discord_id: int) -> None:
"""Append a new row to the sheet with Discord username and User ID pre-filled.
All other columns are left empty for manual entry by an admin.
"""
def _add_new_member_row_sync(username: str, discord_id: int) -> None:
ws = _worksheet or _get_worksheet()
row = [""] * len(EXPECTED_HEADERS)
row[EXPECTED_HEADERS.index("Discord")] = username
row[EXPECTED_HEADERS.index("User ID")] = str(discord_id)
row[EXPECTED_HEADERS.index("Discordis synced?")] = "FALSE"
ws.append_row(row, value_input_option="USER_ENTERED")
# Add to local cache so subsequent find_member() calls work in the same session
new_entry = {h: row[i] for i, h in enumerate(EXPECTED_HEADERS)}
_cache.append(new_entry)
async def add_new_member_row(username: str, discord_id: int) -> None:
"""Append a new row pre-filled with Discord username and User ID (non-blocking)."""
await asyncio.to_thread(_add_new_member_row_sync, username, discord_id)