P0 fixes: - agent register: upsert by agent_id (no duplicate rows) - daemon poll-once: extract _gmail_poll_cycle, run synchronously - garc_core.py: suppress urllib3/googleapiclient DeprecationWarnings P1 fixes: - OAuth: detect RefreshError → delete stale token → re-auth flow - OAuth: scope coverage check before returning valid creds - ingress: add stale-reset subcommand (reset in_progress > N min) - sheets: trim-sheet / clean-all — deleteDimension for empty rows - approval gate: send Gmail notification to GARC_APPROVAL_EMAIL P2 additions: - Google Chat: garc-chat-helper.py + garc send chat subcommands - Service Account: garc auth service-account verify + DWD support - Audit log: Sheets audit tab + garc audit list + bin/garc async hook - garc auth revoke: POST /revoke + delete token file - kg: pagination fix, shell injection fix, garc-kg-query.py - docs: _doc_insert_text / append_doc / garc drive append-doc P3 additions: - Multi-tenant: lib/profile.sh (list/use/add/show/remove/current) bin/garc: auto-load profile config.env and token.json - Google Forms pipeline: garc-forms-helper.py + lib/forms.sh garc forms list/responses/watch - systemd: _daemon_install_service OS-detect → launchd or systemd units - Python version gate (>=3.10) in bin/garc + pyproject.toml - garc doctor command for environment diagnostics Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
GARC Chat Helper — Google Chat Space message operations
|
|
send / list-spaces / list-messages
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
from garc_core import build_service
|
|
|
|
|
|
def get_svc():
|
|
"""Build Google Chat service."""
|
|
return build_service("chat", "v1")
|
|
|
|
|
|
def send_message(space_id: str, message: str, thread_key: str = "") -> dict:
|
|
"""Send a plain-text message to a Chat space."""
|
|
svc = get_svc()
|
|
|
|
body: dict = {"text": message}
|
|
params: dict = {"parent": space_id}
|
|
|
|
if thread_key:
|
|
body["thread"] = {"threadKey": thread_key}
|
|
params["messageReplyOption"] = "REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
|
|
|
|
result = svc.spaces().messages().create(**params, body=body).execute()
|
|
msg_name = result.get("name", "")
|
|
print(f"✅ Message sent: {msg_name}")
|
|
return result
|
|
|
|
|
|
def list_spaces() -> list:
|
|
"""List all Chat spaces the bot has access to."""
|
|
svc = get_svc()
|
|
result = svc.spaces().list().execute()
|
|
spaces = result.get("spaces", [])
|
|
if not spaces:
|
|
print("No spaces found.")
|
|
return []
|
|
print(f"Spaces ({len(spaces)}):")
|
|
for s in spaces:
|
|
display = s.get("displayName", "(no name)")
|
|
name = s.get("name", "")
|
|
stype = s.get("spaceType", "")
|
|
print(f" {name:<35} {display:<30} {stype}")
|
|
return spaces
|
|
|
|
|
|
def list_messages(space_id: str, max_results: int = 25) -> list:
|
|
"""List recent messages in a Chat space."""
|
|
svc = get_svc()
|
|
result = svc.spaces().messages().list(
|
|
parent=space_id,
|
|
pageSize=max_results,
|
|
).execute()
|
|
messages = result.get("messages", [])
|
|
if not messages:
|
|
print(f"No messages in {space_id}")
|
|
return []
|
|
print(f"Messages ({len(messages)}):")
|
|
for m in messages:
|
|
sender = (m.get("sender") or {}).get("displayName", "?")
|
|
text = m.get("text", "")[:80]
|
|
create_time = m.get("createTime", "")[:19]
|
|
print(f" [{create_time}] {sender}: {text}")
|
|
return messages
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="GARC Chat Helper")
|
|
sub = parser.add_subparsers(dest="command")
|
|
|
|
sp = sub.add_parser("send", help="Send a message to a Chat space")
|
|
sp.add_argument("--space-id", required=True, help="Chat space ID (e.g. spaces/AAABBB)")
|
|
sp.add_argument("--message", required=True, help="Message text")
|
|
sp.add_argument("--thread-key", default="", help="Thread key for threaded replies")
|
|
|
|
sub.add_parser("list-spaces", help="List accessible Chat spaces")
|
|
|
|
lmp = sub.add_parser("list-messages", help="List messages in a space")
|
|
lmp.add_argument("--space-id", required=True)
|
|
lmp.add_argument("--max", type=int, default=25)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "send":
|
|
send_message(args.space_id, args.message, args.thread_key)
|
|
elif args.command == "list-spaces":
|
|
list_spaces()
|
|
elif args.command == "list-messages":
|
|
list_messages(args.space_id, args.max)
|
|
else:
|
|
parser.print_help()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|