Compare commits
No commits in common. "9df9942704e750334467e214eb6f4bff2b77d0aa" and "2f1a8c4e0c4aa4482d9b557e9cd70cbeecd320d0" have entirely different histories.
9df9942704
...
2f1a8c4e0c
24 changed files with 4372 additions and 5689 deletions
78
Dockerfile
78
Dockerfile
|
|
@ -1,39 +1,77 @@
|
||||||
# Build Stage for Frontend
|
# Build stage for frontend
|
||||||
FROM node:18-alpine as frontend-build
|
FROM node:20-alpine AS frontend-builder
|
||||||
WORKDIR /app/frontend
|
WORKDIR /app/frontend
|
||||||
COPY frontend/package*.json ./
|
COPY frontend/package*.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Runtime Stage for Backend
|
# Production stage
|
||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
# Install system dependencies required for Playwright and compiled extensions
|
# Install system dependencies (minimal - no VNC needed)
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
wget \
|
||||||
curl \
|
curl \
|
||||||
git \
|
gnupg \
|
||||||
build-essential \
|
ca-certificates \
|
||||||
|
ffmpeg \
|
||||||
|
# Playwright dependencies
|
||||||
|
libnss3 \
|
||||||
|
libnspr4 \
|
||||||
|
libatk1.0-0 \
|
||||||
|
libatk-bridge2.0-0 \
|
||||||
|
libcups2 \
|
||||||
|
libdrm2 \
|
||||||
|
libxkbcommon0 \
|
||||||
|
libxcomposite1 \
|
||||||
|
libxdamage1 \
|
||||||
|
libxfixes3 \
|
||||||
|
libxrandr2 \
|
||||||
|
libgbm1 \
|
||||||
|
libasound2 \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libcairo2 \
|
||||||
|
libatspi2.0-0 \
|
||||||
|
libgtk-3-0 \
|
||||||
|
fonts-liberation \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install Python dependencies
|
# Copy backend requirements and install
|
||||||
COPY backend/requirements.txt backend/
|
COPY backend/requirements.txt ./
|
||||||
RUN pip install --no-cache-dir -r backend/requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Install Playwright browsers (Chromium only to save space)
|
# Install Playwright browsers (headless mode only)
|
||||||
RUN playwright install chromium
|
RUN mkdir -p /root/.cache/ms-playwright && \
|
||||||
RUN playwright install-deps chromium
|
for i in 1 2 3; do \
|
||||||
|
playwright install chromium && break || \
|
||||||
|
(echo "Retry $i..." && rm -rf /root/.cache/ms-playwright/__dirlock && sleep 5); \
|
||||||
|
done
|
||||||
|
|
||||||
# Copy Backend Code
|
# Copy backend code
|
||||||
COPY backend/ backend/
|
COPY backend/ ./backend/
|
||||||
|
|
||||||
# Copy Built Frontend Assets
|
# Copy built frontend
|
||||||
COPY --from=frontend-build /app/frontend/dist /app/frontend/dist
|
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
|
||||||
|
|
||||||
# Expose Port
|
# Create cache and session directories
|
||||||
|
RUN mkdir -p /app/cache /app/session && chmod 777 /app/cache /app/session
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV CACHE_DIR=/app/cache
|
||||||
|
|
||||||
|
# Set working directory to backend for correct imports
|
||||||
|
WORKDIR /app/backend
|
||||||
|
|
||||||
|
# Expose port (8002 = app)
|
||||||
EXPOSE 8002
|
EXPOSE 8002
|
||||||
|
|
||||||
# Run Application
|
# Health check
|
||||||
CMD ["python", "backend/main.py"]
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:8002/health || exit 1
|
||||||
|
|
||||||
|
# Start FastAPI directly (no supervisor needed)
|
||||||
|
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8002"]
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ docker run -d \
|
||||||
--shm-size=2g \
|
--shm-size=2g \
|
||||||
-v purestream_cache:/app/cache \
|
-v purestream_cache:/app/cache \
|
||||||
-v purestream_session:/app/backend/session \
|
-v purestream_session:/app/backend/session \
|
||||||
git.khoavo.myds.me/vndangkhoa/kv-tiktok:latest
|
vndangkhoa/purestream:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Option 3: Development Setup
|
### Option 3: Development Setup
|
||||||
|
|
@ -79,8 +79,7 @@ npm run dev
|
||||||
### Using Container Manager (Docker)
|
### Using Container Manager (Docker)
|
||||||
|
|
||||||
1. **Open Container Manager** → **Registry**
|
1. **Open Container Manager** → **Registry**
|
||||||
1. **Open Container Manager** → **Registry**
|
2. Search for `vndangkhoa/purestream` and download the `latest` tag
|
||||||
2. Search for `git.khoavo.myds.me/vndangkhoa/kv-tiktok` and download the `latest` tag
|
|
||||||
3. Go to **Container** → **Create**
|
3. Go to **Container** → **Create**
|
||||||
4. Configure:
|
4. Configure:
|
||||||
- **Port Settings**: Local `8002` → Container `8002`
|
- **Port Settings**: Local `8002` → Container `8002`
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,8 @@ class BrowserLoginResponse(BaseModel):
|
||||||
cookie_count: int = 0
|
cookie_count: int = 0
|
||||||
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
class CredentialsRequest(BaseModel):
|
class CredentialsRequest(BaseModel):
|
||||||
credentials: Any # Accept both dict and list
|
credentials: dict # JSON credentials in http.headers format
|
||||||
|
|
||||||
|
|
||||||
class CredentialLoginRequest(BaseModel):
|
class CredentialLoginRequest(BaseModel):
|
||||||
|
|
@ -81,8 +79,9 @@ async def save_credentials(request: CredentialsRequest):
|
||||||
if not cookies:
|
if not cookies:
|
||||||
raise HTTPException(status_code=400, detail="No cookies found in credentials")
|
raise HTTPException(status_code=400, detail="No cookies found in credentials")
|
||||||
|
|
||||||
# Save full cookie list with domains/paths preserved
|
# Convert to dict format for storage
|
||||||
PlaywrightManager.save_credentials(cookies, user_agent)
|
cookie_dict = {c["name"]: c["value"] for c in cookies}
|
||||||
|
PlaywrightManager.save_credentials(cookie_dict, user_agent)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
|
|
@ -100,19 +99,10 @@ async def auth_status():
|
||||||
try:
|
try:
|
||||||
with open(COOKIES_FILE, "r") as f:
|
with open(COOKIES_FILE, "r") as f:
|
||||||
cookies = json.load(f)
|
cookies = json.load(f)
|
||||||
# Handle both dict and list formats
|
has_session = "sessionid" in cookies
|
||||||
if isinstance(cookies, dict):
|
|
||||||
has_session = "sessionid" in cookies
|
|
||||||
cookie_count = len(cookies)
|
|
||||||
elif isinstance(cookies, list):
|
|
||||||
has_session = any(c.get("name") == "sessionid" for c in cookies if isinstance(c, dict))
|
|
||||||
cookie_count = len(cookies)
|
|
||||||
else:
|
|
||||||
has_session = False
|
|
||||||
cookie_count = 0
|
|
||||||
return {
|
return {
|
||||||
"authenticated": has_session,
|
"authenticated": has_session,
|
||||||
"cookie_count": cookie_count
|
"cookie_count": len(cookies)
|
||||||
}
|
}
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
@ -157,8 +147,7 @@ async def stop_vnc_login():
|
||||||
# ========== ADMIN ENDPOINTS ==========
|
# ========== ADMIN ENDPOINTS ==========
|
||||||
|
|
||||||
# Admin password from environment variable
|
# Admin password from environment variable
|
||||||
# Force hardcode to 'admin123' to ensure user can login, ignoring potentially bad env var
|
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "admin123")
|
||||||
ADMIN_PASSWORD = "admin123"
|
|
||||||
|
|
||||||
# Simple in-memory admin sessions (resets on restart, that's fine for this use case)
|
# Simple in-memory admin sessions (resets on restart, that's fine for this use case)
|
||||||
_admin_sessions: set = set()
|
_admin_sessions: set = set()
|
||||||
|
|
@ -175,7 +164,6 @@ class AdminCookiesRequest(BaseModel):
|
||||||
@router.post("/admin-login")
|
@router.post("/admin-login")
|
||||||
async def admin_login(request: AdminLoginRequest):
|
async def admin_login(request: AdminLoginRequest):
|
||||||
"""Login as admin with password."""
|
"""Login as admin with password."""
|
||||||
print(f"DEBUG: Admin login attempt. Input: '{request.password}', Expected: '{ADMIN_PASSWORD}'")
|
|
||||||
if request.password == ADMIN_PASSWORD:
|
if request.password == ADMIN_PASSWORD:
|
||||||
import secrets
|
import secrets
|
||||||
session_token = secrets.token_urlsafe(32)
|
session_token = secrets.token_urlsafe(32)
|
||||||
|
|
@ -199,30 +187,22 @@ async def admin_update_cookies(request: AdminCookiesRequest, token: str = ""):
|
||||||
try:
|
try:
|
||||||
cookies = request.cookies
|
cookies = request.cookies
|
||||||
|
|
||||||
# Preserve list if it contains metadata (like domain)
|
# Normalize cookies to dict format
|
||||||
if isinstance(cookies, list):
|
if isinstance(cookies, list):
|
||||||
# Check if this is a simple name-value list or full objects
|
# Cookie-Editor export format: [{"name": "...", "value": "..."}, ...]
|
||||||
if len(cookies) > 0 and isinstance(cookies[0], dict) and "domain" not in cookies[0]:
|
cookie_dict = {}
|
||||||
cookie_dict = {}
|
for c in cookies:
|
||||||
for c in cookies:
|
if isinstance(c, dict) and "name" in c and "value" in c:
|
||||||
if isinstance(c, dict) and "name" in c and "value" in c:
|
cookie_dict[c["name"]] = c["value"]
|
||||||
cookie_dict[c["name"]] = c["value"]
|
cookies = cookie_dict
|
||||||
cookies = cookie_dict
|
|
||||||
|
|
||||||
if not isinstance(cookies, (dict, list)):
|
if not isinstance(cookies, dict):
|
||||||
raise HTTPException(status_code=400, detail="Invalid cookies format")
|
raise HTTPException(status_code=400, detail="Invalid cookies format")
|
||||||
|
|
||||||
# Check for sessionid in both formats
|
if "sessionid" not in cookies:
|
||||||
has_session = False
|
|
||||||
if isinstance(cookies, dict):
|
|
||||||
has_session = "sessionid" in cookies
|
|
||||||
else:
|
|
||||||
has_session = any(c.get("name") == "sessionid" for c in cookies if isinstance(c, dict))
|
|
||||||
|
|
||||||
if not has_session:
|
|
||||||
raise HTTPException(status_code=400, detail="Missing 'sessionid' cookie - this is required")
|
raise HTTPException(status_code=400, detail="Missing 'sessionid' cookie - this is required")
|
||||||
|
|
||||||
# Save cookies (either dict or list)
|
# Save cookies
|
||||||
PlaywrightManager.save_credentials(cookies, None)
|
PlaywrightManager.save_credentials(cookies, None)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -132,11 +132,9 @@ init_cache()
|
||||||
|
|
||||||
# ========== API ROUTES ==========
|
# ========== API ROUTES ==========
|
||||||
|
|
||||||
from typing import Optional, Any, Union, List, Dict
|
|
||||||
|
|
||||||
class FeedRequest(BaseModel):
|
class FeedRequest(BaseModel):
|
||||||
"""Request body for feed endpoint with optional JSON credentials."""
|
"""Request body for feed endpoint with optional JSON credentials."""
|
||||||
credentials: Optional[Union[Dict, List]] = None
|
credentials: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
@router.post("")
|
@router.post("")
|
||||||
|
|
@ -158,21 +156,11 @@ async def get_feed(request: FeedRequest = None):
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
async def get_feed_simple(fast: bool = False, skip_cache: bool = False):
|
async def get_feed_simple(fast: bool = False):
|
||||||
"""Simple GET endpoint to fetch feed using stored credentials.
|
"""Simple GET endpoint to fetch feed using stored credentials."""
|
||||||
|
|
||||||
Args:
|
|
||||||
fast: If True, only get initial batch (0 scrolls). If False, scroll 5 times.
|
|
||||||
skip_cache: If True, always fetch fresh videos (for infinite scroll).
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# Fast mode = 0 scrolls (just initial batch), Normal = 5 scrolls
|
# Fast mode = 0 scrolls (just initial batch), Normal = 5 scrolls
|
||||||
scroll_count = 0 if fast else 5
|
scroll_count = 0 if fast else 5
|
||||||
|
|
||||||
# When skipping cache for infinite scroll, do more scrolling to get different videos
|
|
||||||
if skip_cache:
|
|
||||||
scroll_count = 8 # More scrolling to get fresh content
|
|
||||||
|
|
||||||
videos = await PlaywrightManager.intercept_feed(scroll_count=scroll_count)
|
videos = await PlaywrightManager.intercept_feed(scroll_count=scroll_count)
|
||||||
return videos
|
return videos
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -63,36 +63,3 @@ async def remove_following(username: str):
|
||||||
save_following(following)
|
save_following(following)
|
||||||
|
|
||||||
return {"status": "success", "following": following}
|
return {"status": "success", "following": following}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/feed")
|
|
||||||
async def get_following_feed(limit_per_user: int = 5):
|
|
||||||
"""
|
|
||||||
Get a combined feed of videos from all followed creators.
|
|
||||||
"""
|
|
||||||
from core.playwright_manager import PlaywrightManager
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
following = load_following()
|
|
||||||
if not following:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Load stored credentials
|
|
||||||
cookies, user_agent = PlaywrightManager.load_stored_credentials()
|
|
||||||
|
|
||||||
if not cookies:
|
|
||||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
||||||
|
|
||||||
tasks = [PlaywrightManager.fetch_user_videos(user, cookies, user_agent, limit_per_user) for user in following]
|
|
||||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
||||||
|
|
||||||
all_videos = []
|
|
||||||
for result in results:
|
|
||||||
if isinstance(result, list):
|
|
||||||
all_videos.extend(result)
|
|
||||||
|
|
||||||
# Shuffle results to make it look like a feed
|
|
||||||
import random
|
|
||||||
random.shuffle(all_videos)
|
|
||||||
|
|
||||||
return all_videos
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ async def get_multiple_profiles(usernames: str = Query(..., description="Comma-s
|
||||||
@router.get("/videos")
|
@router.get("/videos")
|
||||||
async def get_user_videos(
|
async def get_user_videos(
|
||||||
username: str = Query(..., description="TikTok username (without @)"),
|
username: str = Query(..., description="TikTok username (without @)"),
|
||||||
limit: int = Query(10, description="Max videos to fetch", ge=1, le=60)
|
limit: int = Query(10, description="Max videos to fetch", ge=1, le=30)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Fetch videos from a TikTok user's profile.
|
Fetch videos from a TikTok user's profile.
|
||||||
|
|
@ -135,8 +135,7 @@ async def get_user_videos(
|
||||||
@router.get("/search")
|
@router.get("/search")
|
||||||
async def search_videos(
|
async def search_videos(
|
||||||
query: str = Query(..., description="Search keyword or hashtag"),
|
query: str = Query(..., description="Search keyword or hashtag"),
|
||||||
limit: int = Query(20, description="Max videos to fetch", ge=1, le=60),
|
limit: int = Query(12, description="Max videos to fetch", ge=1, le=30)
|
||||||
cursor: int = Query(0, description="Pagination cursor (offset)")
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Search for videos by keyword or hashtag.
|
Search for videos by keyword or hashtag.
|
||||||
|
|
@ -148,11 +147,11 @@ async def search_videos(
|
||||||
if not cookies:
|
if not cookies:
|
||||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||||
|
|
||||||
print(f"Searching for: {query} (limit={limit}, cursor={cursor})...")
|
print(f"Searching for: {query}...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
videos = await PlaywrightManager.search_videos(query, cookies, user_agent, limit, cursor)
|
videos = await PlaywrightManager.search_videos(query, cookies, user_agent, limit)
|
||||||
return {"query": query, "videos": videos, "count": len(videos), "cursor": cursor + len(videos)}
|
return {"query": query, "videos": videos, "count": len(videos)}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error searching for {query}: {e}")
|
print(f"Error searching for {query}: {e}")
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,10 @@ Uses Playwright to:
|
||||||
3. Intercept /item_list API responses (instead of scraping HTML)
|
3. Intercept /item_list API responses (instead of scraping HTML)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import json
|
||||||
from typing import List, Dict, Optional, Any
|
import os
|
||||||
|
from typing import List, Dict, Optional
|
||||||
from playwright.async_api import async_playwright, Response, Browser, BrowserContext
|
from playwright.async_api import async_playwright, Response, Browser, BrowserContext
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -45,9 +44,6 @@ class PlaywrightManager:
|
||||||
|
|
||||||
DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
# Use installed Chrome instead of Playwright's Chromium (avoids slow download)
|
|
||||||
CHROME_PATH = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
||||||
|
|
||||||
# VNC login state (class-level to persist across requests)
|
# VNC login state (class-level to persist across requests)
|
||||||
_vnc_playwright = None
|
_vnc_playwright = None
|
||||||
_vnc_browser = None
|
_vnc_browser = None
|
||||||
|
|
@ -56,70 +52,51 @@ class PlaywrightManager:
|
||||||
_vnc_active = False
|
_vnc_active = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_json_credentials(json_creds: Any) -> tuple[List[dict], str]:
|
def parse_json_credentials(json_creds: dict) -> tuple[List[dict], str]:
|
||||||
"""
|
"""
|
||||||
Parse JSON credentials. Supports:
|
Parse JSON credentials in the format:
|
||||||
1. Array format: [{"name": "...", "value": "..."}, ...]
|
{
|
||||||
2. http object format: {"http": {"headers": {...}, "cookies": {...}}}
|
"http": {
|
||||||
|
"headers": {"User-Agent": "...", "Cookie": "..."},
|
||||||
|
"cookies": {"sessionid": "...", "ttwid": "..."}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Returns: (cookies_list, user_agent)
|
Returns: (cookies_list, user_agent)
|
||||||
"""
|
"""
|
||||||
cookies = []
|
cookies = []
|
||||||
user_agent = PlaywrightManager.DEFAULT_USER_AGENT
|
user_agent = PlaywrightManager.DEFAULT_USER_AGENT
|
||||||
|
|
||||||
# Handle array format (Cookie-Editor)
|
http_data = json_creds.get("http", {})
|
||||||
if isinstance(json_creds, list):
|
headers = http_data.get("headers", {})
|
||||||
for c in json_creds:
|
cookies_dict = http_data.get("cookies", {})
|
||||||
if isinstance(c, dict) and "name" in c and "value" in c:
|
|
||||||
cookie = {
|
|
||||||
"name": c["name"],
|
|
||||||
"value": str(c["value"]),
|
|
||||||
"domain": c.get("domain") or ".tiktok.com",
|
|
||||||
"path": c.get("path") or "/",
|
|
||||||
"secure": c.get("secure", True),
|
|
||||||
"httpOnly": c.get("httpOnly", False)
|
|
||||||
}
|
|
||||||
if "sameSite" in c and c["sameSite"]:
|
|
||||||
# Playwright expects "Strict", "Lax", or "None"
|
|
||||||
ss = str(c["sameSite"]).capitalize()
|
|
||||||
if ss in ["Strict", "Lax", "None"]:
|
|
||||||
cookie["sameSite"] = ss
|
|
||||||
|
|
||||||
cookies.append(cookie)
|
# Get User-Agent from headers
|
||||||
return cookies, user_agent
|
if "User-Agent" in headers:
|
||||||
|
user_agent = headers["User-Agent"]
|
||||||
|
|
||||||
# Handle object format
|
# Parse cookies from the cookies dict (preferred)
|
||||||
if isinstance(json_creds, dict):
|
if cookies_dict:
|
||||||
http_data = json_creds.get("http", {})
|
for name, value in cookies_dict.items():
|
||||||
headers = http_data.get("headers", {})
|
cookies.append({
|
||||||
cookies_dict = http_data.get("cookies", {})
|
"name": name,
|
||||||
|
"value": str(value),
|
||||||
# Get User-Agent from headers
|
"domain": ".tiktok.com",
|
||||||
if "User-Agent" in headers:
|
"path": "/"
|
||||||
user_agent = headers["User-Agent"]
|
})
|
||||||
|
# Fallback: parse from Cookie header string
|
||||||
# Parse cookies from the cookies dict (preferred)
|
elif "Cookie" in headers:
|
||||||
if cookies_dict:
|
cookie_str = headers["Cookie"]
|
||||||
for name, value in cookies_dict.items():
|
for part in cookie_str.split(";"):
|
||||||
|
part = part.strip()
|
||||||
|
if "=" in part:
|
||||||
|
name, value = part.split("=", 1)
|
||||||
cookies.append({
|
cookies.append({
|
||||||
"name": name,
|
"name": name.strip(),
|
||||||
"value": str(value),
|
"value": value.strip(),
|
||||||
"domain": ".tiktok.com",
|
"domain": ".tiktok.com",
|
||||||
"path": "/"
|
"path": "/"
|
||||||
})
|
})
|
||||||
# Fallback: parse from Cookie header string
|
|
||||||
elif "Cookie" in headers:
|
|
||||||
cookie_str = headers["Cookie"]
|
|
||||||
for part in cookie_str.split(";"):
|
|
||||||
part = part.strip()
|
|
||||||
if "=" in part:
|
|
||||||
name, value = part.split("=", 1)
|
|
||||||
cookies.append({
|
|
||||||
"name": name.strip(),
|
|
||||||
"value": value.strip(),
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"path": "/"
|
|
||||||
})
|
|
||||||
|
|
||||||
return cookies, user_agent
|
return cookies, user_agent
|
||||||
|
|
||||||
|
|
@ -132,38 +109,14 @@ class PlaywrightManager:
|
||||||
if os.path.exists(COOKIES_FILE):
|
if os.path.exists(COOKIES_FILE):
|
||||||
try:
|
try:
|
||||||
with open(COOKIES_FILE, "r") as f:
|
with open(COOKIES_FILE, "r") as f:
|
||||||
data = json.load(f)
|
cookie_dict = json.load(f)
|
||||||
if isinstance(data, list):
|
for name, value in cookie_dict.items():
|
||||||
# Sanitize each cookie for Playwright compatibility
|
cookies.append({
|
||||||
for c in data:
|
"name": name,
|
||||||
if isinstance(c, dict) and "name" in c and "value" in c:
|
"value": str(value),
|
||||||
cookie = {
|
"domain": ".tiktok.com",
|
||||||
"name": c["name"],
|
"path": "/"
|
||||||
"value": str(c["value"]),
|
})
|
||||||
"domain": c.get("domain") or ".tiktok.com",
|
|
||||||
"path": c.get("path") or "/",
|
|
||||||
}
|
|
||||||
# Only add optional fields if they have valid values
|
|
||||||
if c.get("secure") is not None:
|
|
||||||
cookie["secure"] = bool(c["secure"])
|
|
||||||
if c.get("httpOnly") is not None:
|
|
||||||
cookie["httpOnly"] = bool(c["httpOnly"])
|
|
||||||
# Sanitize sameSite - Playwright only accepts Strict|Lax|None
|
|
||||||
if c.get("sameSite"):
|
|
||||||
ss = str(c["sameSite"]).capitalize()
|
|
||||||
if ss in ["Strict", "Lax", "None"]:
|
|
||||||
cookie["sameSite"] = ss
|
|
||||||
# If invalid, just omit it
|
|
||||||
cookies.append(cookie)
|
|
||||||
elif isinstance(data, dict):
|
|
||||||
# Backward compatibility or simple dict format
|
|
||||||
for name, value in data.items():
|
|
||||||
cookies.append({
|
|
||||||
"name": name,
|
|
||||||
"value": str(value),
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"path": "/"
|
|
||||||
})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading cookies: {e}")
|
print(f"Error loading cookies: {e}")
|
||||||
|
|
||||||
|
|
@ -178,14 +131,13 @@ class PlaywrightManager:
|
||||||
return cookies, user_agent
|
return cookies, user_agent
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_credentials(cookies: List[dict] | dict, user_agent: str = None):
|
def save_credentials(cookies: dict, user_agent: str):
|
||||||
"""Save cookies and user agent to files."""
|
"""Save cookies and user agent to files."""
|
||||||
with open(COOKIES_FILE, "w") as f:
|
with open(COOKIES_FILE, "w") as f:
|
||||||
json.dump(cookies, f, indent=2)
|
json.dump(cookies, f, indent=2)
|
||||||
|
|
||||||
if user_agent:
|
with open(USER_AGENT_FILE, "w") as f:
|
||||||
with open(USER_AGENT_FILE, "w") as f:
|
json.dump({"user_agent": user_agent}, f)
|
||||||
json.dump({"user_agent": user_agent}, f)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def start_vnc_login(cls) -> dict:
|
async def start_vnc_login(cls) -> dict:
|
||||||
|
|
@ -480,16 +432,16 @@ class PlaywrightManager:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def intercept_feed(cookies: List[dict] = None, user_agent: str = None, scroll_count: int = 5) -> List[dict]:
|
async def intercept_feed(cookies: List[dict] = None, user_agent: str = None, scroll_count: int = 5) -> List[dict]:
|
||||||
"""Navigate to TikTok feed and intercept API responses."""
|
"""
|
||||||
try:
|
Navigate to TikTok For You page and intercept the /item_list API response.
|
||||||
return await PlaywrightManager._intercept_feed_impl(cookies, user_agent, scroll_count)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"DEBUG: Error in intercept_feed: {e}")
|
|
||||||
print(traceback.format_exc())
|
|
||||||
raise e
|
|
||||||
|
|
||||||
@staticmethod
|
Args:
|
||||||
async def _intercept_feed_impl(cookies: List[dict] = None, user_agent: str = None, scroll_count: int = 5) -> List[dict]:
|
cookies: Optional list of cookies
|
||||||
|
user_agent: Optional user agent
|
||||||
|
scroll_count: Number of times to scroll to fetch more videos (0 = initial load only)
|
||||||
|
|
||||||
|
Returns: List of video objects
|
||||||
|
"""
|
||||||
if not cookies:
|
if not cookies:
|
||||||
cookies, user_agent = PlaywrightManager.load_stored_credentials()
|
cookies, user_agent = PlaywrightManager.load_stored_credentials()
|
||||||
|
|
||||||
|
|
@ -531,21 +483,11 @@ class PlaywrightManager:
|
||||||
async with async_playwright() as p:
|
async with async_playwright() as p:
|
||||||
browser = await p.chromium.launch(
|
browser = await p.chromium.launch(
|
||||||
headless=True,
|
headless=True,
|
||||||
executable_path=PlaywrightManager.CHROME_PATH,
|
|
||||||
args=PlaywrightManager.BROWSER_ARGS
|
args=PlaywrightManager.BROWSER_ARGS
|
||||||
)
|
)
|
||||||
|
|
||||||
context = await browser.new_context(user_agent=user_agent)
|
context = await browser.new_context(user_agent=user_agent)
|
||||||
|
await context.add_cookies(cookies)
|
||||||
if cookies:
|
|
||||||
try:
|
|
||||||
await context.add_cookies(cookies)
|
|
||||||
print(f"DEBUG: Applied {len(cookies)} cookies to browser context")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"DEBUG: Error applying cookies: {e}")
|
|
||||||
if len(cookies) > 0:
|
|
||||||
print(f"DEBUG: Sample cookie: {cookies[0]}")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
await stealth_async(page)
|
await stealth_async(page)
|
||||||
|
|
@ -594,10 +536,6 @@ class PlaywrightManager:
|
||||||
def _extract_video_data(item: dict) -> Optional[dict]:
|
def _extract_video_data(item: dict) -> Optional[dict]:
|
||||||
"""Extract video data from TikTok API item, including product/shop videos."""
|
"""Extract video data from TikTok API item, including product/shop videos."""
|
||||||
try:
|
try:
|
||||||
if not isinstance(item, dict):
|
|
||||||
print(f"DEBUG: Skipping invalid item (type: {type(item)})")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Handle different API response formats
|
# Handle different API response formats
|
||||||
video_id = item.get("id") or item.get("aweme_id")
|
video_id = item.get("id") or item.get("aweme_id")
|
||||||
|
|
||||||
|
|
@ -725,7 +663,6 @@ class PlaywrightManager:
|
||||||
async with async_playwright() as p:
|
async with async_playwright() as p:
|
||||||
browser = await p.chromium.launch(
|
browser = await p.chromium.launch(
|
||||||
headless=True,
|
headless=True,
|
||||||
executable_path=PlaywrightManager.CHROME_PATH,
|
|
||||||
args=PlaywrightManager.BROWSER_ARGS
|
args=PlaywrightManager.BROWSER_ARGS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -757,17 +694,10 @@ class PlaywrightManager:
|
||||||
return captured_videos
|
return captured_videos
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def search_videos(query: str, cookies: list, user_agent: str = None, limit: int = 20, cursor: int = 0) -> list:
|
async def search_videos(query: str, cookies: list, user_agent: str = None, limit: int = 12) -> list:
|
||||||
"""
|
"""
|
||||||
Search for videos by keyword or hashtag.
|
Search for videos by keyword or hashtag.
|
||||||
Uses Playwright to intercept TikTok search results API.
|
Uses Playwright to intercept TikTok search results API.
|
||||||
|
|
||||||
Args:
|
|
||||||
query: Search query
|
|
||||||
cookies: Auth cookies
|
|
||||||
user_agent: Browser user agent
|
|
||||||
limit: Max videos to capture in this batch
|
|
||||||
cursor: Starting offset for pagination
|
|
||||||
"""
|
"""
|
||||||
from playwright.async_api import async_playwright, Response
|
from playwright.async_api import async_playwright, Response
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
@ -779,7 +709,7 @@ class PlaywrightManager:
|
||||||
print("DEBUG: No cookies available for search")
|
print("DEBUG: No cookies available for search")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
print(f"DEBUG: Searching for '{query}' (limit={limit}, cursor={cursor})...")
|
print(f"DEBUG: Searching for '{query}'...")
|
||||||
|
|
||||||
captured_videos = []
|
captured_videos = []
|
||||||
|
|
||||||
|
|
@ -798,17 +728,13 @@ class PlaywrightManager:
|
||||||
items = data.get("itemList", []) or data.get("data", []) or data.get("item_list", [])
|
items = data.get("itemList", []) or data.get("data", []) or data.get("item_list", [])
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
# If we have enough for this specific batch, we don't need more
|
|
||||||
if len(captured_videos) >= limit:
|
if len(captured_videos) >= limit:
|
||||||
break
|
break
|
||||||
|
|
||||||
video_data = PlaywrightManager._extract_video_data(item)
|
video_data = PlaywrightManager._extract_video_data(item)
|
||||||
if video_data:
|
if video_data:
|
||||||
# Avoid duplicates within the same capture session
|
captured_videos.append(video_data)
|
||||||
if not any(v['id'] == video_data['id'] for v in captured_videos):
|
|
||||||
captured_videos.append(video_data)
|
|
||||||
|
|
||||||
print(f"DEBUG: Captured {len(items)} videos from search API (Total batch: {len(captured_videos)})")
|
print(f"DEBUG: Captured {len(items)} videos from search API")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error parsing search API response: {e}")
|
print(f"DEBUG: Error parsing search API response: {e}")
|
||||||
|
|
@ -816,7 +742,6 @@ class PlaywrightManager:
|
||||||
async with async_playwright() as p:
|
async with async_playwright() as p:
|
||||||
browser = await p.chromium.launch(
|
browser = await p.chromium.launch(
|
||||||
headless=True,
|
headless=True,
|
||||||
executable_path=PlaywrightManager.CHROME_PATH,
|
|
||||||
args=PlaywrightManager.BROWSER_ARGS
|
args=PlaywrightManager.BROWSER_ARGS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -830,40 +755,22 @@ class PlaywrightManager:
|
||||||
try:
|
try:
|
||||||
# Navigate to TikTok search page
|
# Navigate to TikTok search page
|
||||||
search_url = f"https://www.tiktok.com/search/video?q={quote(query)}"
|
search_url = f"https://www.tiktok.com/search/video?q={quote(query)}"
|
||||||
try:
|
await page.goto(search_url, wait_until="networkidle", timeout=30000)
|
||||||
await page.goto(search_url, wait_until="domcontentloaded", timeout=15000)
|
|
||||||
except:
|
|
||||||
print("DEBUG: Navigation timeout, proceeding anyway")
|
|
||||||
|
|
||||||
# Wait for initial results
|
# Wait for videos to load
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
|
|
||||||
# Scroll based on cursor to reach previous results and then capture new ones
|
# Scroll to trigger more loading
|
||||||
# Each scroll typically loads 12-20 items
|
for _ in range(2):
|
||||||
# We scroll more as the cursor increases
|
await page.evaluate("window.scrollBy(0, 800)")
|
||||||
scroll_count = (cursor // 10) + 1
|
await asyncio.sleep(1)
|
||||||
# Limit total scrolls to avoid hanging
|
|
||||||
scroll_count = min(scroll_count, 10)
|
|
||||||
|
|
||||||
for i in range(scroll_count):
|
|
||||||
await page.evaluate("window.scrollBy(0, 1500)")
|
|
||||||
await asyncio.sleep(1.5)
|
|
||||||
|
|
||||||
# After reaching the offset, scroll a bit more to trigger the specific batch capture
|
|
||||||
batch_scrolls = (limit // 10) + 2 # Add extra scrolls to be safe
|
|
||||||
for _ in range(batch_scrolls):
|
|
||||||
await page.evaluate("window.scrollBy(0, 2000)") # Larger scroll
|
|
||||||
await asyncio.sleep(1.0) # Faster scroll cadence
|
|
||||||
|
|
||||||
# Wait a bit after scrolling for all responses to settle
|
|
||||||
await asyncio.sleep(2.5)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error during search: {e}")
|
print(f"DEBUG: Error during search: {e}")
|
||||||
|
|
||||||
await browser.close()
|
await browser.close()
|
||||||
|
|
||||||
print(f"DEBUG: Total captured search videos in this batch: {len(captured_videos)}")
|
print(f"DEBUG: Total captured search videos: {len(captured_videos)}")
|
||||||
return captured_videos
|
return captured_videos
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -916,7 +823,6 @@ class PlaywrightManager:
|
||||||
async with async_playwright() as p:
|
async with async_playwright() as p:
|
||||||
browser = await p.chromium.launch(
|
browser = await p.chromium.launch(
|
||||||
headless=True,
|
headless=True,
|
||||||
executable_path=PlaywrightManager.CHROME_PATH,
|
|
||||||
args=PlaywrightManager.BROWSER_ARGS
|
args=PlaywrightManager.BROWSER_ARGS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,40 +5,16 @@ from fastapi.responses import FileResponse
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from api.routes import auth, feed, download, following, config, user
|
from api.routes import auth, feed, download, following, config, user
|
||||||
import sys
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
# Force Proactor on Windows for Playwright
|
|
||||||
if sys.platform == "win32":
|
|
||||||
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
"""Startup and shutdown events."""
|
"""Startup and shutdown events."""
|
||||||
print("🚀 Starting PureStream API (Network Interception Mode)...")
|
print("🚀 Starting PureStream API (Network Interception Mode)...")
|
||||||
import asyncio
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
print(f"DEBUG: Running event loop: {type(loop)}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"DEBUG: Could not get running loop: {e}")
|
|
||||||
yield
|
yield
|
||||||
print("👋 Shutting down PureStream API...")
|
print("👋 Shutting down PureStream API...")
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
|
|
||||||
app = FastAPI(title="PureStream API", version="2.0.0", lifespan=lifespan)
|
app = FastAPI(title="PureStream API", version="2.0.0", lifespan=lifespan)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if sys.platform == "win32":
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
print(f"DEBUG: Current event loop: {type(loop)}")
|
|
||||||
except:
|
|
||||||
print("DEBUG: No event loop yet")
|
|
||||||
|
|
||||||
|
|
||||||
# CORS middleware
|
# CORS middleware
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
|
|
@ -78,5 +54,5 @@ if FRONTEND_DIR.exists():
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run("main:app", host="0.0.0.0", port=8002, reload=False, loop="asyncio")
|
uvicorn.run("main:app", host="0.0.0.0", port=8002, reload=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
# Fix sys.path for user site-packages where pip installed dependencies
|
|
||||||
user_site = os.path.expanduser("~\\AppData\\Roaming\\Python\\Python312\\site-packages")
|
|
||||||
if os.path.exists(user_site) and user_site not in sys.path:
|
|
||||||
print(f"DEBUG: Adding user site-packages to path: {user_site}")
|
|
||||||
sys.path.append(user_site)
|
|
||||||
|
|
||||||
# Enforce ProactorEventLoopPolicy for Playwright on Windows
|
|
||||||
# This is required for asyncio.create_subprocess_exec used by Playwright
|
|
||||||
if sys.platform == "win32":
|
|
||||||
# Check if policy is already set
|
|
||||||
current_policy = asyncio.get_event_loop_policy()
|
|
||||||
if not isinstance(current_policy, asyncio.WindowsProactorEventLoopPolicy):
|
|
||||||
print("DEBUG: Setting WindowsProactorEventLoopPolicy")
|
|
||||||
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
||||||
else:
|
|
||||||
print("DEBUG: WindowsProactorEventLoopPolicy already active")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import uvicorn
|
|
||||||
print("🚀 Bootstrapping Uvicorn with Proactor Loop (Reload Disabled)...")
|
|
||||||
uvicorn.run("main:app", host="0.0.0.0", port=8002, reload=False, loop="asyncio")
|
|
||||||
312
cookies.json
312
cookies.json
|
|
@ -1,312 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1784039026,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "delay_guest_mode_vid",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1768142590.076948,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "msToken",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "qKUFutg-Q184Mo5kpuAm4XvPCCucGe3O2KXf5G9pHV61Hb9puK-ZVQ7XzexuVGLLzwmFZ1mVYOgR3QbKBXk58AX9UgPPPkWk_koDZF3e-gqQGg_9GGjcdIOxGN-JTL_g0FM4qN8NKV84LdU="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646103,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "tt_session_tlb_tag",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "sttt%7C1%7C6M35zM57kkqAGSs_LSUdRP_________zyplxYaEARSr2PNU_6cKcB0lq4WRz1GKKY43u399i5hs%3D"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1798369620.645922,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sid_guard",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44%7C1767265616%7C15552000%7CTue%2C+30-Jun-2026+11%3A06%3A56+GMT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1798814589.722864,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "ttwid",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "1%7CAYDsetgnxt5vzYX8hD6Wq2DQ4FXiL_pqcdLwHWwz6B8%7C1767278585%7C46a4b0b8d2fc0ee903d0e781593a0d2d7b491a49752c86f034adf69319d371a0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1767883392,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "perf_feed_cache",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "{%22expireTimestamp%22:0%2C%22itemIds%22:[%22%22%2C%227584357225863335188%22%2C%227580369401002659079%22]}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.645952,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "uid_tt",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "44deb1e89d254f610eefd18c39ec97fa708e9c0f22c0207f7140c6ffd6c81b2c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449601.742227,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "passport_csrf_token_default",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "9c66ab10306611c75fa19c87e54fd31b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "s_v_web_id",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": true,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "verify_mjvcbi31_l3mxUEeU_ykis_4x6z_859S_8zEFmNseEnJU"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.64617,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "ssid_ucp_v1",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "1.0.1-KDlhMTg2NTQ1MmJiZmNlZjgzYzNiZGU4ZjAyNzk1NWRkNTlkOTYxNjIKIQiBiIHG4PKvxV8Q0KrZygYYswsgDDC50ZfDBjgIQBJIBBADGgJteSIgZThjZGY5Y2NjZTdiOTI0YTgwMTkyYjNmMmQyNTFkNDQyTgog40q2JTBb3lGgiNKowpX3zbxplmW4zO3AUFhAo6LMB-wSIDpAp_OQ2Q5qEBZvL59v7fgLmw27UIxLQHoimzDg3U5BGAIiBnRpa3Rvaw"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1793198587,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "tiktok_webapp_theme",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "dark"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1799409936.219767,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "_ttp",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "32XOXKxwj8YLtBQf0OBn4TvlkPN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449620.645821,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "cmpl_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "AgQYAPOF_hfkTtKPtFExgPKdOPKrXVkNUj-FDmCi6K4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449620.645628,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "multi_sids",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "6884525631502042113%3Ae8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1769857620.645892,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "passport_auth_status_ss",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "966972581a398dbb9ead189c044cf98c%2C"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449601.742082,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "passport_csrf_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "9c66ab10306611c75fa19c87e54fd31b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646041,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sessionid",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646073,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sessionid_ss",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646008,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sid_tt",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646132,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sid_ucp_v1",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "1.0.1-KDlhMTg2NTQ1MmJiZmNlZjgzYzNiZGU4ZjAyNzk1NWRkNTlkOTYxNjIKIQiBiIHG4PKvxV8Q0KrZygYYswsgDDC50ZfDBjgIQBJIBBADGgJteSIgZThjZGY5Y2NjZTdiOTI0YTgwMTkyYjNmMmQyNTFkNDQyTgog40q2JTBb3lGgiNKowpX3zbxplmW4zO3AUFhAo6LMB-wSIDpAp_OQ2Q5qEBZvL59v7fgLmw27UIxLQHoimzDg3U5BGAIiBnRpa3Rvaw"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1793198587,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "tiktok_webapp_theme_source",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "auto"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782830586.65306,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "tt_chain_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "6deMEWrkAGUe9R0tCISIoQ=="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "tt_csrf_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "lax",
|
|
||||||
"secure": true,
|
|
||||||
"session": true,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "q0Q4ki72-I7zQB6eLbpBBaqFBGrUF_v85N9s"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.645979,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "uid_tt_ss",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "44deb1e89d254f610eefd18c39ec97fa708e9c0f22c0207f7140c6ffd6c81b2c"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
360
frontend/package-lock.json
generated
360
frontend/package-lock.json
generated
|
|
@ -81,6 +81,7 @@
|
||||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.5",
|
"@babel/generator": "^7.28.5",
|
||||||
|
|
@ -724,9 +725,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.9.1",
|
"version": "4.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
|
||||||
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
|
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1037,9 +1038,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
|
||||||
"integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==",
|
"integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1051,9 +1052,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==",
|
"integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1065,9 +1066,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==",
|
"integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1079,9 +1080,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz",
|
||||||
"integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==",
|
"integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1093,9 +1094,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==",
|
"integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1107,9 +1108,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz",
|
||||||
"integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==",
|
"integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1121,9 +1122,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz",
|
||||||
"integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==",
|
"integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1135,9 +1136,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz",
|
||||||
"integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==",
|
"integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -1149,9 +1150,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==",
|
"integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1163,9 +1164,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz",
|
||||||
"integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==",
|
"integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1177,9 +1178,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==",
|
"integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -1191,9 +1192,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==",
|
"integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -1205,9 +1206,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==",
|
"integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -1219,9 +1220,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz",
|
||||||
"integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==",
|
"integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -1233,9 +1234,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==",
|
"integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -1247,9 +1248,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==",
|
"integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1261,9 +1262,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz",
|
||||||
"integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==",
|
"integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1275,9 +1276,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz",
|
||||||
"integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==",
|
"integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1289,9 +1290,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz",
|
||||||
"integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==",
|
"integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1303,9 +1304,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz",
|
||||||
"integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==",
|
"integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -1317,9 +1318,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
|
||||||
"integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==",
|
"integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1331,9 +1332,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
|
||||||
"integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==",
|
"integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1409,6 +1410,7 @@
|
||||||
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
|
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
|
|
@ -1426,6 +1428,7 @@
|
||||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
|
|
@ -1442,20 +1445,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
|
||||||
"integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
|
"integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.51.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/type-utils": "8.51.0",
|
"@typescript-eslint/type-utils": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.51.0",
|
"@typescript-eslint/utils": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.51.0",
|
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||||
"ignore": "^7.0.0",
|
"ignore": "^7.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"ts-api-utils": "^2.2.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -1465,7 +1468,7 @@
|
||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@typescript-eslint/parser": "^8.51.0",
|
"@typescript-eslint/parser": "^8.50.0",
|
||||||
"eslint": "^8.57.0 || ^9.0.0",
|
"eslint": "^8.57.0 || ^9.0.0",
|
||||||
"typescript": ">=4.8.4 <6.0.0"
|
"typescript": ">=4.8.4 <6.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -1481,16 +1484,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
|
||||||
"integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
|
"integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.51.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.51.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.51.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.51.0",
|
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1506,14 +1510,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/project-service": {
|
"node_modules/@typescript-eslint/project-service": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
|
||||||
"integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
|
"integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/tsconfig-utils": "^8.51.0",
|
"@typescript-eslint/tsconfig-utils": "^8.50.0",
|
||||||
"@typescript-eslint/types": "^8.51.0",
|
"@typescript-eslint/types": "^8.50.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1528,14 +1532,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
|
||||||
"integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
|
"integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.51.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.51.0"
|
"@typescript-eslint/visitor-keys": "8.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -1546,9 +1550,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
|
"integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1563,17 +1567,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
|
"integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.51.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.51.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.51.0",
|
"@typescript-eslint/utils": "8.50.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^2.2.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -1588,9 +1592,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
|
||||||
"integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
|
"integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1602,21 +1606,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
|
||||||
"integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
|
"integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/project-service": "8.51.0",
|
"@typescript-eslint/project-service": "8.50.0",
|
||||||
"@typescript-eslint/tsconfig-utils": "8.51.0",
|
"@typescript-eslint/tsconfig-utils": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.51.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.51.0",
|
"@typescript-eslint/visitor-keys": "8.50.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"minimatch": "^9.0.4",
|
"minimatch": "^9.0.4",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.0",
|
||||||
"tinyglobby": "^0.2.15",
|
"tinyglobby": "^0.2.15",
|
||||||
"ts-api-utils": "^2.2.0"
|
"ts-api-utils": "^2.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -1669,16 +1673,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
|
||||||
"integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
|
"integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.7.0",
|
"@eslint-community/eslint-utils": "^4.7.0",
|
||||||
"@typescript-eslint/scope-manager": "8.51.0",
|
"@typescript-eslint/scope-manager": "8.50.0",
|
||||||
"@typescript-eslint/types": "8.51.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.51.0"
|
"@typescript-eslint/typescript-estree": "8.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -1693,13 +1697,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
|
||||||
"integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
|
"integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.51.0",
|
"@typescript-eslint/types": "8.50.0",
|
||||||
"eslint-visitor-keys": "^4.2.1"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -1737,6 +1741,7 @@
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -1893,9 +1898,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.9.11",
|
"version": "2.9.9",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.9.tgz",
|
||||||
"integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
|
"integrity": "sha512-V8fbOCSeOFvlDj7LLChUcqbZrdKD9RU/VR260piF1790vT0mfLSwGc/Qzxv3IqiTukOpNtItePa0HBpMAj7MDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -1959,6 +1964,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
|
|
@ -2007,9 +2013,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001762",
|
"version": "1.0.30001760",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz",
|
||||||
"integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
|
"integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|
@ -2364,6 +2370,7 @@
|
||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
|
|
@ -2490,9 +2497,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esquery": {
|
"node_modules/esquery": {
|
||||||
"version": "1.7.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||||
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
|
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -2587,9 +2594,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.20.1",
|
"version": "1.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||||
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
|
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -3007,6 +3014,7 @@
|
||||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
|
|
@ -3507,6 +3515,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
|
|
@ -3702,6 +3711,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -3714,6 +3724,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
|
|
@ -3830,9 +3841,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.54.0",
|
"version": "4.53.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz",
|
||||||
"integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
|
"integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -3846,28 +3857,28 @@
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.54.0",
|
"@rollup/rollup-android-arm-eabi": "4.53.5",
|
||||||
"@rollup/rollup-android-arm64": "4.54.0",
|
"@rollup/rollup-android-arm64": "4.53.5",
|
||||||
"@rollup/rollup-darwin-arm64": "4.54.0",
|
"@rollup/rollup-darwin-arm64": "4.53.5",
|
||||||
"@rollup/rollup-darwin-x64": "4.54.0",
|
"@rollup/rollup-darwin-x64": "4.53.5",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.54.0",
|
"@rollup/rollup-freebsd-arm64": "4.53.5",
|
||||||
"@rollup/rollup-freebsd-x64": "4.54.0",
|
"@rollup/rollup-freebsd-x64": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.54.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.54.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.54.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.54.0",
|
"@rollup/rollup-linux-arm64-musl": "4.53.5",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.54.0",
|
"@rollup/rollup-linux-loong64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.54.0",
|
"@rollup/rollup-linux-ppc64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.54.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.54.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.53.5",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.54.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.54.0",
|
"@rollup/rollup-linux-x64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.54.0",
|
"@rollup/rollup-linux-x64-musl": "4.53.5",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.54.0",
|
"@rollup/rollup-openharmony-arm64": "4.53.5",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.54.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.53.5",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.54.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.53.5",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.54.0",
|
"@rollup/rollup-win32-x64-gnu": "4.53.5",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.54.0",
|
"@rollup/rollup-win32-x64-msvc": "4.53.5",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -4121,6 +4132,7 @@
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
@ -4142,9 +4154,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "2.3.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||||
"integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
|
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -4186,6 +4198,7 @@
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
@ -4195,16 +4208,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript-eslint": {
|
"node_modules/typescript-eslint": {
|
||||||
"version": "8.51.0",
|
"version": "8.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz",
|
||||||
"integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
|
"integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "8.51.0",
|
"@typescript-eslint/eslint-plugin": "8.50.0",
|
||||||
"@typescript-eslint/parser": "8.51.0",
|
"@typescript-eslint/parser": "8.50.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.51.0",
|
"@typescript-eslint/typescript-estree": "8.50.0",
|
||||||
"@typescript-eslint/utils": "8.51.0"
|
"@typescript-eslint/utils": "8.50.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -4279,6 +4292,7 @@
|
||||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -23,7 +23,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({ onSearch }) => {
|
||||||
{/* Search Icon / Toggle */}
|
{/* Search Icon / Toggle */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
className={`text-white p-3 hover:text-white transition-colors duration-300 ${isOpen ? '' : 'w-full h-full flex items-center justify-center'}`}
|
className={`text-white p-3 hover:text-pink-500 transition-colors duration-300 ${isOpen ? '' : 'w-full h-full flex items-center justify-center'}`}
|
||||||
>
|
>
|
||||||
<Search size={isOpen ? 18 : 20} className={isOpen ? 'opacity-60' : ''} />
|
<Search size={isOpen ? 18 : 20} className={isOpen ? 'opacity-60' : ''} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -41,7 +41,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({ onSearch }) => {
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-gray-400/10 to-gray-300/10 pointer-events-none" />
|
<div className="absolute inset-0 bg-gradient-to-r from-pink-500/10 to-violet-500/10 pointer-events-none" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface SearchSkeletonProps {
|
|
||||||
count?: number;
|
|
||||||
estimatedTime?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SearchSkeleton: React.FC<SearchSkeletonProps> = ({
|
|
||||||
count = 9,
|
|
||||||
estimatedTime
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{/* Countdown Timer */}
|
|
||||||
{estimatedTime !== undefined && estimatedTime > 0 && (
|
|
||||||
<div className="text-center py-2">
|
|
||||||
<p className="text-white/50 text-xs">
|
|
||||||
Estimated time: ~{Math.ceil(estimatedTime)}s
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Skeleton Grid */}
|
|
||||||
<div className="grid grid-cols-3 gap-1">
|
|
||||||
{Array.from({ length: count }).map((_, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="aspect-[9/16] bg-white/5 rounded-lg animate-pulse relative overflow-hidden"
|
|
||||||
style={{
|
|
||||||
animationDelay: `${index * 100}ms`
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Shimmer effect */}
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent shimmer" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Add shimmer keyframes via inline style */}
|
|
||||||
<style>{`
|
|
||||||
@keyframes shimmer {
|
|
||||||
0% { transform: translateX(-100%); }
|
|
||||||
100% { transform: translateX(100%); }
|
|
||||||
}
|
|
||||||
.shimmer {
|
|
||||||
animation: shimmer 1.5s infinite;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -25,7 +25,6 @@ interface VideoPlayerProps {
|
||||||
onAuthorClick?: (author: string) => void; // In-app navigation to creator
|
onAuthorClick?: (author: string) => void; // In-app navigation to creator
|
||||||
isMuted?: boolean; // Global mute state from parent
|
isMuted?: boolean; // Global mute state from parent
|
||||||
onMuteToggle?: () => void; // Callback to toggle parent mute state
|
onMuteToggle?: () => void; // Callback to toggle parent mute state
|
||||||
onPauseChange?: (isPaused: boolean) => void; // Notify parent when play/pause state changes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
|
|
@ -35,15 +34,14 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
onFollow,
|
onFollow,
|
||||||
onAuthorClick,
|
onAuthorClick,
|
||||||
isMuted: externalMuted,
|
isMuted: externalMuted,
|
||||||
onMuteToggle,
|
onMuteToggle
|
||||||
onPauseChange
|
|
||||||
}) => {
|
}) => {
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const progressBarRef = useRef<HTMLDivElement>(null);
|
const progressBarRef = useRef<HTMLDivElement>(null);
|
||||||
const [isPaused, setIsPaused] = useState(false);
|
const [isPaused, setIsPaused] = useState(false);
|
||||||
const [showControls, setShowControls] = useState(false);
|
const [showControls, setShowControls] = useState(false);
|
||||||
const [objectFit] = useState<'cover' | 'contain'>('cover');
|
const [objectFit, setObjectFit] = useState<'cover' | 'contain'>('contain');
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const [duration, setDuration] = useState(0);
|
const [duration, setDuration] = useState(0);
|
||||||
const [isSeeking, setIsSeeking] = useState(false);
|
const [isSeeking, setIsSeeking] = useState(false);
|
||||||
|
|
@ -114,15 +112,12 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [isActive]);
|
}, [isActive]);
|
||||||
|
|
||||||
const [showSidebar, setShowSidebar] = useState(false);
|
|
||||||
|
|
||||||
// Reset fallback and loading state when video changes
|
// Reset fallback and loading state when video changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUseFallback(false);
|
setUseFallback(false);
|
||||||
setIsLoading(true); // Show loading for new video
|
setIsLoading(true); // Show loading for new video
|
||||||
setCodecError(false); // Reset codec error for new video
|
setCodecError(false); // Reset codec error for new video
|
||||||
setCachedUrl(null);
|
setCachedUrl(null);
|
||||||
setShowSidebar(false); // Reset sidebar for new video
|
|
||||||
|
|
||||||
const checkCache = async () => {
|
const checkCache = async () => {
|
||||||
const cached = await videoCache.get(video.url);
|
const cached = await videoCache.get(video.url);
|
||||||
|
|
@ -208,14 +203,15 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
if (videoRef.current.paused) {
|
if (videoRef.current.paused) {
|
||||||
videoRef.current.play();
|
videoRef.current.play();
|
||||||
setIsPaused(false);
|
setIsPaused(false);
|
||||||
onPauseChange?.(false);
|
|
||||||
} else {
|
} else {
|
||||||
videoRef.current.pause();
|
videoRef.current.pause();
|
||||||
setIsPaused(true);
|
setIsPaused(true);
|
||||||
onPauseChange?.(true);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleObjectFit = () => {
|
||||||
|
setObjectFit(prev => prev === 'contain' ? 'cover' : 'contain');
|
||||||
|
};
|
||||||
|
|
||||||
const toggleMute = (e: React.MouseEvent | React.TouchEvent) => {
|
const toggleMute = (e: React.MouseEvent | React.TouchEvent) => {
|
||||||
e.stopPropagation(); // Prevent video tap
|
e.stopPropagation(); // Prevent video tap
|
||||||
|
|
@ -251,17 +247,6 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
touches.forEach((touch, index) => {
|
touches.forEach((touch, index) => {
|
||||||
const timeSinceLastTap = now - lastTapRef.current;
|
const timeSinceLastTap = now - lastTapRef.current;
|
||||||
|
|
||||||
// Swipe Left from right edge check (to open sidebar)
|
|
||||||
const rectWidth = rect.width;
|
|
||||||
const startX = touch.clientX - rect.left;
|
|
||||||
|
|
||||||
// If touch starts near right edge (last 15% of screen)
|
|
||||||
if (startX > rectWidth * 0.85 && touches.length === 1) {
|
|
||||||
// We'll handle the actual swipe logic in touchMove/End,
|
|
||||||
// but setting a flag or using the existing click logic might be easier.
|
|
||||||
// For now, let's allow a simple tap on the edge to toggle too, as per existing click logic.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show heart if:
|
// Show heart if:
|
||||||
// 1. Double tap (< 400ms)
|
// 1. Double tap (< 400ms)
|
||||||
// 2. OR Multi-touch (2+ fingers)
|
// 2. OR Multi-touch (2+ fingers)
|
||||||
|
|
@ -310,13 +295,6 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
const rect = containerRef.current.getBoundingClientRect();
|
const rect = containerRef.current.getBoundingClientRect();
|
||||||
const x = e.clientX - rect.left;
|
const x = e.clientX - rect.left;
|
||||||
const y = e.clientY - rect.top;
|
const y = e.clientY - rect.top;
|
||||||
|
|
||||||
// If clicked on the right edge, toggle sidebar
|
|
||||||
if (x > rect.width * 0.9) {
|
|
||||||
setShowSidebar(prev => !prev);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heartId = Date.now() + Math.random();
|
const heartId = Date.now() + Math.random();
|
||||||
setHearts(prev => [...prev, { id: heartId, x, y }]);
|
setHearts(prev => [...prev, { id: heartId, x, y }]);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -394,7 +372,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
{/* Loading Overlay - Subtle pulsing logo */}
|
{/* Loading Overlay - Subtle pulsing logo */}
|
||||||
{isLoading && !codecError && (
|
{isLoading && !codecError && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/40 z-20">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/40 z-20">
|
||||||
<div className="w-16 h-16 bg-gradient-to-r from-gray-400/80 to-gray-300/80 rounded-2xl flex items-center justify-center animate-pulse">
|
<div className="w-16 h-16 bg-gradient-to-r from-cyan-400/80 to-pink-500/80 rounded-2xl flex items-center justify-center animate-pulse">
|
||||||
<svg className="w-8 h-8 text-white" viewBox="0 0 24 24" fill="currentColor">
|
<svg className="w-8 h-8 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z" />
|
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -413,7 +391,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
<a
|
<a
|
||||||
href={downloadUrl}
|
href={downloadUrl}
|
||||||
download
|
download
|
||||||
className="px-4 py-2 bg-gradient-to-r from-gray-500 to-gray-400 text-white text-sm font-medium rounded-full hover:opacity-90 transition-opacity"
|
className="px-4 py-2 bg-gradient-to-r from-cyan-500 to-pink-500 text-white text-sm font-medium rounded-full hover:opacity-90 transition-opacity"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
Download Video
|
Download Video
|
||||||
|
|
@ -431,7 +409,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
top: heart.y - 24,
|
top: heart.y - 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg className="w-16 h-16 text-white drop-shadow-xl filter drop-shadow-lg" viewBox="0 0 24 24" fill="currentColor">
|
<svg className="w-16 h-16 text-pink-500 drop-shadow-xl filter drop-shadow-lg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -463,7 +441,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
onTouchEnd={handleSeekEnd}
|
onTouchEnd={handleSeekEnd}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="h-full bg-gradient-to-r from-gray-400 to-gray-300 transition-all pointer-events-none"
|
className="h-full bg-gradient-to-r from-cyan-400 to-pink-500 transition-all pointer-events-none"
|
||||||
style={{ width: duration ? `${(progress / duration) * 100}%` : '0%' }}
|
style={{ width: duration ? `${(progress / duration) * 100}%` : '0%' }}
|
||||||
/>
|
/>
|
||||||
{/* Scrubber Thumb (always visible when seeking or on hover) */}
|
{/* Scrubber Thumb (always visible when seeking or on hover) */}
|
||||||
|
|
@ -482,17 +460,17 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Side Controls - Only show when video is paused */}
|
{/* Side Controls */}
|
||||||
<div
|
<div
|
||||||
className={`absolute bottom-36 right-4 flex flex-col gap-3 transition-all duration-300 transform ${isPaused && showSidebar ? 'translate-x-0 opacity-100' : 'translate-x-[200%] opacity-0'
|
className={`absolute bottom-36 right-4 flex flex-col gap-3 transition-opacity duration-300 ${showControls ? 'opacity-100' : 'opacity-0'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{/* Follow Button */}
|
{/* Follow Button */}
|
||||||
{onFollow && (
|
{onFollow && (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); onFollow(video.author); }}
|
onClick={() => onFollow(video.author)}
|
||||||
className={`w-12 h-12 flex items-center justify-center backdrop-blur-xl border border-white/10 rounded-full transition-all ${isFollowing
|
className={`w-12 h-12 flex items-center justify-center backdrop-blur-xl border border-white/10 rounded-full transition-all ${isFollowing
|
||||||
? 'bg-gray-500 text-white'
|
? 'bg-pink-500 text-white'
|
||||||
: 'bg-white/10 hover:bg-white/20 text-white'
|
: 'bg-white/10 hover:bg-white/20 text-white'
|
||||||
}`}
|
}`}
|
||||||
title={isFollowing ? 'Following' : 'Follow'}
|
title={isFollowing ? 'Following' : 'Follow'}
|
||||||
|
|
@ -511,6 +489,15 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
<Download size={20} />
|
<Download size={20} />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
{/* Object Fit Toggle */}
|
||||||
|
<button
|
||||||
|
onClick={toggleObjectFit}
|
||||||
|
className="w-12 h-12 flex items-center justify-center bg-white/10 hover:bg-white/20 backdrop-blur-xl border border-white/10 rounded-full text-white text-xs font-bold transition-all"
|
||||||
|
title={objectFit === 'contain' ? 'Fill Screen' : 'Fit Content'}
|
||||||
|
>
|
||||||
|
{objectFit === 'contain' ? '⛶' : '⊡'}
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* Mute Toggle */}
|
{/* Mute Toggle */}
|
||||||
<button
|
<button
|
||||||
onClick={toggleMute}
|
onClick={toggleMute}
|
||||||
|
|
@ -521,15 +508,15 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Author Info - Only show when video is paused */}
|
{/* Author Info */}
|
||||||
<div className={`absolute bottom-10 left-4 right-20 z-10 transition-opacity duration-300 ${isPaused ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}>
|
<div className="absolute bottom-10 left-4 right-20 z-10">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onAuthorClick?.(video.author);
|
onAuthorClick?.(video.author);
|
||||||
}}
|
}}
|
||||||
className="text-white font-semibold text-sm truncate hover:text-white/70 transition-colors inline-flex items-center gap-1"
|
className="text-white font-semibold text-sm truncate hover:text-cyan-400 transition-colors inline-flex items-center gap-1"
|
||||||
>
|
>
|
||||||
@{video.author}
|
@{video.author}
|
||||||
<svg className="w-3 h-3 opacity-50" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
<svg className="w-3 h-3 opacity-50" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
|
|
@ -555,21 +542,8 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Gradient - Only show when video is paused */}
|
{/* Bottom Gradient */}
|
||||||
<div className={`absolute bottom-0 left-0 w-full h-32 bg-gradient-to-t from-black/80 to-transparent pointer-events-none transition-opacity duration-300 ${isPaused ? 'opacity-100' : 'opacity-0'}`} />
|
<div className="absolute bottom-0 left-0 w-full h-32 bg-gradient-to-t from-black/80 to-transparent pointer-events-none" />
|
||||||
|
|
||||||
{/* Right Sidebar Hint - Only show when video is paused */}
|
|
||||||
<div
|
|
||||||
className={`absolute top-0 right-0 w-6 h-full z-40 flex items-center justify-end cursor-pointer transition-opacity duration-300 ${isPaused ? 'opacity-100' : 'opacity-0 pointer-events-none'} ${showSidebar ? 'pointer-events-none' : ''}`}
|
|
||||||
onClick={(e) => { e.stopPropagation(); setShowSidebar(true); }}
|
|
||||||
onTouchEnd={() => {
|
|
||||||
// Check if it was a swipe logic here or just rely on the click/tap
|
|
||||||
setShowSidebar(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Visual Hint */}
|
|
||||||
<div className="w-1 h-12 bg-white/20 rounded-full mr-1 shadow-[0_0_10px_rgba(255,255,255,0.3)] animate-pulse" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,6 @@
|
||||||
#root {
|
#root {
|
||||||
@apply h-full overflow-hidden;
|
@apply h-full overflow-hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile-safe full height - accounts for browser chrome */
|
|
||||||
.h-screen-safe {
|
|
||||||
height: 100vh;
|
|
||||||
height: 100dvh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|
|
||||||
|
|
@ -20,35 +20,26 @@ class FeedLoader {
|
||||||
|
|
||||||
async loadFeedWithOptimization(
|
async loadFeedWithOptimization(
|
||||||
fast: boolean = false,
|
fast: boolean = false,
|
||||||
onProgress?: (videos: Video[]) => void,
|
onProgress?: (videos: Video[]) => void
|
||||||
skipCache: boolean = false
|
|
||||||
): Promise<Video[]> {
|
): Promise<Video[]> {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (fast && !skipCache) {
|
if (fast) {
|
||||||
const videos = await this.loadWithCache('feed-fast');
|
const videos = await this.loadWithCache('feed-fast');
|
||||||
onProgress?.(videos);
|
onProgress?.(videos);
|
||||||
return videos;
|
return videos;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = 'feed-full';
|
const cacheKey = 'feed-full';
|
||||||
|
const cached = this.getCached(cacheKey);
|
||||||
// Skip cache check when explicitly requested (for infinite scroll)
|
if (cached) {
|
||||||
if (!skipCache) {
|
onProgress?.(cached);
|
||||||
const cached = this.getCached(cacheKey);
|
return cached;
|
||||||
if (cached) {
|
|
||||||
onProgress?.(cached);
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const videos = await this.fetchFeed(skipCache);
|
const videos = await this.fetchFeed();
|
||||||
|
this.setCached(cacheKey, videos);
|
||||||
// Only cache if not skipping (initial load)
|
|
||||||
if (!skipCache) {
|
|
||||||
this.setCached(cacheKey, videos);
|
|
||||||
}
|
|
||||||
|
|
||||||
onProgress?.(videos);
|
onProgress?.(videos);
|
||||||
|
|
||||||
|
|
@ -62,12 +53,8 @@ class FeedLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchFeed(skipCache: boolean = false): Promise<Video[]> {
|
private async fetchFeed(): Promise<Video[]> {
|
||||||
// Add skip_cache parameter to force backend to fetch fresh videos
|
const response = await axios.get(`${API_BASE_URL}/feed`);
|
||||||
const url = skipCache
|
|
||||||
? `${API_BASE_URL}/feed?skip_cache=true`
|
|
||||||
: `${API_BASE_URL}/feed`;
|
|
||||||
const response = await axios.get(url);
|
|
||||||
|
|
||||||
if (!Array.isArray(response.data)) {
|
if (!Array.isArray(response.data)) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ interface PrefetchConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CONFIG: PrefetchConfig = {
|
const DEFAULT_CONFIG: PrefetchConfig = {
|
||||||
lookahead: 3, // Increased from 2 for better buffering
|
lookahead: 2,
|
||||||
concurrency: 2, // Increased from 1 for parallel downloads
|
concurrency: 1,
|
||||||
timeoutMs: 30000
|
timeoutMs: 30000
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -49,33 +49,6 @@ class VideoPrefetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prefetch initial batch of videos immediately after feed loads.
|
|
||||||
* This ensures first few videos are ready before user starts scrolling.
|
|
||||||
*/
|
|
||||||
async prefetchInitialBatch(
|
|
||||||
videos: Video[],
|
|
||||||
count: number = 3
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.isInitialized) await this.init();
|
|
||||||
if (videos.length === 0) return;
|
|
||||||
|
|
||||||
console.log(`PREFETCH: Starting initial batch of ${Math.min(count, videos.length)} videos...`);
|
|
||||||
|
|
||||||
const toPrefetch = videos
|
|
||||||
.slice(0, count)
|
|
||||||
.filter((v) => v.url && !this.prefetchQueue.has(v.id));
|
|
||||||
|
|
||||||
// Start all prefetches in parallel (respects concurrency via browser limits)
|
|
||||||
const promises = toPrefetch.map((video) => {
|
|
||||||
this.prefetchQueue.add(video.id);
|
|
||||||
return this.prefetchVideo(video);
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.allSettled(promises);
|
|
||||||
console.log(`PREFETCH: Initial batch complete (${toPrefetch.length} videos buffered)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async prefetchVideo(video: Video): Promise<void> {
|
private async prefetchVideo(video: Video): Promise<void> {
|
||||||
if (!video.url) return;
|
if (!video.url) return;
|
||||||
|
|
||||||
|
|
@ -85,12 +58,6 @@ class VideoPrefetcher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE_URL = 'http://localhost:8002/api'; // Hardcoded or imported from config
|
|
||||||
const fullProxyUrl = `${API_BASE_URL}/feed/proxy?url=${encodeURIComponent(video.url)}`;
|
|
||||||
// Use thin proxy if available for better performance
|
|
||||||
const thinProxyUrl = video.cdn_url ? `${API_BASE_URL}/feed/thin-proxy?cdn_url=${encodeURIComponent(video.cdn_url)}` : null;
|
|
||||||
const targetUrl = thinProxyUrl || fullProxyUrl;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(
|
const timeoutId = setTimeout(
|
||||||
|
|
@ -98,7 +65,7 @@ class VideoPrefetcher {
|
||||||
this.config.timeoutMs
|
this.config.timeoutMs
|
||||||
);
|
);
|
||||||
|
|
||||||
const response = await fetch(targetUrl, {
|
const response = await fetch(video.url, {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
headers: { Range: 'bytes=0-1048576' }
|
headers: { Range: 'bytes=0-1048576' }
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("Testing /health...")
|
|
||||||
with urllib.request.urlopen("http://localhost:8002/health", timeout=5) as r:
|
|
||||||
print(f"Health: {r.status}")
|
|
||||||
|
|
||||||
print("Testing /api/feed...")
|
|
||||||
with open("temp_cookies.json", "r") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
# Ensure list format
|
|
||||||
if isinstance(data, dict) and "credentials" in data:
|
|
||||||
data = data["credentials"]
|
|
||||||
|
|
||||||
# Prepare body as dict for safety with new Union type
|
|
||||||
body = {"credentials": data}
|
|
||||||
|
|
||||||
req = urllib.request.Request(
|
|
||||||
"http://localhost:8002/api/feed",
|
|
||||||
data=json.dumps(body).encode('utf-8'),
|
|
||||||
headers={'Content-Type': 'application/json'}
|
|
||||||
)
|
|
||||||
with urllib.request.urlopen(req, timeout=30) as r:
|
|
||||||
print(f"Feed: {r.status}")
|
|
||||||
print(r.read().decode('utf-8')[:100])
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
@ -1,314 +0,0 @@
|
||||||
{
|
|
||||||
"credentials": [
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1784039026,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "delay_guest_mode_vid",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1768131143.251207,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "msToken",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "jntmMFSrdBzHw3GQQ7xigi2HLM03wLgd2s8xW8sa8bm3gVg-VJu63FlYSfvPAW6tmoNM-Ww9ho9sOKZc75EN1XIGwct0ndkyOairFWbXgkiFwPXfDpQaBA9pn2_9mSOYSylT1H60yH1ufg=="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646103,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "tt_session_tlb_tag",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "sttt%7C1%7C6M35zM57kkqAGSs_LSUdRP_________zyplxYaEARSr2PNU_6cKcB0lq4WRz1GKKY43u399i5hs%3D"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1798369620.645922,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sid_guard",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44%7C1767265616%7C15552000%7CTue%2C+30-Jun-2026+11%3A06%3A56+GMT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1798801628.385793,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "ttwid",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "1%7CAYDsetgnxt5vzYX8hD6Wq2DQ4FXiL_pqcdLwHWwz6B8%7C1767265624%7C6121d82381fb651afeae94341e45b87fca1d903fbec0d8a19e4dd5440a89a424"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1767870430,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "perf_feed_cache",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "{%22expireTimestamp%22:1767438000000%2C%22itemIds%22:[%227588749061168123154%22%2C%227589493510613552404%22%2C%227586917939568332054%22]}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.645952,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "uid_tt",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "44deb1e89d254f610eefd18c39ec97fa708e9c0f22c0207f7140c6ffd6c81b2c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449601.742227,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "passport_csrf_token_default",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "9c66ab10306611c75fa19c87e54fd31b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "s_v_web_id",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": true,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "verify_mjvcbi31_l3mxUEeU_ykis_4x6z_859S_8zEFmNseEnJU"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.64617,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "ssid_ucp_v1",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "1.0.1-KDlhMTg2NTQ1MmJiZmNlZjgzYzNiZGU4ZjAyNzk1NWRkNTlkOTYxNjIKIQiBiIHG4PKvxV8Q0KrZygYYswsgDDC50ZfDBjgIQBJIBBADGgJteSIgZThjZGY5Y2NjZTdiOTI0YTgwMTkyYjNmMmQyNTFkNDQyTgog40q2JTBb3lGgiNKowpX3zbxplmW4zO3AUFhAo6LMB-wSIDpAp_OQ2Q5qEBZvL59v7fgLmw27UIxLQHoimzDg3U5BGAIiBnRpa3Rvaw"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1793185625,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "tiktok_webapp_theme",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "dark"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1799409936.219767,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "_ttp",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "32XOXKxwj8YLtBQf0OBn4TvlkPN"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449620.645821,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "cmpl_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "AgQYAPOF_hfkTtKPtFExgPKdOPKrXVkNUj-FDmCi6K4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449620.645628,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "multi_sids",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "6884525631502042113%3Ae8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1769857620.645892,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "passport_auth_status_ss",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "966972581a398dbb9ead189c044cf98c%2C"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1772449601.742082,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "passport_csrf_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "9c66ab10306611c75fa19c87e54fd31b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646041,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sessionid",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646073,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sessionid_ss",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646008,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sid_tt",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "e8cdf9ccce7b924a80192b3f2d251d44"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.646132,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "sid_ucp_v1",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "1.0.1-KDlhMTg2NTQ1MmJiZmNlZjgzYzNiZGU4ZjAyNzk1NWRkNTlkOTYxNjIKIQiBiIHG4PKvxV8Q0KrZygYYswsgDDC50ZfDBjgIQBJIBBADGgJteSIgZThjZGY5Y2NjZTdiOTI0YTgwMTkyYjNmMmQyNTFkNDQyTgog40q2JTBb3lGgiNKowpX3zbxplmW4zO3AUFhAo6LMB-wSIDpAp_OQ2Q5qEBZvL59v7fgLmw27UIxLQHoimzDg3U5BGAIiBnRpa3Rvaw"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".www.tiktok.com",
|
|
||||||
"expirationDate": 1793185625,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": false,
|
|
||||||
"name": "tiktok_webapp_theme_source",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "auto"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817624.001151,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "tt_chain_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": null,
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "6deMEWrkAGUe9R0tCISIoQ=="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "tt_csrf_token",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "lax",
|
|
||||||
"secure": true,
|
|
||||||
"session": true,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "q0Q4ki72-I7zQB6eLbpBBaqFBGrUF_v85N9s"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": ".tiktok.com",
|
|
||||||
"expirationDate": 1782817620.645979,
|
|
||||||
"hostOnly": false,
|
|
||||||
"httpOnly": true,
|
|
||||||
"name": "uid_tt_ss",
|
|
||||||
"path": "/",
|
|
||||||
"sameSite": "no_restriction",
|
|
||||||
"secure": true,
|
|
||||||
"session": false,
|
|
||||||
"storeId": null,
|
|
||||||
"value": "44deb1e89d254f610eefd18c39ec97fa708e9c0f22c0207f7140c6ffd6c81b2c"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import requests
|
|
||||||
import time
|
|
||||||
|
|
||||||
URL = "http://localhost:8002/api/auth/admin-login"
|
|
||||||
|
|
||||||
def test_login():
|
|
||||||
print("Testing Admin Login...")
|
|
||||||
try:
|
|
||||||
res = requests.post(URL, json={"password": "admin123"})
|
|
||||||
print(f"Status: {res.status_code}")
|
|
||||||
print(f"Response: {res.text}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_login()
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
with open("temp_cookies.json", "r") as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
# Ensure data is in the expected dict format for the request body
|
|
||||||
if isinstance(data, list):
|
|
||||||
# If temp_cookies is just the list, wrap it
|
|
||||||
body = {"credentials": data}
|
|
||||||
elif "credentials" not in data:
|
|
||||||
body = {"credentials": data}
|
|
||||||
else:
|
|
||||||
body = data
|
|
||||||
|
|
||||||
req = urllib.request.Request(
|
|
||||||
"http://localhost:8002/api/feed",
|
|
||||||
data=json.dumps(body).encode('utf-8'),
|
|
||||||
headers={'Content-Type': 'application/json'}
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req) as response:
|
|
||||||
print(response.read().decode('utf-8'))
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
print(f"HTTP Error: {e.code}")
|
|
||||||
print(e.read().decode('utf-8'))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
|
|
||||||
BASE_URL = "http://localhost:8002/api/user/search"
|
|
||||||
|
|
||||||
def test_search():
|
|
||||||
print("Testing Search API...")
|
|
||||||
try:
|
|
||||||
# Simple query
|
|
||||||
params = {
|
|
||||||
"query": "dance",
|
|
||||||
"limit": 50,
|
|
||||||
"cursor": 0
|
|
||||||
}
|
|
||||||
start = time.time()
|
|
||||||
res = requests.get(BASE_URL, params=params)
|
|
||||||
duration = time.time() - start
|
|
||||||
|
|
||||||
print(f"Status Code: {res.status_code}")
|
|
||||||
print(f"Duration: {duration:.2f}s")
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
data = res.json()
|
|
||||||
print(f"Videos Found: {len(data.get('videos', []))}")
|
|
||||||
# print(json.dumps(data, indent=2))
|
|
||||||
else:
|
|
||||||
print("Error Response:")
|
|
||||||
print(res.text)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Request Failed: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_search()
|
|
||||||
Loading…
Reference in a new issue