forked from sass/tipibot
More bug fixes
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user