You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RedditVideoMakerBot/utils/check_token.py

199 lines
6.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
Preflight Access-Token Checker — chạy trước khi pipeline bắt đầu.
Kiểm tra:
1. access_token có được cấu hình trong config.toml không.
2. Token có hợp lệ trên Threads API (/me endpoint) không.
3. Nếu token hết hạn → tự động thử refresh.
4. user_id trong config khớp với user sở hữu token không.
Usage:
# Gọi trực tiếp:
python -m utils.check_token
# Hoặc import trong code:
from utils.check_token import preflight_check
preflight_check() # raises SystemExit on failure
"""
import sys
from typing import Optional
import requests
from utils import settings
from utils.console import print_step, print_substep
THREADS_API_BASE = "https://graph.threads.net/v1.0"
_REQUEST_TIMEOUT = 15 # seconds preflight should be fast
class TokenCheckError(Exception):
"""Raised when the access-token preflight fails."""
def _call_me_endpoint(access_token: str) -> dict:
"""GET /me?fields=id,username&access_token=… with minimal retry."""
url = f"{THREADS_API_BASE}/me"
params = {
"fields": "id,username",
"access_token": access_token,
}
response = requests.get(url, params=params, timeout=_REQUEST_TIMEOUT)
# HTTP-level errors
if response.status_code == 401:
raise TokenCheckError(
"Access token không hợp lệ hoặc đã hết hạn (HTTP 401).\n"
"→ Cập nhật [threads.creds] access_token trong config.toml."
)
if response.status_code == 403:
raise TokenCheckError(
"Token thiếu quyền (HTTP 403).\n"
"→ Đảm bảo token có quyền threads_basic_read trong Meta Developer Portal."
)
response.raise_for_status()
data = response.json()
# Graph API may return 200 with an error body
if "error" in data:
err = data["error"]
msg = err.get("message", "Unknown error")
code = err.get("code", 0)
raise TokenCheckError(f"Threads API trả về lỗi: {msg} (code={code})")
return data
def _try_refresh(access_token: str) -> Optional[str]:
"""Attempt to refresh a long-lived Threads token.
Returns new token string, or None if refresh is not possible.
"""
url = f"{THREADS_API_BASE}/refresh_access_token"
try:
resp = requests.get(
url,
params={
"grant_type": "th_refresh_token",
"access_token": access_token,
},
timeout=_REQUEST_TIMEOUT,
)
resp.raise_for_status()
data = resp.json()
if "error" in data:
return None
return data.get("access_token") or None
except requests.RequestException:
return None
def preflight_check() -> None:
"""Validate the Threads access token configured in *config.toml*.
On success, prints a confirmation and returns normally.
On failure, prints actionable diagnostics and raises ``SystemExit(1)``.
"""
print_step("🔑 Kiểm tra access token trước khi chạy...")
# --- 1. Check config values exist -----------------------------------
try:
threads_creds = settings.config["threads"]["creds"]
access_token: str = threads_creds.get("access_token", "").strip()
user_id: str = threads_creds.get("user_id", "").strip()
except (KeyError, TypeError):
print_substep(
"❌ Thiếu cấu hình [threads.creds] trong config.toml.\n"
" Cần có access_token và user_id.",
style="bold red",
)
sys.exit(1)
if not access_token:
print_substep(
"❌ access_token trống trong config.toml!\n"
" Lấy token tại: https://developers.facebook.com/docs/threads/get-started",
style="bold red",
)
sys.exit(1)
if not user_id:
print_substep(
"❌ user_id trống trong config.toml!\n"
" Lấy user_id bằng cách gọi /me với access token.",
style="bold red",
)
sys.exit(1)
# --- 2. Validate token via /me endpoint -----------------------------
try:
me_data = _call_me_endpoint(access_token)
except TokenCheckError as exc:
# Token invalid → try refresh
print_substep(
f"⚠️ Token hiện tại không hợp lệ: {exc}\n" " Đang thử refresh token...",
style="bold yellow",
)
new_token = _try_refresh(access_token)
if new_token:
try:
me_data = _call_me_endpoint(new_token)
access_token = new_token
# Update in-memory config so downstream code uses the new token
settings.config["threads"]["creds"]["access_token"] = new_token
print_substep("✅ Token đã được refresh thành công!", style="bold green")
except TokenCheckError as inner:
print_substep(
f"❌ Token mới sau refresh vẫn lỗi: {inner}\n"
" Vui lòng lấy token mới từ Meta Developer Portal:\n"
" https://developers.facebook.com/docs/threads/get-started",
style="bold red",
)
sys.exit(1)
else:
print_substep(
"❌ Không thể refresh token.\n"
" Vui lòng lấy token mới từ Meta Developer Portal:\n"
" https://developers.facebook.com/docs/threads/get-started",
style="bold red",
)
sys.exit(1)
except requests.RequestException as exc:
print_substep(
f"❌ Lỗi kết nối khi kiểm tra token: {exc}\n" " Kiểm tra kết nối mạng và thử lại.",
style="bold red",
)
sys.exit(1)
# --- 3. Cross-check user_id ----------------------------------------
api_user_id = me_data.get("id", "")
api_username = me_data.get("username", "N/A")
if api_user_id and api_user_id != user_id:
print_substep(
f"⚠️ user_id trong config ({user_id}) khác với user sở hữu token ({api_user_id}).\n"
" Nếu bạn muốn lấy threads của chính mình, hãy cập nhật user_id trong config.toml.\n"
" Đang tiếp tục với token hiện tại...",
style="bold yellow",
)
print_substep(
f"✅ Access token hợp lệ — @{api_username} (ID: {api_user_id})",
style="bold green",
)
# Allow running standalone: python -m utils.check_token
if __name__ == "__main__":
from pathlib import Path
directory = Path().absolute()
settings.check_toml(
f"{directory}/utils/.config.template.toml",
f"{directory}/config.toml",
)
preflight_check()
print_step("🎉 Tất cả kiểm tra đều OK — sẵn sàng chạy!")