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
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 |
-
|
|
|
|
|
|
|
| 215 |
logger.info("YOUTUBE_API_KEY present: %s (length=%d)", bool(api_key), len(api_key))
|
| 216 |
if not api_key:
|
| 217 |
-
msg = "
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
)
|