feat(skills): Add notion-writer skill and YouTube manager CLI scripts
- Add 02-notion-writer skill with Python script for pushing markdown to Notion - Add YouTube API CLI scripts for jamie-youtube-manager (channel status, video info, batch update) - Update jamie-youtube-manager SKILL.md with CLI script documentation - Update CLAUDE.md with quick reference guides 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
YouTube API Connection Test Script for Jamie Clinic
|
||||
Tests OAuth authentication and verifies Jamie channel access.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
from google.auth.transport.requests import Request
|
||||
from googleapiclient.discovery import build
|
||||
import pickle
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv(Path(__file__).parent / '.env')
|
||||
|
||||
# YouTube API scopes needed for updating video metadata
|
||||
SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
|
||||
|
||||
# Token file path
|
||||
TOKEN_FILE = Path(__file__).parent / 'jamie_youtube_token.pickle'
|
||||
CLIENT_SECRETS_FILE = Path(os.getenv('GOOGLE_CLIENT_SECRETS_FILE',
|
||||
'/Users/ourdigital/.config/gcloud/keys/jamie-youtube-manager.json'))
|
||||
|
||||
# Jamie Clinic YouTube Channel (Default)
|
||||
JAMIE_CHANNEL_ID = "UCtjR6NnlaX1dPPER7wHbGpw"
|
||||
JAMIE_CHANNEL_NAME = "제이미 성형외과"
|
||||
|
||||
|
||||
def get_authenticated_service():
|
||||
"""Authenticate and return YouTube API service."""
|
||||
creds = None
|
||||
|
||||
if TOKEN_FILE.exists():
|
||||
with open(TOKEN_FILE, 'rb') as token:
|
||||
creds = pickle.load(token)
|
||||
|
||||
if not creds or not creds.valid:
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
print("Refreshing expired credentials...")
|
||||
creds.refresh(Request())
|
||||
else:
|
||||
if not CLIENT_SECRETS_FILE.exists():
|
||||
print(f"\n[ERROR] OAuth client secret file not found!")
|
||||
print(f"Expected location: {CLIENT_SECRETS_FILE}")
|
||||
return None
|
||||
|
||||
print("Starting OAuth authentication flow...")
|
||||
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES)
|
||||
creds = flow.run_local_server(port=8080)
|
||||
|
||||
with open(TOKEN_FILE, 'wb') as token:
|
||||
pickle.dump(creds, token)
|
||||
print(f"Credentials saved to: {TOKEN_FILE}")
|
||||
|
||||
return build('youtube', 'v3', credentials=creds)
|
||||
|
||||
|
||||
def test_jamie_channel(youtube):
|
||||
"""Test connection to Jamie's YouTube channel."""
|
||||
print("\n" + "="*60)
|
||||
print(f"Testing Jamie Channel Access: {JAMIE_CHANNEL_NAME}")
|
||||
print("="*60)
|
||||
|
||||
try:
|
||||
response = youtube.channels().list(
|
||||
part="snippet,statistics,brandingSettings",
|
||||
id=JAMIE_CHANNEL_ID
|
||||
).execute()
|
||||
|
||||
if response.get('items'):
|
||||
channel = response['items'][0]
|
||||
stats = channel.get('statistics', {})
|
||||
print(f"\n✅ Jamie Channel Connected!")
|
||||
print(f"\n📺 Channel Info:")
|
||||
print(f" Name: {channel['snippet']['title']}")
|
||||
print(f" ID: {JAMIE_CHANNEL_ID}")
|
||||
print(f" Subscribers: {stats.get('subscriberCount', 'Hidden')}")
|
||||
print(f" Total Views: {stats.get('viewCount', '0')}")
|
||||
print(f" Total Videos: {stats.get('videoCount', '0')}")
|
||||
return True
|
||||
else:
|
||||
print(f"\n❌ Jamie channel not found")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Connection failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_video_access(youtube, video_id):
|
||||
"""Test if we can access a Jamie video."""
|
||||
print(f"\n" + "="*60)
|
||||
print(f"Testing Video Access: {video_id}")
|
||||
print("="*60)
|
||||
|
||||
try:
|
||||
response = youtube.videos().list(
|
||||
part="snippet,status,statistics",
|
||||
id=video_id
|
||||
).execute()
|
||||
|
||||
if response.get('items'):
|
||||
video = response['items'][0]
|
||||
snippet = video['snippet']
|
||||
stats = video.get('statistics', {})
|
||||
|
||||
# Verify it's a Jamie video
|
||||
is_jamie = snippet['channelId'] == JAMIE_CHANNEL_ID
|
||||
|
||||
print(f"\n✅ Video accessible!")
|
||||
print(f" Title: {snippet['title']}")
|
||||
print(f" Channel: {snippet['channelTitle']}")
|
||||
print(f" Status: {video['status']['privacyStatus']}")
|
||||
print(f" Views: {stats.get('viewCount', '0')}")
|
||||
print(f" Jamie Channel: {'✅ Yes' if is_jamie else '❌ No'}")
|
||||
return True
|
||||
else:
|
||||
print(f"\n⚠️ Video not found or not accessible")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Video access failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
print("="*60)
|
||||
print(f"Jamie Clinic ({JAMIE_CHANNEL_NAME}) - API Connection Test")
|
||||
print("="*60)
|
||||
|
||||
youtube = get_authenticated_service()
|
||||
if not youtube:
|
||||
sys.exit(1)
|
||||
|
||||
# Test Jamie channel access
|
||||
if not test_jamie_channel(youtube):
|
||||
sys.exit(1)
|
||||
|
||||
# Test access to Jamie's intro video
|
||||
test_video_id = "P-ovr-aaD1E"
|
||||
test_video_access(youtube, test_video_id)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("API Connection Test Complete!")
|
||||
print("="*60)
|
||||
print("\nAvailable commands:")
|
||||
print(" python jamie_channel_status.py # Full channel status")
|
||||
print(" python jamie_video_info.py <URL> # Get video info")
|
||||
print(" python jamie_youtube_batch_update.py --dry-run")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user