Skill: matejmicek/youtube-playlists (version 1.0.0)

--- name: youtube-playlists description: Create and manage YouTube playlists. Use when user wants to create a playlist, add videos to playlists, or manage their YouTube playlists. metadata: {"openclaw":{"emoji":"📋","requires":{"bins":["python3"]}}} --- # YouTube Playlists Create and manage YouTube playlists via OAuth. ## Commands ```bash # Authenticate (first time only) python3 {baseDir}/scripts/yt_playlist.py auth # Create empty playlist python3 {baseDir}/scripts/yt_playlist.py create "Playlist Name" # Add video to existing playlist python3 {baseDir}/scripts/yt_playlist.py add <playlist_id> <video_id_or_url> # Create playlist with multiple videos (best for agent use) python3 {baseDir}/scripts/yt_playlist.py bulk-create "Playlist Name" <video1> <video2> ... # List your playlists python3 {baseDir}/scripts/yt_playlist.py list ``` ## Examples Create a Zwift watchlist: ```bash python3 {baseDir}/scripts/yt_playlist.py bulk-create "Zwift Feb 3" \ l3u_FAv33G0 \ MY5omSLtAvk \ VdaZqfEKv38 \ Wq16lyNpmYs \ SE7d4eaOJv4 ``` ## Notes - First run requires browser auth (opens automatically) - Token is cached in `token.pickle` - Accepts video IDs or full YouTube URLs - Default privacy is "unlisted" for bulk-create, "private" for single create

path: scripts/yt_playlist.py
content: #!/usr/bin/env python3
"""
YouTube Playlist Manager - Create and manage YouTube playlists via OAuth.

Usage:
  python3 yt_playlist.py auth                              # Authenticate (first time)
  python3 yt_playlist.py create "Playlist Name"            # Create empty playlist
  python3 yt_playlist.py add <playlist_id> <video_id>      # Add video to playlist
  python3 yt_playlist.py bulk-create "Name" <video_ids>... # Create playlist with videos
  python3 yt_playlist.py list                              # List your playlists
"""

import os
import sys
import json
import pickle
from pathlib import Path

from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build

SKILL_DIR = Path(__file__).parent.parent
CREDENTIALS_FILE = SKILL_DIR / "credentials.json"
TOKEN_FILE = SKILL_DIR / "token.pickle"

SCOPES = ['https://www.googleapis.com/auth/youtube']

def get_authenticated_service():
    """Get authenticated 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:
            creds.refresh(Request())
        else:
            if not CREDENTIALS_FILE.exists():
                print(f"Error: credentials.json not found at {CREDENTIALS_FILE}")
                print("Download OAuth credentials from Google Cloud Console.")
                sys.exit(1)
            
            flow = InstalledAppFlow.from_client_secrets_file(
                str(CREDENTIALS_FILE), SCOPES)
            creds = flow.run_local_server(port=8080)
        
        with open(TOKEN_FILE, 'wb') as [REDACTED_SECRET], token)
    
    return build('youtube', 'v3', credentials=creds)

def auth():
    """Authenticate and store credentials."""
    print("Opening browser for Google authentication...")
    service = get_authenticated_service()
    print("✅ Authentication successful! Token saved.")
    return service

def create_playlist(title: str, description: str = "", privacy: str = "private"):
    """Create a new playlist."""
    service = get_authenticated_service()
    
    request = service.playlists().insert(
        part="snippet,status",
        body={
            "snippet": {
                "title": title,
                "description": description
            },
            "status": {
                "privacyStatus": privacy
            }
        }
    )
    response = request.execute()
    
    playlist_id = response['id']
    print(f"✅ Created playlist: {title}")
    print(f"   ID: {playlist_id}")
    print(f"   URL: https://youtube.com/playlist?list={playlist_id}")
    
    return playlist_id

def add_video_to_playlist(playlist_id: str, video_id: str):
    """Add a video to a playlist."""
    service = get_authenticated_service()
    
    # Clean video_id (handle full URLs)
    if "youtube.com" in video_id or "youtu.be" in video_id:
        if "v=" in video_id:
            video_id = video_id.split("v=")[1].split("&")[0]
        elif "youtu.be/" in video_id:
            video_id = video_id.split("youtu.be/")[1].split("?")[0]
    
    request = service.playlistItems().insert(
        part="snippet",
        body={
            "snippet": {
                "playlistId": playlist_id,
                "resourceId": {
                    "kind": "youtube#video",
                    "videoId": video_id
                }
            }
        }
    )
    response = request.execute()
    print(f"✅ Added video {video_id} to playlist")
    return response

def bulk_create_playlist(title: str, video_ids: list, description: str = "", privacy: str = "unlisted"):
    """Create a playlist and add multiple videos."""
    playlist_id = create_playlist(title, description, privacy)
    
    print(f"\nAdding {len(video_ids)} videos...")
    for i, video_id in enumerate(video_ids, 1):
        try:
            add_video_to_playlist(playlist_id, video_id)
            print(f"   [{i}/{len(video_ids)}] Added {video_id}")
        except Exception as e:
            print(f"   [{i}/{len(video_ids)}] Failed: {video_id} - {e}")
    
    print(f"\n🎉 Done! Playlist URL: https://youtube.com/playlist?list={playlist_id}")
    return playlist_id

def list_playlists(max_results: int = 25):
    """List user's playlists."""
    service = get_authenticated_service()
    
    request = service.playlists().list(
        part="snippet,contentDetails",
        mine=True,
        maxResults=max_results
    )
    response = request.execute()
    
    print("Your playlists:\n")
    for item in response.get('items', []):
        title = item['snippet']['title']
        playlist_id = item['id']
        count = item['contentDetails']['itemCount']
        print(f"  • {title} ({count} videos)")
        print(f"    ID: {playlist_id}")
        print(f"    URL: https://youtube.com/playlist?list={playlist_id}\n")
    
    return response.get('items', [])

def remove_video_from_playlist(playlist_id: str, video_id: str):
    """Remove a video from a playlist."""
    service = get_authenticated_service()
    
    # Clean video_id (handle full URLs)
    if "youtube.com" in video_id or "youtu.be" in video_id:
        if "v=" in video_id:
            video_id = video_id.split("v=")[1].split("&")[0]
        elif "youtu.be/" in video_id:
            video_id = video_id.split("youtu.be/")[1].split("?")[0]
    
    # Find the playlistItem ID for this video
    request = service.playlistItems().list(
        part="id,snippet",
        playlistId=playlist_id,
        maxResults=50
    )
    response = request.execute()
    
    item_id = None
    for item in response.get('items', []):
        if item['snippet']['resourceId']['videoId'] == video_id:
            item_id = item['id']
            break
    
    if not item_id:
        print(f"❌ Video {video_id} not found in playlist")
        return None
    
    # Delete the playlist item
    service.playlistItems().delete(id=item_id).execute()
    print(f"✅ Removed video {video_id} from playlist")
    return True

def list_playlist_videos(playlist_id: str, max_results: int = 50):
    """List videos in a playlist."""
    service = get_authenticated_service()
    
    request = service.playlistItems().list(
        part="snippet,contentDetails",
        playlistId=playlist_id,
        maxResults=max_results
    )
    response = request.execute()
    
    print(f"Videos in playlist:\n")
    for item in response.get('items', []):
        title = item['snippet']['title']
        video_id = item['snippet']['resourceId']['videoId']
        channel = item['snippet'].get('videoOwnerChannelTitle', 'Unknown')
        print(f"  • {title}")
        print(f"    ID: {video_id} | Channel: {channel}\n")
    
    return response.get('items', [])

def get_watch_history():
    """Get user's watch history (liked videos as proxy)."""
    service = get_authenticated_service()
    
    # Get liked videos
    request = service.videos().list(
        part="snippet,contentDetails",
        myRating="like",
        maxResults=50
    )
    response = request.execute()
    
    print("Your liked videos:\n")
    for item in response.get('items', []):
        title = item['snippet']['title']
        video_id = item['id']
        channel = item['snippet']['channelTitle']
        print(f"  • {title}")
        print(f"    ID: {video_id} | Channel: {channel}\n")
    
    return response.get('items', [])

def get_subscriptions(max_results: int = 50):
    """Get user's subscriptions."""
    service = get_authenticated_service()
    
    request = service.subscriptions().list(
        part="snippet",
        mine=True,
        maxResults=max_results
    )
    response = request.execute()
    
    print("Your subscriptions:\n")
    for item in response.get('items', []):
        title = item['snippet']['title']
        channel_id = item['snippet']['resourceId']['channelId']
        print(f"  • {title}")
        print(f"    Channel ID: {channel_id}\n")
    
    return response.get('items', [])

def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)
    
    command = sys.argv[1]
    
    if command == "auth":
        auth()
    
    elif command == "create":
        if len(sys.argv) < 3:
            print("Usage: yt_playlist.py create <title> [description] [privacy]")
            sys.exit(1)
        title = sys.argv[2]
        description = sys.argv[3] if len(sys.argv) > 3 else ""
        privacy = sys.argv[4] if len(sys.argv) > 4 else "private"
        create_playlist(title, description, privacy)
    
    elif command == "add":
        if len(sys.argv) < 4:
            print("Usage: yt_playlist.py add <playlist_id> <video_id>")
            sys.exit(1)
        add_video_to_playlist(sys.argv[2], sys.argv[3])
    
    elif command == "bulk-create":
        if len(sys.argv) < 4:
            print("Usage: yt_playlist.py bulk-create <title> <video_id1> [video_id2] ...")
            sys.exit(1)
        title = sys.argv[2]
        video_ids = sys.argv[3:]
        bulk_create_playlist(title, video_ids)
    
    elif command == "list":
        list_playlists()
    
    elif command == "remove":
        if len(sys.argv) < 4:
            print("Usage: yt_playlist.py remove <playlist_id> <video_id>")
            sys.exit(1)
        remove_video_from_playlist(sys.argv[2], sys.argv[3])
    
    elif command == "videos":
        if len(sys.argv) < 3:
            print("Usage: yt_playlist.py videos <playlist_id>")
            sys.exit(1)
        list_playlist_videos(sys.argv[2])
    
    elif command == "liked":
        get_watch_history()
    
    elif command == "subscriptions":
        get_subscriptions()
    
    else:
        print(f"Unknown command: {command}")
        print(__doc__)
        sys.exit(1)

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)