← back to Rentv Sheet Enrich Refine
connect.py
84 lines
#!/usr/bin/env python3
"""
connect.py — ONE-TIME Google Sheets auth for the RENTV CRE-sheet enrichment.
Reuses the existing DESKTOP-type OAuth client (YT_UPLOAD_CLIENT_ID/SECRET) that
already auto-trusts the http://localhost loopback — so there is NOTHING to register
in the Google console. Runs the consent flow once, captures a long-lived refresh
token, and stores it as SHEETS_REFRESH_TOKEN in the secure secrets store.
IMPORTANT: log in with the Google account that can EDIT the target spreadsheet.
Run it yourself in this session with the `!` prefix so the browser opens here:
! python3 ~/Projects/rentv-sheet-enrich/connect.py
Then click "Allow".
"""
import os, sys, json, time, urllib.parse, urllib.request, http.server, threading, webbrowser, re
ENV_PATH = os.path.expanduser("~/Projects/secrets-manager/.env")
def load_env(path):
d = {}
try:
for ln in open(path):
m = re.match(r'^([A-Z0-9_]+)=(.*)$', ln.rstrip("\n"))
if m:
d[m.group(1)] = m.group(2)
except FileNotFoundError:
pass
return d
def upsert_env(path, kv):
try: lines = open(path).read().splitlines()
except FileNotFoundError: lines = []
keys = set(kv); out, seen = [], set()
for ln in lines:
m = re.match(r'^([A-Z0-9_]+)=', ln)
if m and m.group(1) in keys:
out.append(f'{m.group(1)}={kv[m.group(1)]}'); seen.add(m.group(1))
else:
out.append(ln)
for k, v in kv.items():
if k not in seen: out.append(f'{k}={v}')
open(path, "w").write("\n".join(out) + "\n")
envf = load_env(ENV_PATH)
CID = os.environ.get("YT_UPLOAD_CLIENT_ID") or envf.get("YT_UPLOAD_CLIENT_ID") or sys.exit("No desktop OAuth client id found (YT_UPLOAD_CLIENT_ID)")
CSEC = os.environ.get("YT_UPLOAD_CLIENT_SECRET") or envf.get("YT_UPLOAD_CLIENT_SECRET") or sys.exit("No desktop OAuth client secret found")
PORT = int(os.environ.get("SHEETS_PORT", "8733"))
REDIRECT = f"http://localhost:{PORT}/"
SCOPE = "https://www.googleapis.com/auth/spreadsheets"
hold = {}
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
p = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
if "code" in p: hold["code"] = p["code"][0]
elif "error" in p: hold["error"] = p["error"][0]
self.send_response(200); self.send_header("Content-Type", "text/html"); self.end_headers()
self.wfile.write(b"<h2 style='font-family:sans-serif'>Sheets connected. You can close this tab.</h2>")
def log_message(self, *a): pass
srv = http.server.HTTPServer(("127.0.0.1", PORT), H)
threading.Thread(target=srv.serve_forever, daemon=True).start()
auth = "https://accounts.google.com/o/oauth2/v2/auth?" + urllib.parse.urlencode({
"client_id": CID, "redirect_uri": REDIRECT, "response_type": "code",
"scope": SCOPE, "access_type": "offline", "prompt": "consent"})
print("AUTH_URL:", auth, flush=True)
try: webbrowser.open(auth)
except Exception: pass
print("Waiting for consent (Allow in the browser)...", flush=True)
for _ in range(300):
if hold: break
time.sleep(1)
srv.shutdown()
if "code" not in hold: sys.exit(f"No consent captured: {hold or 'timeout'}")
data = urllib.parse.urlencode({"code": hold["code"], "client_id": CID, "client_secret": CSEC,
"redirect_uri": REDIRECT, "grant_type": "authorization_code"}).encode()
tok = json.load(urllib.request.urlopen(urllib.request.Request("https://oauth2.googleapis.com/token", data)))
rt = tok.get("refresh_token")
if not rt: sys.exit("No refresh_token returned (revoke prior grant + retry).")
upsert_env(ENV_PATH, {"SHEETS_REFRESH_TOKEN": rt})
print("CONNECTED — Sheets refresh token saved. Enrichment can now write to the sheet.", flush=True)