Skill: kanfred/pollinations-ai (version 1.0.4)

--- name: pollinations-ai description: Generate images, music, and video using Pollinations AI API. Use when: (1) User wants to create images from text prompts, (2) User wants to generate music/audio, (3) User wants to create videos from text, (4) Using AI image generation with models like flux, zimage, nanobanana. --- # Pollinations AI - Image, Music & Video Generation A comprehensive skill for generating images, music, and video using the Pollinations AI API. ## Overview Pollinations.ai provides free AI generation with API key support. This skill enables: - **Image Generation** - Create images from text prompts using state-of-the-art models - **Music/Audio Generation** - Generate background music and audio tracks - **Video Generation** - Create videos from text descriptions ## Prerequisites ### 1. Get an API Key Visit [pollinations.ai](https://pollinations.ai/) to get your free API key. ### 2. Set Environment Variable Add to your shell profile (`~/.bashrc` or `~/.zshrc`): ```bash export POLLINATIONS_API_KEY="your_api_key_here" ``` Then reload: ```bash source ~/.bashrc ``` ### 3. Install Dependencies ```bash pip install requests Pillow ``` ## Security Configuration ### Required Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `POLLINATIONS_API_KEY` | Yes | Your API key from pollinations.ai | ### Optional Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `TELEGRAM_CHAT_ID` | (none) | Chat ID for Telegram sending. If not set, --telegram flag is disabled | | `ALLOWED_OUTPUT_DIRS` | `.`, `/tmp` | Comma-separated list of allowed output directories | | `OPENCLAW_WORKSPACE` | `~/.openclaw/workspace` | Workspace directory for output files | **Note:** The `--telegram` flag requires `TELEGRAM_CHAT_ID` to be explicitly set. If not set, the feature is disabled for security. ## Available Scripts | Script | Purpose | |--------|---------| | `pollinations_image_gen.py` | Generate images from text prompts | | `pollinations_audio_gen.py` | Generate music and audio | | `pollinations_video_gen.py` | Generate videos from text | ## Usage ### Image Generation ```bash # Basic usage python pollinations_image_gen.py "a cute cat" --output cat.jpg # With specific model and enhancements python pollinations_image_gen.py "cyberpunk city at night" --model flux --enhance --width 1920 --height 1080 --output city.jpg # Using different models python pollinations_image_gen.py "your prompt" --model zimage python pollinations_image_gen.py "your prompt" --model flux-2-dev # Free tier ``` **Parameters:** | Parameter | Description | Default | |-----------|-------------|---------| | `prompt` | Text description of desired image | (required) | | `--model` | AI model to use | flux | | `--width` | Image width in pixels | 1024 | | `--height` | Image height in pixels | 1024 | | `--enhance` | Enable AI prompt enhancement | false | | `--seed` | Fixed random seed for reproducibility | random | | `--safe` | Enable safe mode | false | | `--output` | Output filename | output.jpg | | `--telegram` | Send result via Telegram (requires TELEGRAM_CHAT_ID) | false | ### Music/Audio Generation ```bash # Generate music from prompt python pollinations_audio_gen.py "upbeat electronic dance music" --output music.mp3 # With custom duration python pollinations_audio_gen.py "calm piano melody" --duration 60 --output piano.mp3 ``` **Parameters:** | Parameter | Description | Default | |-----------|-------------|---------| | `prompt` | Description of desired music | (required) | | `--model` | Audio model | suno-4 | | `--duration` | Length in seconds | 30 | | `--output` | Output filename | output.mp3 | ### Video Generation ```bash # Generate video python pollinations_video_gen.py "a cat playing with a ball" --output video.mp4 # Custom duration and aspect ratio python pollinations_video_gen.py "ocean waves" --duration 30 --aspectRatio 16:9 --output ocean.mp4 ``` **Parameters:** | Parameter | Description | Default | |-----------|-------------|---------| | `prompt` | Description of desired video | (required) | | `--model` | Video model | suno-4 | | `--duration` | Length in seconds | 10 | | `--aspectRatio` | Video aspect ratio (16:9 or 9:16) | 16:9 | | `--output` | Output filename | output.mp4 | ## Supported Models ### Image Models | Model | Description | Quality | |-------|-------------|---------| | `flux` | Default high-quality model | ⭐⭐⭐⭐⭐ | | `flux-2-dev` | Latest flux (FREE) | ⭐⭐⭐⭐⭐ | | `zimage` | Fast generation | ⭐⭐⭐⭐ | | `nanobanana` | Creative/artistic | ⭐⭐⭐⭐ | | `gptimage` | OpenAI-based | ⭐⭐⭐⭐ | | `seedream` | High detail | ⭐⭐⭐⭐ | ### Audio Models | Model | Description | |-------|-------------| | `suno-4` | Latest (recommended) ⭐ | | `suno-3.5` | Previous version | | `suno-3` | Legacy model | ### Video Models | Model | Description | |-------|-------------| | `suno` | Music video generation ⭐ | | `veo-3.1` | Google video AI | | `seedance` | High quality | | `wan` | Open source option | | `ltx-2` | Fast generation | ## Security Features This skill includes several security measures: 1. **Path Traversal Protection** - All output paths are validated against allowed directories 2. **Environment Variable Validation** - Telegram sending requires explicit opt-in 3. **No Hardcoded Credentials** - All credentials come from environment variables 4. **File Access Whitelist** - Only configured directories can be written to ## Troubleshooting | Error | Cause | Solution | |-------|-------|----------| | 401 Unauthorized | Missing or invalid API key | Set `POLLINATIONS_API_KEY` correctly | | 403 Forbidden | API key lacks permission | Check your key permissions at pollinations.ai | | 429 Too Many Requests | Rate limit exceeded | Wait before making more requests | | 402 Payment Required | Insufficient balance | Add credits at pollinations.ai | ## References - API Documentation: https://gen.pollinations.ai/api/docs - Pollinations Website: https://pollinations.ai/

path: _meta.json
content: {
  "slug": "pollinations-ai",
  "name": "Pollinations AI",
  "description": "Generate images, music, and video using Pollinations AI API",
  "emoji": "🎨",
  "author": {
    "name": "kanfred"
  },
  "openclaw": {
    "version": "1.0.4",
    "emoji": "🎨",
    "requires": {
      "env": [
        "POLLINATIONS_API_KEY"
      ],
      "optionalEnv": [
        "TELEGRAM_CHAT_ID",
        "ALLOWED_OUTPUT_DIRS",
        "OPENCLAW_WORKSPACE"
      ],
      "bins": [
        "python3"
      ],
      "pip": [
        "requests>=2.28.0",
        "Pillow>=9.0.0"
      ]
    }
  },
  "tags": [
    "ai",
    "image-generation",
    "music",
    "video",
    "pollinations"
  ],
  "license": "MIT"
}


path: pollinations_audio_gen.py
content: # pollinations_audio_gen.py
import os
import random
import requests
import urllib.parse
import argparse

def sanitize_output_path(output_path, default_dir=None):
    """Sanitize output path to prevent path traversal attacks."""
    if not output_path:
        return "output.mp3"
    
    allowed_dirs = os.getenv("ALLOWED_OUTPUT_DIRS", default_dir or ".").split(",")
    allowed_dirs = [os.path.abspath(d.strip()) for d in allowed_dirs]
    
    if os.path.isabs(output_path):
        output_path = os.path.abspath(output_path)
    else:
        output_path = os.path.abspath(output_path)
    
    is_allowed = any(
        output_path.startswith(ad + os.sep) or output_path == ad 
        for ad in allowed_dirs
    )
    
    if not is_allowed:
        safe_path = os.path.join(allowed_dirs[0], "output.mp3")
        print(f"Warning: Output path not allowed. Using '{safe_path}'")
        return safe_path
    
    if ".." in output_path:
        safe_path = os.path.join(allowed_dirs[0], "output.mp3")
        print(f"Warning: Path traversal detected. Using '{safe_path}'")
        return safe_path
    
    return output_path

class PollinationsAudioGenerator:
    def __init__(self, api_key=None):
        self.[REDACTED_SECRET] or os.getenv('POLLINATIONS_API_KEY')
        self.audio_api_base_url = "https://gen.pollinations.ai"

    def generate_audio(self, prompt, model="suno-4", duration=30, seed=None, output_file="output.mp3"):
        """生成音樂"""
        if not prompt:
            raise ValueError("Prompt 唔可以係空")

        if seed is None:
            seed = random.randint(0, 1000000000)

        params = {
            "model": model,
            "duration": duration,
            "seed": seed,
        }

        if self.[REDACTED_SECRET]'key'] = self.api_key

        encoded_prompt = urllib.parse.quote(prompt)
        req = requests.Request('GET', f"{self.audio_api_base_url}/audio/{encoded_prompt}", params=params)
        prepared = req.prepare()

        # Log URL without exposing API key
        safe_url = prepared.url
        if 'key=' in safe_url:
            safe_url = safe_url.split('key=')[0] + "key=***"
        print(f"Generating audio from URL: {safe_url}")

        with requests.Session() as session:
            response = session.send(prepared)
            response.raise_for_status()

            with open(output_file, 'wb') as f:
                f.write(response.content)

            print(f"Audio successfully saved to {output_file}")
            return output_file

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Pollinations AI Audio Generator CLI")
    parser.add_argument("prompt", type=str, help="描述你想生成嘅音樂 (Prompt)")
    parser.add_argument("--model", type=str, default="suno-4",
                        choices=["suno-4", "suno-3.5", "suno-3", "whisper"],
                        help="選擇 Model (預設: suno-4)")
    parser.add_argument("--duration", type=int, default=30, help="音樂時長（秒），預設: 30")
    parser.add_argument("--seed", type=int, default=None, help="Seed 值（預設: 隨機）")
    parser.add_argument("--output", type=str, default="output.mp3", help="輸出檔案名稱（預設: output.mp3）")

    args = parser.parse_args()

    # Get workspace path and sanitize output
    workspace_path = os.getenv("OPENCLAW_WORKSPACE", os.path.expanduser("~/.openclaw/workspace"))
    default_output_dir = os.path.join(workspace_path, "outputs")
    os.makedirs(default_output_dir, exist_ok=True)
    
    args.output = sanitize_output_path(args.output, default_output_dir)

    generator = PollinationsAudioGenerator()
    generator.generate_audio(
        prompt=args.prompt,
        model=args.model,
        duration=args.duration,
        seed=args.seed,
        output_file=args.output
    )


path: pollinations_image_gen.py
content: # pollinations_image_gen.py
import os
import random
import requests
import urllib.parse
import argparse
import subprocess

def send_via_telegram(image_path, caption=None):
    """Send image via Telegram using openclaw message command"""
    if not os.path.exists(image_path):
        print(f"Error: File {image_path} not found")
        return False
    
    # Get chat ID from environment - must be explicitly set for security
    chat_id = os.getenv("TELEGRAM_CHAT_ID")
    if not chat_id:
        print("Warning: TELEGRAM_CHAT_ID not set. Telegram send skipped.")
        print("To enable Telegram sending, set TELEGRAM_CHAT_ID environment variable.")
        return False
    
    # Validate chat_id is numeric only (basic security)
    if not chat_id.isdigit():
        print("Error: TELEGRAM_CHAT_ID must be numeric")
        return False
    
    try:
        # Send to specified chat ID
        cmd = ["openclaw", "message", "send", "--target", chat_id, "--media", image_path]
        if caption:
            cmd.extend(["--message", caption])
        
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode == 0:
            print(f"Image sent via Telegram: {image_path}")
            return True
        else:
            print(f"Failed to send via Telegram: {result.stderr}")
            return False
    except Exception as e:
        print(f"Error sending via Telegram: {e}")
        return False

class PollinationsImageGenerator:
    def __init__(self, api_key=None):
        # 如果冇傳入 api_key，會自動搵環境變數 POLLINATIONS_API_KEY
        self.[REDACTED_SECRET] or os.getenv('POLLINATIONS_API_KEY')
        self.text_api_url = "https://gen.pollinations.ai/v1/chat/completions"
        self.image_api_base_url = "https://gen.pollinations.ai"

    def enhance_prompt(self, prompt):
        """利用 Pollinations 內建嘅 LLM 潤飾 Prompt"""
        if not prompt:
            raise ValueError("Prompt 唔可以係空")

        headers = {'Content-Type': 'application/json'}
        if self.[REDACTED_SECRET]'Authorization'] = f"Bearer {self.api_key}"

        payload = {
            "model": "openai",
            "messages": [
                {"role": "system", "content": "Rewrite as high-quality English image prompt. Raw text only."},
                {"role": "user", "content": prompt}
            ]
        }

        response = requests.post(self.text_api_url, json=payload, headers=headers)
        response.raise_for_status()
        
        data = response.json()
        return data['choices'][0]['message']['content']

    def generate_image(self, prompt, model="flux", width=1024, height=1024,
                       seed=None, safe=False, enhance=False, output_file="output.jpg"):
        """產生並下載圖片"""
        if not prompt:
            raise ValueError("Prompt 唔可以係空")

        if seed is None:
            seed = random.randint(0, 1000000000)

        params = {
            "model": model,
            "width": width,
            "height": height,
            "seed": seed,
            "nologo": "true",
            "safe": str(safe).lower(),
            "enhance": str(enhance).lower()
        }

        if self.[REDACTED_SECRET]'key'] = self.api_key

        encoded_prompt = urllib.parse.quote(prompt)
        # 用 requests.Request 組合 URL 方便 debug
        req = requests.Request('GET', f"{self.image_api_base_url}/image/{encoded_prompt}", params=params)
        prepared = req.prepare()
        
        # Log URL without exposing API key
        safe_url = prepared.url
        if 'key=' in safe_url:
            safe_url = safe_url.split('key=')[0] + "key=***"
        print(f"Generating image from URL: {safe_url}")

        with requests.Session() as session:
            response = session.send(prepared)
            response.raise_for_status()

            with open(output_file, 'wb') as f:
                f.write(response.content)
            
            print(f"Image successfully saved to {output_file}")
            return output_file

def sanitize_output_path(output_path, default_dir=None):
    """
    Sanitize output path to prevent path traversal attacks.
    Only allows relative paths or paths within allowed directories.
    """
    if not output_path:
        return "output.jpg"
    
    # Get allowed directories from environment or use default
    allowed_dirs = os.getenv("ALLOWED_OUTPUT_DIRS", default_dir or ".").split(",")
    allowed_dirs = [os.path.abspath(d.strip()) for d in allowed_dirs]
    
    # Resolve the output path
    if os.path.isabs(output_path):
        output_path = os.path.abspath(output_path)
    else:
        output_path = os.path.abspath(output_path)
    
    # Check if path is within allowed directories
    is_allowed = any(
        output_path.startswith(ad + os.sep) or output_path == ad 
        for ad in allowed_dirs
    )
    
    if not is_allowed:
        # Default to first allowed directory
        safe_path = os.path.join(allowed_dirs[0], "output.jpg")
        print(f"Warning: Output path '{output_path}' not allowed. Using '{safe_path}'")
        return safe_path
    
    # Prevent path traversal with ..
    if ".." in output_path:
        safe_path = os.path.join(allowed_dirs[0], "output.jpg")
        print(f"Warning: Path traversal detected. Using '{safe_path}'")
        return safe_path
    
    return output_path

if __name__ == "__main__":
    # Get workspace path from environment or use default
    workspace_path = os.getenv("OPENCLAW_WORKSPACE", os.path.expanduser("~/.openclaw/workspace"))
    default_output_dir = os.path.join(workspace_path, "outputs")
    
    # Create outputs directory if not exists
    os.makedirs(default_output_dir, exist_ok=True)
    parser = argparse.ArgumentParser(description="Pollinations AI Image Generator CLI")
    parser.add_argument("prompt", type=str, help="描述你想產生嘅圖片 (Prompt)")
    parser.add_argument("--enhance", action="store_true", help="使用 AI 潤飾 Prompt")
    parser.add_argument("--model", type=str, default="flux",
                        choices=["flux", "flux-2-dev", "zimage", "gptimage", "gptimage-large", "kontext",
                                 "seedream", "seedream-pro", "seedream5", "nanobanana", "nanobanana-2", "nanobanana-pro",
                                 "klein", "klein-large", "imagen-4", "grok-imagine"],
                        help="選擇 Model (預設: flux)")
    parser.add_argument("--width", type=int, default=1024, help="圖片闊度")
    parser.add_argument("--height", type=int, default=1024, help="圖片高度")
    parser.add_argument("--seed", type=int, default=None, help="Seed 值 (預設隨機)")
    parser.add_argument("--safe", action="store_true", help="開啟 Safe Mode")
    parser.add_argument("--output", type=str, default="output.jpg", help="輸出檔案名稱 (預設: output.jpg)")
    parser.add_argument("--telegram", action="store_true", help="生成後透過 Telegram 發送圖片")

    args = parser.parse_args()

    generator = PollinationsImageGenerator()
    
    final_prompt = args.prompt
    if args.enhance:
        print("Enhancing prompt...")
        final_prompt = generator.enhance_prompt(args.prompt)
        print(f"Enhanced Prompt: {final_prompt}")

    # Sanitize output path before generating
    args.output = sanitize_output_path(args.output, default_output_dir)

    generator.generate_image(
        prompt=final_prompt,
        model=args.model,
        width=args.width,
        height=args.height,
        seed=args.seed,
        safe=args.safe,
        output_file=args.output
    )
    
    # 如果開啟 --telegram flag，發送圖片到 Telegram
    if args.telegram:
        # Get workspace path from environment
        workspace_path = os.getenv("OPENCLAW_WORKSPACE", os.path.expanduser("~/.openclaw/workspace"))
        
        # If output is in /tmp, copy to workspace outputs folder
        if args.output.startswith("/tmp"):
            import shutil
            safe_output = os.path.join(workspace_path, "outputs", os.path.basename(args.output))
            os.makedirs(os.path.dirname(safe_output), exist_ok=True)
            shutil.copy(args.output, safe_output)
            args.output = safe_output
            print(f"Copied to allowed directory: {safe_output}")
        
        caption = f"🎨 {args.model.upper()} - {final_prompt[:100]}{'...' if len(final_prompt) > 100 else ''}"
        send_via_telegram(args.output, caption)


path: pollinations_video_gen.py
content: # pollinations_video_gen.py
import os
import random
import requests
import urllib.parse
import argparse

def sanitize_output_path(output_path, default_dir=None):
    """Sanitize output path to prevent path traversal attacks."""
    if not output_path:
        return "output.mp4"
    
    allowed_dirs = os.getenv("ALLOWED_OUTPUT_DIRS", default_dir or ".").split(",")
    allowed_dirs = [os.path.abspath(d.strip()) for d in allowed_dirs]
    
    if os.path.isabs(output_path):
        output_path = os.path.abspath(output_path)
    else:
        output_path = os.path.abspath(output_path)
    
    is_allowed = any(
        output_path.startswith(ad + os.sep) or output_path == ad 
        for ad in allowed_dirs
    )
    
    if not is_allowed:
        safe_path = os.path.join(allowed_dirs[0], "output.mp4")
        print(f"Warning: Output path not allowed. Using '{safe_path}'")
        return safe_path
    
    if ".." in output_path:
        safe_path = os.path.join(allowed_dirs[0], "output.mp4")
        print(f"Warning: Path traversal detected. Using '{safe_path}'")
        return safe_path
    
    return output_path

class PollinationsVideoGenerator:
    def __init__(self, api_key=None):
        self.[REDACTED_SECRET] or os.getenv('POLLINATIONS_API_KEY')
        self.video_api_base_url = "https://gen.pollinations.ai"

    def generate_video(self, prompt, model="suno-4", duration=30, aspect_ratio="16:9",
                      seed=None, audio=False, output_file="output.mp4"):
        """生成視頻"""
        if not prompt:
            raise ValueError("Prompt 唔可以係空")

        if seed is None:
            seed = random.randint(0, 1000000000)

        params = {
            "model": model,
            "duration": duration,
            "aspectRatio": aspect_ratio,
            "audio": str(audio).lower(),
            "seed": seed,
        }

        if self.[REDACTED_SECRET]'key'] = self.api_key

        encoded_prompt = urllib.parse.quote(prompt)
        req = requests.Request('GET', f"{self.video_api_base_url}/video/{encoded_prompt}", params=params)
        prepared = req.prepare()

        # Log URL without exposing API key
        safe_url = prepared.url
        if 'key=' in safe_url:
            safe_url = safe_url.split('key=')[0] + "key=***"
        print(f"Generating video from URL: {safe_url}")

        with requests.Session() as session:
            response = session.send(prepared)
            response.raise_for_status()

            with open(output_file, 'wb') as f:
                f.write(response.content)

            print(f"Video successfully saved to {output_file}")
            return output_file

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Pollinations AI Video Generator CLI")
    parser.add_argument("prompt", type=str, help="描述你想生成嘅視頻 (Prompt)")
    parser.add_argument("--model", type=str, default="suno-4",
                        choices=["grok-video", "suno", "suno-3.5", "veo", "veo-3.1", "seedance", "seedance-lite", "seedance-pro", "seedance-pro-fast", "wan", "wan-2.6", "ltx-2"],
                        help="選擇 Model (預設: suno-4)")
    parser.add_argument("--duration", type=int, default=30, help="視頻時長（秒），預設: 30")
    parser.add_argument("--aspect", type=str, default="16:9", choices=["16:9", "9:16"], help="視頻比例（預設: 16:9）")
    parser.add_argument("--audio", action="store_true", help="生成音頻（veo 需要設置）")
    parser.add_argument("--seed", type=int, default=None, help="Seed 值（預設: 隨機）")
    parser.add_argument("--output", type=str, default="output.mp4", help="輸出檔案名稱（預設: output.mp4）")

    args = parser.parse_args()

    # Get workspace path and sanitize output
    workspace_path = os.getenv("OPENCLAW_WORKSPACE", os.path.expanduser("~/.openclaw/workspace"))
    default_output_dir = os.path.join(workspace_path, "outputs")
    os.makedirs(default_output_dir, exist_ok=True)
    
    args.output = sanitize_output_path(args.output, default_output_dir)

    generator = PollinationsVideoGenerator()
    generator.generate_video(
        prompt=args.prompt,
        model=args.model,
        duration=args.duration,
        aspect_ratio=args.aspect,
        audio=args.audio,
        seed=args.seed,
        output_file=args.output
    )


path: requirements.txt
content: requests
Pillow


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