Merge pull request #2 from thaitien280401-stack/copilot/fix-no-threads-error
Preflight access token validation before pipeline executionpull/2482/head
commit
36b702eceb
@ -0,0 +1,198 @@
|
||||
"""
|
||||
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_SECONDS = 15 # 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_SECONDS)
|
||||
|
||||
# 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_SECONDS,
|
||||
)
|
||||
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!")
|
||||
Loading…
Reference in new issue