DivYonko commited on
Commit
6285ada
·
1 Parent(s): 67899d6

Add API key input field in sidebar - users can enter their own YouTube API key

Browse files
Files changed (2) hide show
  1. app.py +36 -6
  2. shared.py +6 -5
app.py CHANGED
@@ -209,12 +209,14 @@ def _fetch_chat_messages(live_chat_id: str, api_key: str, page_token: str | None
209
  return [], None, 5000
210
 
211
 
212
- def _scraper_thread_fn(video_id: str, redis_key: str, stop_event: threading.Event, min_poll_s: float = 10.0) -> None:
213
  """Background thread � scrapes live chat via YouTube Data API v3."""
214
- api_key = os.getenv("YOUTUBE_API_KEY", "")
 
 
215
  logger.info("YOUTUBE_API_KEY present: %s (length=%d)", bool(api_key), len(api_key))
216
  if not api_key:
217
- msg = "YOUTUBE_API_KEY env var not set. Cannot start scraper."
218
  logger.error(msg)
219
  _META["scraper_error"] = msg
220
  return
@@ -330,7 +332,7 @@ def _scraper_thread_fn(video_id: str, redis_key: str, stop_event: threading.Even
330
  logger.info("Scraper thread ended � key=%s", redis_key)
331
 
332
 
333
- def start_scraper(slot_idx: int, video_id: str, redis_key: str, min_poll_s: float = 10.0) -> None:
334
  """Start a scraper thread for the given slot, stopping any existing one first."""
335
  key = str(slot_idx)
336
  stop_scraper(slot_idx)
@@ -338,7 +340,7 @@ def start_scraper(slot_idx: int, video_id: str, redis_key: str, min_poll_s: floa
338
  stop_event = threading.Event()
339
  t = threading.Thread(
340
  target=_scraper_thread_fn,
341
- args=(video_id, redis_key, stop_event, min_poll_s),
342
  daemon=True,
343
  name=f"scraper-{slot_idx}",
344
  )
@@ -969,6 +971,34 @@ with st.sidebar:
969
  spam_threshold = st.slider("Spam alert threshold (%)", 10, 60, 30, key="spam_threshold_pct") / 100
970
  st.divider()
971
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
972
  # -- Multi-Stream Scraper Control --
973
  st.markdown('<p style="font-size:0.68rem;font-weight:700;color:var(--accent);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Stream Control</p>', unsafe_allow_html=True)
974
 
@@ -997,7 +1027,7 @@ with st.sidebar:
997
  vid = extract_video_id(st.session_state[vid_skey])
998
  rkey = st.session_state[rkey_skey].strip() or f"chat_messages_{label.lower()}"
999
  if vid:
1000
- start_scraper(idx, vid, rkey, min_poll_s=float(st.session_state.get("refresh_rate", 10)))
1001
  st.session_state.streams[idx]["proc"] = _SCRAPER_THREADS.get(str(idx))
1002
  st.session_state.streams[idx]["video_id"] = vid
1003
  st.session_state.streams[idx]["redis_key"] = rkey
 
209
  return [], None, 5000
210
 
211
 
212
+ def _scraper_thread_fn(video_id: str, redis_key: str, stop_event: threading.Event, min_poll_s: float = 10.0, api_key: str = "") -> None:
213
  """Background thread � scrapes live chat via YouTube Data API v3."""
214
+ # Use passed key first, fall back to environment variable
215
+ if not api_key:
216
+ api_key = os.getenv("YOUTUBE_API_KEY", "")
217
  logger.info("YOUTUBE_API_KEY present: %s (length=%d)", bool(api_key), len(api_key))
218
  if not api_key:
219
+ msg = "No API key provided. Enter your YouTube Data API v3 key in the sidebar."
220
  logger.error(msg)
221
  _META["scraper_error"] = msg
222
  return
 
332
  logger.info("Scraper thread ended � key=%s", redis_key)
333
 
334
 
335
+ def start_scraper(slot_idx: int, video_id: str, redis_key: str, min_poll_s: float = 10.0, api_key: str = "") -> None:
336
  """Start a scraper thread for the given slot, stopping any existing one first."""
337
  key = str(slot_idx)
338
  stop_scraper(slot_idx)
 
340
  stop_event = threading.Event()
341
  t = threading.Thread(
342
  target=_scraper_thread_fn,
343
+ args=(video_id, redis_key, stop_event, min_poll_s, api_key),
344
  daemon=True,
345
  name=f"scraper-{slot_idx}",
346
  )
 
971
  spam_threshold = st.slider("Spam alert threshold (%)", 10, 60, 30, key="spam_threshold_pct") / 100
972
  st.divider()
973
 
974
+ # -- API Key --
975
+ st.markdown('<p style="font-size:0.68rem;font-weight:700;color:var(--accent);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">YouTube API Key</p>', unsafe_allow_html=True)
976
+ _env_key = os.getenv("YOUTUBE_API_KEY", "")
977
+ _api_key_input = st.text_input(
978
+ "API Key",
979
+ value=st.session_state.get("user_api_key", ""),
980
+ type="password",
981
+ placeholder="AIza... (paste your YouTube Data API v3 key)",
982
+ key="api_key_input",
983
+ help="Your YouTube Data API v3 key. Never shared or stored permanently.",
984
+ )
985
+ # Store in session state whenever changed
986
+ if _api_key_input:
987
+ st.session_state["user_api_key"] = _api_key_input
988
+ # Show status
989
+ _effective_key = _api_key_input or _env_key
990
+ if _effective_key:
991
+ st.markdown(
992
+ f'<div style="font-size:0.7rem;color:#22c55e;margin-bottom:4px;">\u2713 API key set ({len(_effective_key)} chars)</div>',
993
+ unsafe_allow_html=True
994
+ )
995
+ else:
996
+ st.markdown(
997
+ '<div style="font-size:0.7rem;color:#ef4444;margin-bottom:4px;">\u26a0 No API key — scraper won\'t start</div>',
998
+ unsafe_allow_html=True
999
+ )
1000
+ st.divider()
1001
+
1002
  # -- Multi-Stream Scraper Control --
1003
  st.markdown('<p style="font-size:0.68rem;font-weight:700;color:var(--accent);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Stream Control</p>', unsafe_allow_html=True)
1004
 
 
1027
  vid = extract_video_id(st.session_state[vid_skey])
1028
  rkey = st.session_state[rkey_skey].strip() or f"chat_messages_{label.lower()}"
1029
  if vid:
1030
+ start_scraper(idx, vid, rkey, min_poll_s=float(st.session_state.get("refresh_rate", 10)), api_key=st.session_state.get("user_api_key", "") or os.getenv("YOUTUBE_API_KEY", ""))
1031
  st.session_state.streams[idx]["proc"] = _SCRAPER_THREADS.get(str(idx))
1032
  st.session_state.streams[idx]["video_id"] = vid
1033
  st.session_state.streams[idx]["redis_key"] = rkey
shared.py CHANGED
@@ -208,10 +208,11 @@ def _fetch_chat_messages(live_chat_id: str, api_key: str, page_token: str | None
208
  return [], None, 5000
209
 
210
 
211
- def _scraper_thread_fn(video_id: str, redis_key: str, stop_event: threading.Event, min_poll_s: float = 10.0) -> None:
212
- api_key = os.getenv("YOUTUBE_API_KEY", "")
213
  if not api_key:
214
- msg = "YOUTUBE_API_KEY env var not set. Cannot start scraper."
 
 
215
  logger.error(msg)
216
  _META["scraper_error"] = msg
217
  return
@@ -296,13 +297,13 @@ def _scraper_thread_fn(video_id: str, redis_key: str, stop_event: threading.Even
296
  stop_event.wait(timeout=wait_s)
297
 
298
 
299
- def start_scraper(slot_idx: int, video_id: str, redis_key: str, min_poll_s: float = 10.0) -> None:
300
  key = str(slot_idx)
301
  stop_scraper(slot_idx)
302
  stop_event = threading.Event()
303
  t = threading.Thread(
304
  target=_scraper_thread_fn,
305
- args=(video_id, redis_key, stop_event, min_poll_s),
306
  daemon=True,
307
  name=f"scraper-{slot_idx}",
308
  )
 
208
  return [], None, 5000
209
 
210
 
211
+ def _scraper_thread_fn(video_id: str, redis_key: str, stop_event: threading.Event, min_poll_s: float = 10.0, api_key: str = "") -> None:
 
212
  if not api_key:
213
+ api_key = os.getenv("YOUTUBE_API_KEY", "")
214
+ if not api_key:
215
+ msg = "No API key provided. Enter your YouTube Data API v3 key in the sidebar."
216
  logger.error(msg)
217
  _META["scraper_error"] = msg
218
  return
 
297
  stop_event.wait(timeout=wait_s)
298
 
299
 
300
+ def start_scraper(slot_idx: int, video_id: str, redis_key: str, min_poll_s: float = 10.0, api_key: str = "") -> None:
301
  key = str(slot_idx)
302
  stop_scraper(slot_idx)
303
  stop_event = threading.Event()
304
  t = threading.Thread(
305
  target=_scraper_thread_fn,
306
+ args=(video_id, redis_key, stop_event, min_poll_s, api_key),
307
  daemon=True,
308
  name=f"scraper-{slot_idx}",
309
  )