Skill: saharcarmel/i-love-you-mom (version 1.0.0)

--- name: mixtiles-monthly description: Automated monthly photo-to-Mixtiles pipeline. Collects photos from a WhatsApp group, curates the best ones using vision, builds a multi-photo Mixtiles cart link, and sends it. Use when it's time for the monthly Mixtiles order, when asked to "run the monthly tiles", "collect family photos for tiles", or on the monthly cron trigger. metadata: {"openclaw": {"emoji": "🖼️", "requires": {"bins": ["wacli", "jq", "python3"]}}} --- # Mixtiles Monthly Pipeline Automatically collect the best family photos from a WhatsApp group each month, curate them, and generate a ready-to-order Mixtiles cart link. ## Configuration These environment variables control the pipeline. Set them before running: | Variable | Description | Default | |----------|-------------|---------| | `MIXTILES_GROUP_JID` | WhatsApp group JID to collect photos from | *(required)* | | `MIXTILES_SEND_TO` | Where to send the cart link (group JID or phone number) | Same as `MIXTILES_GROUP_JID` | | `MIXTILES_PHOTO_COUNT` | How many photos to select | `4` | | `MIXTILES_TILE_SIZE` | Tile size for the order | `RECTANGLE_12X16` | ## Pipeline Steps ### Step 1: Collect Photos Calculate the date range for last month and download all photos from the group: ```bash # Calculate first day of last month YEAR_MONTH=$(date -v-1m +%Y-%m) # macOS AFTER_DATE="${YEAR_MONTH}-01" OUTPUT_DIR=~/mixtiles-queue/${YEAR_MONTH} # Run the collection script bash <skill-dir>/scripts/collect-photos.sh "$MIXTILES_GROUP_JID" "$AFTER_DATE" "$OUTPUT_DIR" ``` The script outputs a JSON manifest on stdout with `{id, sender, timestamp, filepath}` for each downloaded photo. ### Step 2: Curate with Vision Read each downloaded photo using your vision capability. For each photo, evaluate: **Include if:** - Real family/life moment (people, gatherings, milestones, kids, travel, pets) - Good image quality (clear, well-lit, in focus) - Unique scene (not a near-duplicate of another photo) **Exclude if:** - Screenshot, meme, forwarded image, or link preview - Blurry, too dark, or very low quality - Near-duplicate of a better version already selected - Text-heavy image (WhatsApp forwards, news articles) - Promotional content or ads ### Step 3: Select Top Photos From the curated set, pick the top `$MIXTILES_PHOTO_COUNT` photos (default: 4). Prioritize: 1. People and faces (especially kids, family gatherings) 2. Milestone moments (birthdays, first steps, graduations) 3. Travel and experiences 4. Variety — don't pick 4 photos from the same event if there are others If fewer than `$MIXTILES_PHOTO_COUNT` good photos exist, use whatever passes curation. ### Step 4: Build Multi-Photo Cart Use the mixtiles-it skill's script with the `--batch` flag: ```bash MIXTILES_CART_SCRIPT="$(find ~/.openclaw/workspace/skills/mixtiles-it/scripts -name 'mixtiles-cart.py')" python3 "$MIXTILES_CART_SCRIPT" \ --batch <photo1> <photo2> <photo3> <photo4> \ --size "${MIXTILES_TILE_SIZE:-RECTANGLE_12X16}" ``` This uploads each photo to Cloudinary and outputs a single Mixtiles cart URL with all photos. ### Step 5: Send the Link Send the cart link to the target chat: ```bash SEND_TO="${MIXTILES_SEND_TO:-$MIXTILES_GROUP_JID}" wacli send text \ --to "$SEND_TO" \ --message "Your monthly tiles are ready! Here are the best ${MIXTILES_PHOTO_COUNT:-4} photos from last month. Tap to customize and order: $CART_URL" ``` ## Error Handling - If `collect-photos.sh` finds 0 photos: report that no images were found for the period and skip the pipeline. - If fewer photos pass curation than `MIXTILES_PHOTO_COUNT`: use all that passed — even 1 photo is worth sending. - If Cloudinary upload fails for a photo: skip that photo, continue with the rest. - If `wacli send` fails: print the cart URL so the user can send it manually. ## Manual Trigger To run the pipeline outside the monthly schedule: > Run the mixtiles-monthly skill: collect photos from the family group for the past month, curate the best ones, build a multi-photo cart link, and send it.

path: scripts/collect-photos.sh
content: #!/usr/bin/env bash
# collect-photos.sh — Batch-download photos from a WhatsApp group via wacli.
#
# Usage: collect-photos.sh <group-jid> <after-date> <output-dir>
#
# Arguments:
#   group-jid   — WhatsApp group JID (e.g. [REDACTED_SECRET])
#   after-date  — Only photos after this date (YYYY-MM-DD)
#   output-dir  — Directory to save downloaded photos
#
# Output: JSON manifest on stdout — array of {id, sender, timestamp, filepath}
#
# Requires: wacli, jq

set -euo pipefail

GROUP_JID="${1:?Usage: collect-photos.sh <group-jid> <after-date> <output-dir>}"
AFTER_DATE="${2:?Usage: collect-photos.sh <group-jid> <after-date> <output-dir>}"
OUTPUT_DIR="${3:?Usage: collect-photos.sh <group-jid> <after-date> <output-dir>}"

# Ensure output dir exists
mkdir -p "$OUTPUT_DIR"

# 1. Search for image messages in the group after the given date
echo "[collect] Searching for images in $GROUP_JID after $AFTER_DATE..." >&2
MESSAGES_JSON=$(wacli messages search "*" \
  --chat "$GROUP_JID" \
  --type image \
  --after "$AFTER_DATE" \
  --limit 100 \
  --json 2>/dev/null)

# Parse message count
MSG_COUNT=$(echo "$MESSAGES_JSON" | jq 'length')
echo "[collect] Found $MSG_COUNT image messages" >&2

if [ "$MSG_COUNT" -eq 0 ]; then
  echo "[]"
  exit 0
fi

# 2. Download each photo and build manifest
MANIFEST="[]"
DOWNLOADED=0
FAILED=0

echo "$MESSAGES_JSON" | jq -c '.[]' | while IFS= read -r msg; do
  MSG_ID=$(echo "$msg" | jq -r '.id // .messageId // .ID')
  SENDER=$(echo "$msg" | jq -r '.sender // .from // .Sender // ""')
  TIMESTAMP=$(echo "$msg" | jq -r '.timestamp // .Timestamp // ""')

  if [ -z "$MSG_ID" ] || [ "$MSG_ID" = "null" ]; then
    echo "[collect] Skipping message with no ID" >&2
    continue
  fi

  echo "[collect] Downloading media for message $MSG_ID..." >&2
  DL_RESULT=$(wacli media download \
    --chat "$GROUP_JID" \
    --id "$MSG_ID" \
    --output "$OUTPUT_DIR" \
    --json 2>/dev/null) || {
    echo "[collect] Failed to download $MSG_ID, skipping" >&2
    FAILED=$((FAILED + 1))
    continue
  }

  # Try to extract the file path from download result
  FILEPATH=$(echo "$DL_RESULT" | jq -r '.path // .filePath // .file // ""' 2>/dev/null)

  # If JSON parsing fails, check if the output dir has a new file
  if [ -z "$FILEPATH" ] || [ "$FILEPATH" = "null" ]; then
    # Fallback: find most recently modified file in output dir
    FILEPATH=$(ls -t "$OUTPUT_DIR"/* 2>/dev/null | head -1)
  fi

  if [ -n "$FILEPATH" ] && [ -f "$FILEPATH" ]; then
    DOWNLOADED=$((DOWNLOADED + 1))
    echo "[collect] Downloaded: $FILEPATH" >&2
    # Append to manifest (output to stdout at the end)
    echo "{\"id\":\"$MSG_ID\",\"sender\":\"$SENDER\",\"timestamp\":\"$TIMESTAMP\",\"filepath\":\"$FILEPATH\"}"
  else
    echo "[collect] No file found for $MSG_ID" >&2
    FAILED=$((FAILED + 1))
  fi
done | jq -s '.'

echo "[collect] Done: downloaded files from $GROUP_JID" >&2


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