Add TikTok sync script with auto token refresh
- sync-tiktok.sh: Syncs video stats and account metrics to Arbiter - .tiktok-tokens: Initial OAuth tokens (sandbox credentials) - Auto-refreshes access token on each run (refresh token lasts 1 year) - Cron: 5 13 * * * for 8:05 AM CT daily Chronicler #76
This commit is contained in:
2
scripts/.tiktok-tokens
Normal file
2
scripts/.tiktok-tokens
Normal file
@@ -0,0 +1,2 @@
|
||||
ACCESS_TOKEN="act.6olZAg0uSnTVR6hO2jTTEpak8FPmtEEdS8uzTPmlTtVl06vZ8hswkOHGLyAV!6393.u1"
|
||||
REFRESH_TOKEN="rft.v0FuyZmcsNbeZNZSKo9Pojwq1LYUxp5YeTPXkqb3wxfP0FGb53zY7YcxHyUM!6431.u1"
|
||||
191
scripts/sync-tiktok.sh
Normal file
191
scripts/sync-tiktok.sh
Normal file
@@ -0,0 +1,191 @@
|
||||
#!/bin/bash
|
||||
# /opt/scripts/sync-tiktok.sh
|
||||
# TikTok analytics sync for Firefrost Gaming
|
||||
# Runs via cron, refreshes token automatically, syncs to Arbiter
|
||||
|
||||
set -e
|
||||
|
||||
# Config
|
||||
TOKEN_FILE="/opt/scripts/.tiktok-tokens"
|
||||
ARBITER_URL="https://discord-bot.firefrostgaming.com/api/internal/social"
|
||||
ARBITER_TOKEN="6fYF1akCRW6pM2F8n3S3RxeIod4YgRniUJNEQurvBP4="
|
||||
|
||||
# TikTok App credentials (sandbox)
|
||||
CLIENT_KEY="sbawse6t5serp8xdqp"
|
||||
CLIENT_SECRET="Ib24OixwLnjZbB8KXAUcYn6ewM60KKDp"
|
||||
|
||||
# Load existing tokens
|
||||
if [ -f "$TOKEN_FILE" ]; then
|
||||
source "$TOKEN_FILE"
|
||||
else
|
||||
echo "ERROR: Token file not found at $TOKEN_FILE"
|
||||
echo "Create it with:"
|
||||
echo " ACCESS_TOKEN=act.xxxxx"
|
||||
echo " REFRESH_TOKEN=rft.xxxxx"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to refresh access token
|
||||
refresh_token() {
|
||||
echo "Refreshing TikTok access token..."
|
||||
|
||||
RESPONSE=$(curl -s -X POST 'https://open.tiktokapis.com/v2/oauth/token/' \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d "client_key=$CLIENT_KEY" \
|
||||
-d "client_secret=$CLIENT_SECRET" \
|
||||
-d "refresh_token=$REFRESH_TOKEN" \
|
||||
-d 'grant_type=refresh_token')
|
||||
|
||||
# Check for error
|
||||
ERROR=$(echo "$RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
|
||||
if [ -n "$ERROR" ] && [ "$ERROR" != "ok" ]; then
|
||||
echo "ERROR: Token refresh failed: $ERROR"
|
||||
echo "$RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract new tokens
|
||||
NEW_ACCESS=$(echo "$RESPONSE" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
|
||||
NEW_REFRESH=$(echo "$RESPONSE" | grep -o '"refresh_token":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -n "$NEW_ACCESS" ]; then
|
||||
ACCESS_TOKEN="$NEW_ACCESS"
|
||||
echo "Access token refreshed successfully"
|
||||
fi
|
||||
|
||||
if [ -n "$NEW_REFRESH" ]; then
|
||||
REFRESH_TOKEN="$NEW_REFRESH"
|
||||
fi
|
||||
|
||||
# Save updated tokens
|
||||
cat > "$TOKEN_FILE" << TOKENS
|
||||
ACCESS_TOKEN="$ACCESS_TOKEN"
|
||||
REFRESH_TOKEN="$REFRESH_TOKEN"
|
||||
TOKENS
|
||||
chmod 600 "$TOKEN_FILE"
|
||||
}
|
||||
|
||||
# Function to sync account stats
|
||||
sync_account_stats() {
|
||||
echo "Fetching TikTok account stats..."
|
||||
|
||||
RESPONSE=$(curl -s -X GET \
|
||||
'https://open.tiktokapis.com/v2/user/info/?fields=display_name,follower_count,following_count,likes_count,video_count' \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN")
|
||||
|
||||
# Check for auth error (token expired)
|
||||
ERROR=$(echo "$RESPONSE" | grep -o '"code":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
if [ "$ERROR" = "access_token_invalid" ]; then
|
||||
echo "Access token expired, refreshing..."
|
||||
refresh_token
|
||||
# Retry
|
||||
RESPONSE=$(curl -s -X GET \
|
||||
'https://open.tiktokapis.com/v2/user/info/?fields=display_name,follower_count,following_count,likes_count,video_count' \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN")
|
||||
fi
|
||||
|
||||
FOLLOWERS=$(echo "$RESPONSE" | grep -o '"follower_count":[0-9]*' | cut -d':' -f2)
|
||||
FOLLOWING=$(echo "$RESPONSE" | grep -o '"following_count":[0-9]*' | cut -d':' -f2)
|
||||
LIKES=$(echo "$RESPONSE" | grep -o '"likes_count":[0-9]*' | cut -d':' -f2)
|
||||
VIDEOS=$(echo "$RESPONSE" | grep -o '"video_count":[0-9]*' | cut -d':' -f2)
|
||||
|
||||
echo " Followers: $FOLLOWERS, Following: $FOLLOWING, Likes: $LIKES, Videos: $VIDEOS"
|
||||
|
||||
# Sync to Arbiter
|
||||
curl -s -X POST "$ARBITER_URL/snapshot" \
|
||||
-H "Authorization: Bearer $ARBITER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"platform\": \"tiktok\",
|
||||
\"handle\": \"playfirefrost\",
|
||||
\"followers\": ${FOLLOWERS:-0},
|
||||
\"following\": ${FOLLOWING:-0},
|
||||
\"posts\": ${VIDEOS:-0}
|
||||
}" > /dev/null
|
||||
|
||||
echo " Account snapshot synced to Arbiter"
|
||||
}
|
||||
|
||||
# Function to sync video stats
|
||||
sync_videos() {
|
||||
echo "Fetching TikTok video stats..."
|
||||
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
'https://open.tiktokapis.com/v2/video/list/?fields=id,title,create_time,view_count,like_count,comment_count,share_count' \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"max_count": 50}')
|
||||
|
||||
# Check for auth error
|
||||
ERROR=$(echo "$RESPONSE" | grep -o '"code":"access_token_invalid"')
|
||||
if [ -n "$ERROR" ]; then
|
||||
echo "Access token expired, refreshing..."
|
||||
refresh_token
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
'https://open.tiktokapis.com/v2/video/list/?fields=id,title,create_time,view_count,like_count,comment_count,share_count' \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"max_count": 50}')
|
||||
fi
|
||||
|
||||
# Parse and sync each video using Python for JSON handling
|
||||
python3 << PYTHON
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
response = '''$RESPONSE'''
|
||||
data = json.loads(response)
|
||||
|
||||
if 'data' not in data or 'videos' not in data['data']:
|
||||
print(" No videos found or error in response")
|
||||
sys.exit(0)
|
||||
|
||||
videos = data['data']['videos']
|
||||
print(f" Found {len(videos)} videos")
|
||||
|
||||
for video in videos:
|
||||
video_id = video.get('id', '')
|
||||
title = video.get('title', '')[:100] # Truncate for post_url field
|
||||
views = video.get('view_count', 0)
|
||||
likes = video.get('like_count', 0)
|
||||
comments = video.get('comment_count', 0)
|
||||
shares = video.get('share_count', 0)
|
||||
|
||||
# Create post URL
|
||||
post_url = f"https://www.tiktok.com/@playfirefrost/video/{video_id}"
|
||||
|
||||
payload = {
|
||||
"platform": "tiktok",
|
||||
"post_url": post_url,
|
||||
"views": views,
|
||||
"likes": likes,
|
||||
"comments": comments,
|
||||
"shares": shares
|
||||
}
|
||||
|
||||
result = subprocess.run([
|
||||
'curl', '-s', '-X', 'POST',
|
||||
'$ARBITER_URL/sync',
|
||||
'-H', 'Authorization: Bearer $ARBITER_TOKEN',
|
||||
'-H', 'Content-Type: application/json',
|
||||
'-d', json.dumps(payload)
|
||||
], capture_output=True, text=True)
|
||||
|
||||
print(f" {video_id}: {views} views, {likes} likes")
|
||||
|
||||
print(" All videos synced to Arbiter")
|
||||
PYTHON
|
||||
}
|
||||
|
||||
# Main
|
||||
echo "=== TikTok Sync $(date) ==="
|
||||
|
||||
# Refresh token proactively (do this every run to keep token fresh)
|
||||
refresh_token
|
||||
|
||||
# Sync data
|
||||
sync_account_stats
|
||||
sync_videos
|
||||
|
||||
echo "=== Sync complete ==="
|
||||
Reference in New Issue
Block a user