← back to Grassclothwallpaper
force-crop-all-grs.js
203 lines
const fetch = require('node-fetch');
const getColors = require('get-image-colors');
const axios = require('axios');
const sharp = require('sharp');
const SUPABASE_URL = 'https://fypvpcxyfmmdhtklgbrt.supabase.co';
const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZ5cHZwY3h5Zm1tZGh0a2xnYnJ0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTg4MTAxMjMsImV4cCI6MjA3NDM4NjEyM30.fzDf6VE7jyrqLIbebAwI1NHXi50pI0b_FgQVO-6QcAk';
// Convert color to hex
function toHex(color) {
const rgb = color.rgb();
const r = Math.round(rgb[0]).toString(16).padStart(2, '0');
const g = Math.round(rgb[1]).toString(16).padStart(2, '0');
const b = Math.round(rgb[2]).toString(16).padStart(2, '0');
return `#${r}${g}${b}`.toUpperCase();
}
// Extract colors with FORCED 20% top crop - NO CONDITIONS
async function extractColorsWithCrop(imageUrl) {
try {
const response = await axios.get(imageUrl, {
responseType: 'arraybuffer',
timeout: 15000,
maxContentLength: 10 * 1024 * 1024
});
const buffer = Buffer.from(response.data);
const metadata = await sharp(buffer).metadata();
// ALWAYS crop 20% from top, 15% from bottom
const cropTop = Math.floor(metadata.height * 0.2);
const cropBottom = Math.floor(metadata.height * 0.15);
const croppedHeight = metadata.height - cropTop - cropBottom;
const croppedBuffer = await sharp(buffer)
.extract({
left: 0,
top: cropTop,
width: metadata.width,
height: croppedHeight
})
.jpeg()
.toBuffer();
const colors = await getColors(croppedBuffer, {
count: 30,
type: 'image/jpeg'
});
// Filter out whites and blacks
const hexColors = colors
.map(c => toHex(c))
.filter(hex => {
const r = parseInt(hex.substr(1, 2), 16);
const g = parseInt(hex.substr(3, 2), 16);
const b = parseInt(hex.substr(5, 2), 16);
const brightness = (r + g + b) / 3;
return brightness > 20 && brightness < 235;
});
if (hexColors.length < 2) return null;
// Find most distinct colors
let color1 = hexColors[0];
let color2 = hexColors[1];
let maxDist = 0;
for (let i = 0; i < Math.min(hexColors.length, 15); i++) {
for (let j = i + 1; j < Math.min(hexColors.length, 15); j++) {
const r1 = parseInt(hexColors[i].substr(1, 2), 16);
const g1 = parseInt(hexColors[i].substr(3, 2), 16);
const b1 = parseInt(hexColors[i].substr(5, 2), 16);
const r2 = parseInt(hexColors[j].substr(1, 2), 16);
const g2 = parseInt(hexColors[j].substr(3, 2), 16);
const b2 = parseInt(hexColors[j].substr(5, 2), 16);
const dist = Math.sqrt(
Math.pow(r2 - r1, 2) +
Math.pow(g2 - g1, 2) +
Math.pow(b2 - b1, 2)
);
if (dist > maxDist) {
maxDist = dist;
color1 = hexColors[i];
color2 = hexColors[j];
}
}
}
return { primary: color1, secondary: color2 };
} catch (error) {
return null;
}
}
async function forceCropAllGRS() {
console.log('=== FORCE CROPPING ALL GRS PRODUCTS - NO CONDITIONS ===\n');
console.log('WILL UPDATE EVERY SINGLE GRS PRODUCT WITH 20% TOP CROP\n');
console.log('=' .repeat(60) + '\n');
try {
// Get ALL GRS products
const response = await fetch(
`${SUPABASE_URL}/rest/v1/grasscloth_products?select=*&sku=like.GRS*&order=sku`,
{
headers: {
'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}`
}
}
);
if (!response.ok) {
console.error(`Failed to fetch products: ${response.status} ${response.statusText}`);
return;
}
const products = await response.json();
if (!Array.isArray(products)) {
console.error('Unexpected response from Supabase:', products);
return;
}
console.log(`Found ${products.length} GRS products\n`);
console.log('STARTING FORCED UPDATE OF ALL PRODUCTS...\n');
let processedCount = 0;
let updatedCount = 0;
let errorCount = 0;
for (const product of products) {
processedCount++;
if (!product.image_url) {
console.log(`[${processedCount}/${products.length}] ${product.sku}: NO IMAGE`);
errorCount++;
continue;
}
process.stdout.write(`[${processedCount}/${products.length}] ${product.sku}: `);
process.stdout.write(`OLD: ${product.hex_color}|${product.hex2_color} → `);
const newColors = await extractColorsWithCrop(product.image_url);
if (newColors) {
process.stdout.write(`NEW: ${newColors.primary}|${newColors.secondary} `);
// ALWAYS UPDATE - NO COMPARISON
const updateResponse = await fetch(
`${SUPABASE_URL}/rest/v1/grasscloth_products?id=eq.${product.id}`,
{
method: 'PATCH',
headers: {
'apikey': SUPABASE_KEY,
'Authorization': `Bearer ${SUPABASE_KEY}`,
'Content-Type': 'application/json',
'Prefer': 'return=minimal'
},
body: JSON.stringify({
hex_color: newColors.primary,
hex2_color: newColors.secondary
})
}
);
if (updateResponse.ok) {
console.log('✓ UPDATED');
updatedCount++;
} else {
console.log('✗ FAILED');
errorCount++;
}
} else {
console.log('ERROR EXTRACTING');
errorCount++;
}
// Progress every 10 items
if (processedCount % 10 === 0) {
const percent = Math.round((processedCount / products.length) * 100);
console.log(`\n--- ${percent}% COMPLETE | ${updatedCount} UPDATED, ${errorCount} ERRORS ---\n`);
}
}
// Final summary
console.log('\n' + '=' .repeat(70));
console.log('FORCED CROP COMPLETE');
console.log('=' .repeat(70));
console.log(`Total GRS products: ${processedCount}`);
console.log(`Successfully updated: ${updatedCount}`);
console.log(`Errors: ${errorCount}`);
console.log(`\nALL PRODUCTS HAVE BEEN PROCESSED WITH 20% TOP CROP`);
} catch (error) {
console.error('Fatal error:', error.message);
}
}
// RUN IT
forceCropAllGRS();