opencode-pollinations-plugin 6.4.5 → 6.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/locales/de.json +24 -9
- package/dist/locales/en.json +24 -9
- package/dist/locales/es.json +24 -9
- package/dist/locales/fr.json +23 -8
- package/dist/locales/it.json +24 -9
- package/dist/locales/zh.json +24 -9
- package/dist/server/commands.js +17 -10
- package/dist/server/connect-response.js +15 -9
- package/dist/server/quota.js +10 -10
- package/dist/server/toast.d.ts +1 -0
- package/dist/server/toast.js +7 -2
- package/dist/tools/index.js +8 -0
- package/dist/tools/pollinations/gen_edit_image_free.js +4 -3
- package/dist/tools/pollinations/gen_video_free.js +3 -2
- package/dist/tools/pollinations/image_enhancer.d.ts +2 -0
- package/dist/tools/pollinations/image_enhancer.js +62 -0
- package/dist/tools/pollinations/image_upscaler.d.ts +2 -0
- package/dist/tools/pollinations/image_upscaler.js +61 -0
- package/dist/tools/pollinations/imgtools/clients.d.ts +11 -0
- package/dist/tools/pollinations/imgtools/clients.js +182 -0
- package/dist/tools/pollinations/imgtools/config.d.ts +42 -0
- package/dist/tools/pollinations/imgtools/config.js +81 -0
- package/dist/tools/pollinations/imgtools/crypto.d.ts +4 -0
- package/dist/tools/pollinations/imgtools/crypto.js +34 -0
- package/dist/tools/pollinations/imgtools/helpers.d.ts +24 -0
- package/dist/tools/pollinations/imgtools/helpers.js +111 -0
- package/dist/tools/pollinations/object_remover.d.ts +2 -0
- package/dist/tools/pollinations/object_remover.js +62 -0
- package/dist/tools/power/remove_background.js +16 -60
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// imgtools/helpers.ts — HTTP, multipart, image dimensions
|
|
2
|
+
// Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/helpers.js
|
|
3
|
+
import * as https from 'https';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
export function sleep(ms) {
|
|
6
|
+
return new Promise(r => setTimeout(r, ms));
|
|
7
|
+
}
|
|
8
|
+
export function buildMultipart(fields) {
|
|
9
|
+
const boundary = '----WebKitFormBoundary' + crypto.randomBytes(16).toString('hex');
|
|
10
|
+
const parts = [];
|
|
11
|
+
for (const f of fields) {
|
|
12
|
+
let h = `--${boundary}\r\nContent-Disposition: form-data; name="${f.name}"`;
|
|
13
|
+
if (f.filename) {
|
|
14
|
+
h += `; filename="${f.filename}"\r\nContent-Type: ${f.contentType || 'application/octet-stream'}\r\n\r\n`;
|
|
15
|
+
parts.push(Buffer.from(h));
|
|
16
|
+
parts.push(typeof f.value === 'string' ? Buffer.from(f.value) : f.value);
|
|
17
|
+
parts.push(Buffer.from('\r\n'));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
h += `\r\n\r\n${f.value}\r\n`;
|
|
21
|
+
parts.push(Buffer.from(h));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
25
|
+
return { boundary, body: Buffer.concat(parts) };
|
|
26
|
+
}
|
|
27
|
+
export function httpsPost(host, path, headers, body) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const req = https.request({
|
|
30
|
+
hostname: host,
|
|
31
|
+
path,
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Length': Buffer.byteLength(body),
|
|
35
|
+
...headers,
|
|
36
|
+
},
|
|
37
|
+
timeout: 120000,
|
|
38
|
+
}, (res) => {
|
|
39
|
+
const chunks = [];
|
|
40
|
+
res.on('data', c => chunks.push(c));
|
|
41
|
+
res.on('end', () => {
|
|
42
|
+
resolve({
|
|
43
|
+
status: res.statusCode || 0,
|
|
44
|
+
body: Buffer.concat(chunks),
|
|
45
|
+
headers: res.headers,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
req.on('error', reject);
|
|
50
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
51
|
+
if (typeof body === 'string')
|
|
52
|
+
req.write(body);
|
|
53
|
+
else
|
|
54
|
+
req.write(body);
|
|
55
|
+
req.end();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function getDims(buf) {
|
|
59
|
+
try {
|
|
60
|
+
// PNG
|
|
61
|
+
if (buf[0] === 0x89 && buf[1] === 0x50) {
|
|
62
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
63
|
+
}
|
|
64
|
+
// JPEG
|
|
65
|
+
if (buf[0] === 0xff && buf[1] === 0xd8) {
|
|
66
|
+
let offset = 2;
|
|
67
|
+
while (offset < buf.length) {
|
|
68
|
+
if (buf[offset] !== 0xff)
|
|
69
|
+
break;
|
|
70
|
+
const marker = buf[offset + 1];
|
|
71
|
+
if (marker === 0xc0 || marker === 0xc2) {
|
|
72
|
+
return { width: buf.readUInt16BE(offset + 7), height: buf.readUInt16BE(offset + 5) };
|
|
73
|
+
}
|
|
74
|
+
offset += 2 + buf.readUInt16BE(offset + 2);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function aspectRatio(w, h) {
|
|
84
|
+
if (w === 0 || h === 0)
|
|
85
|
+
return '1:1';
|
|
86
|
+
const r = w / h;
|
|
87
|
+
if (r > 1.7)
|
|
88
|
+
return '16:9';
|
|
89
|
+
if (r > 1.2)
|
|
90
|
+
return '4:3';
|
|
91
|
+
if (r > 0.8)
|
|
92
|
+
return '1:1';
|
|
93
|
+
if (r > 0.55)
|
|
94
|
+
return '3:4';
|
|
95
|
+
return '9:16';
|
|
96
|
+
}
|
|
97
|
+
export async function dlImage(url) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
https.get(url, { timeout: 15000 }, (res) => {
|
|
100
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
101
|
+
return reject(new Error(`Download failed: ${res.statusCode}`));
|
|
102
|
+
}
|
|
103
|
+
const chunks = [];
|
|
104
|
+
res.on('data', c => chunks.push(c));
|
|
105
|
+
res.on('end', () => resolve({
|
|
106
|
+
body: Buffer.concat(chunks),
|
|
107
|
+
contentType: res.headers['content-type'] || 'image/png',
|
|
108
|
+
}));
|
|
109
|
+
}).on('error', reject);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// object_remover — Suppression d'objets par prompt via objectremover.com
|
|
2
|
+
// Standalone : appel direct depuis l'IP utilisateur, pas d'API, pas de clé
|
|
3
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { ensureDir, generateFilename, getDefaultOutputDir, formatFileSize, sanitizeFilename, httpsGet, } from './shared.js';
|
|
7
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
8
|
+
import { t } from '../../locales/index.js';
|
|
9
|
+
import { processTool } from './imgtools/clients.js';
|
|
10
|
+
export const objectRemoverTool = tool({
|
|
11
|
+
description: `Supprime un objet d'une image via un prompt (gratuit, appel direct).
|
|
12
|
+
Exemples : "remove the person", "erase the text", "delete the car".
|
|
13
|
+
Temps de traitement : 30-120s. Pas de clé requise.`,
|
|
14
|
+
args: {
|
|
15
|
+
file: tool.schema.string().describe('Chemin local de l\'image à traiter'),
|
|
16
|
+
prompt: tool.schema.string().describe('Description de l\'objet à supprimer (ex: "remove the red car")'),
|
|
17
|
+
save_to: tool.schema.string().optional().describe('Dossier de sortie'),
|
|
18
|
+
filename: tool.schema.string().optional().describe('Nom du fichier de sortie (sans extension)'),
|
|
19
|
+
},
|
|
20
|
+
async execute(args, context) {
|
|
21
|
+
const imagePath = args.file;
|
|
22
|
+
if (!fs.existsSync(imagePath)) {
|
|
23
|
+
return t('tools.object_remover.file_not_found', { path: imagePath }) || `❌ Fichier introuvable : ${imagePath}`;
|
|
24
|
+
}
|
|
25
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
26
|
+
const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
|
|
27
|
+
const imageData = fs.readFileSync(imagePath);
|
|
28
|
+
const prompt = args.prompt || 'remove unwanted objects';
|
|
29
|
+
context.metadata({ title: '🧹 object_remover', metadata: { type: 'info', message: `Suppression de "${prompt}"...` } });
|
|
30
|
+
try {
|
|
31
|
+
const result = await processTool('ruo', {
|
|
32
|
+
data: imageData,
|
|
33
|
+
contentType: mimeType,
|
|
34
|
+
filename: path.basename(imagePath),
|
|
35
|
+
options: { prompt },
|
|
36
|
+
});
|
|
37
|
+
if (!result.imageUrl) {
|
|
38
|
+
return t('tools.object_remover.no_result') || '❌ Aucun résultat reçu.';
|
|
39
|
+
}
|
|
40
|
+
const dl = await httpsGet(result.imageUrl);
|
|
41
|
+
const outputDir = args.save_to ? args.save_to : getDefaultOutputDir('object_remover');
|
|
42
|
+
const outputFilename = (args.filename ? sanitizeFilename(args.filename) : generateFilename('ruo', 'object-remover', 'png'));
|
|
43
|
+
const filePath = path.join(outputDir, outputFilename.includes('.') ? outputFilename : `${outputFilename}.png`);
|
|
44
|
+
ensureDir(outputDir);
|
|
45
|
+
fs.writeFileSync(filePath, dl.data);
|
|
46
|
+
emitStatusToast("success", "🧹 Objet supprimé", "object_remover", { filePath, freeTool: true });
|
|
47
|
+
emitStatusToast('success', '🧹 Objet supprimé', 'object_remover', { filePath, freeTool: true });
|
|
48
|
+
const fileSize = fs.statSync(filePath).size;
|
|
49
|
+
const lines = [];
|
|
50
|
+
lines.push('🧹 **Objet Supprimé**');
|
|
51
|
+
lines.push('━━━━━━━━━━━━━━━━━━');
|
|
52
|
+
lines.push(`Fichier : \`${filePath}\``);
|
|
53
|
+
lines.push(`Taille : ${formatFileSize(fileSize)}`);
|
|
54
|
+
lines.push(`Prompt : ${prompt}`);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
emitStatusToast("warning", "❌ " + (err.message?.substring(0, 80) || ""), "object_remover", { freeTool: true });
|
|
59
|
+
return "❌ Erreur : " + err.message;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -4,12 +4,11 @@ import * as https from 'https';
|
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { resolveOutputDir, formatFileSize, TOOL_DIRS } from '../shared.js';
|
|
7
|
-
import { sanitizeFilename } from '../pollinations/shared.js';
|
|
7
|
+
import { sanitizeFilename, httpsGet } from '../pollinations/shared.js';
|
|
8
|
+
import { processTool } from '../pollinations/imgtools/clients.js';
|
|
8
9
|
import { getConfigDir } from '../../server/config.js';
|
|
9
10
|
// ─── Provider Defaults ───────────────────────────────────────────────────────
|
|
10
|
-
const CUT_API_URL = 'https://cut.esprit-artificiel.com';
|
|
11
11
|
const BACKGROUNDCUT_API_URL = 'https://backgroundcut.co/api/v1/cut/';
|
|
12
|
-
const HMAC_SECRET = "super_secret_community_key_2026"; // Sel caché dans le code transpilé
|
|
13
12
|
// ─── Key Storage ─────────────────────────────────────────────────────────────
|
|
14
13
|
const KEYS_FILE = path.join(getConfigDir(), 'backgroundcut_keys.json');
|
|
15
14
|
function loadKeys() {
|
|
@@ -103,56 +102,11 @@ function getImageSize(filePath) {
|
|
|
103
102
|
}
|
|
104
103
|
return null;
|
|
105
104
|
}
|
|
106
|
-
// ─── Provider:
|
|
107
|
-
async function
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
parts.push(imageData);
|
|
112
|
-
parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
|
|
113
|
-
const body = Buffer.concat(parts);
|
|
114
|
-
const url = new URL(`${CUT_API_URL}/remove-bg`);
|
|
115
|
-
const headers = {
|
|
116
|
-
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
117
|
-
'Content-Length': body.length,
|
|
118
|
-
'User-Agent': 'OpenCode-Pollinations-Plugin/6.1',
|
|
119
|
-
};
|
|
120
|
-
// 1. Vérifier si l'utilisateur (Franck) a configuré une clé VIP localement
|
|
121
|
-
let vipKey = null;
|
|
122
|
-
try {
|
|
123
|
-
const vipPath = path.join(getConfigDir(), 'cut_vip.json');
|
|
124
|
-
if (fs.existsSync(vipPath)) {
|
|
125
|
-
const data = JSON.parse(fs.readFileSync(vipPath, 'utf-8'));
|
|
126
|
-
if (data.vip_key)
|
|
127
|
-
vipKey = data.vip_key;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch (e) {
|
|
131
|
-
console.error(`[Cut VIP] Error loading vip key: ${e}`);
|
|
132
|
-
}
|
|
133
|
-
if (vipKey) {
|
|
134
|
-
// Mode Fast-Lane (VIP) : la requête passe directement en tête de file
|
|
135
|
-
headers['X-Api-Key'] = vipKey;
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
// Mode Communauté (Plugin public) : Génération de la signature dynamique courte durée (Anti-leech)
|
|
139
|
-
const timestamp = Date.now().toString();
|
|
140
|
-
const payloadToSign = `request-rembg-v1:${timestamp}`;
|
|
141
|
-
const signature = require('crypto')
|
|
142
|
-
.createHmac('sha256', HMAC_SECRET)
|
|
143
|
-
.update(payloadToSign)
|
|
144
|
-
.digest('hex');
|
|
145
|
-
headers['X-Cut-Timestamp'] = timestamp;
|
|
146
|
-
headers['Authorization'] = `Bearer community:${signature}`;
|
|
147
|
-
}
|
|
148
|
-
const res = await httpRequest(url.toString(), {
|
|
149
|
-
method: 'POST',
|
|
150
|
-
headers
|
|
151
|
-
}, body);
|
|
152
|
-
if (res.statusCode >= 400) {
|
|
153
|
-
throw new Error(`CUT API Error ${res.statusCode}: ${res.body.toString().substring(0, 200)}`);
|
|
154
|
-
}
|
|
155
|
-
return res.body;
|
|
105
|
+
// ─── Provider: imgtools/rmbg (bgeraser.com) ──────────────────────────────
|
|
106
|
+
async function removeViaImgtools(imageData, mimeType, filename) {
|
|
107
|
+
const result = await processTool('rmbg', { data: imageData, contentType: mimeType, filename });
|
|
108
|
+
const res = await httpsGet(result.imageUrl);
|
|
109
|
+
return res.data;
|
|
156
110
|
}
|
|
157
111
|
// ─── Provider: BackgroundCut.co (returns JSON with output_image_url) ─────────
|
|
158
112
|
async function removeViaBackgroundCut(imageData, filename, mimeType, apiKey, quality = 'medium', returnFormat = 'png', maxResolution) {
|
|
@@ -204,11 +158,11 @@ export const removeBackgroundTool = tool({
|
|
|
204
158
|
description: `Remove the background from an image, producing a transparent PNG or WebP.
|
|
205
159
|
|
|
206
160
|
**Providers:**
|
|
207
|
-
- \`
|
|
161
|
+
- \`imgtools\` (default) — rmbg via bgeraser.com. Free.
|
|
208
162
|
- \`backgroundcut\` — Premium API. Requires API key. Supports all parameters.
|
|
209
163
|
|
|
210
164
|
**Setup:** Use \`rmbg_keys\` tool to manage API keys.
|
|
211
|
-
**Auto mode:** Uses
|
|
165
|
+
**Auto mode:** Uses imgtools by default, falls back to BackgroundCut if key is available.`,
|
|
212
166
|
args: {
|
|
213
167
|
image_path: tool.schema.string().describe('Absolute path to the image file'),
|
|
214
168
|
filename: tool.schema.string().optional().describe('Custom output filename (e.g. "my_image.png") or name without extension'),
|
|
@@ -253,7 +207,7 @@ export const removeBackgroundTool = tool({
|
|
|
253
207
|
let effectiveProvider = provider;
|
|
254
208
|
if (provider === 'auto') {
|
|
255
209
|
// If we have keys, start with backgroundcut, else cut
|
|
256
|
-
effectiveProvider =
|
|
210
|
+
effectiveProvider = 'imgtools'; // Default to imgtools (free, no key needed)
|
|
257
211
|
}
|
|
258
212
|
// ── Info message when no BackgroundCut key ──
|
|
259
213
|
if (keysToCheck.length === 0 && (provider === 'auto' || provider === 'backgroundcut')) {
|
|
@@ -303,7 +257,7 @@ export const removeBackgroundTool = tool({
|
|
|
303
257
|
// ── Execute ──
|
|
304
258
|
try {
|
|
305
259
|
let resultBuffer = null;
|
|
306
|
-
let usedProvider = '
|
|
260
|
+
let usedProvider = 'imgtools'; // Default
|
|
307
261
|
let fallbackUsed = false;
|
|
308
262
|
let successKey = '';
|
|
309
263
|
// 1. Try BackgroundCut loop if applicable
|
|
@@ -353,12 +307,13 @@ export const removeBackgroundTool = tool({
|
|
|
353
307
|
else {
|
|
354
308
|
emitStatusToast('info', `Détourage via API Gratuite: ${basename}`, '✂️ Free RMBG');
|
|
355
309
|
}
|
|
356
|
-
|
|
310
|
+
emitStatusToast('info', `Détourage via rmbg (bgeraser.com): ${basename}`, '✂️ Free RMBG', { freeTool: true });
|
|
311
|
+
resultBuffer = await removeViaImgtools(imageData, mimeType, basename);
|
|
357
312
|
context.metadata({
|
|
358
313
|
title: "RMBG (Free)",
|
|
359
|
-
metadata: { type: 'success', message: "
|
|
314
|
+
metadata: { type: 'success', message: "Background removed (imgtools/rmbg)" }
|
|
360
315
|
});
|
|
361
|
-
usedProvider = '
|
|
316
|
+
usedProvider = 'imgtools';
|
|
362
317
|
}
|
|
363
318
|
if (!resultBuffer || resultBuffer.length < 100) {
|
|
364
319
|
return `❌ Background removal returned invalid data.`;
|
|
@@ -371,6 +326,7 @@ export const removeBackgroundTool = tool({
|
|
|
371
326
|
}
|
|
372
327
|
const dims = getImageSize(finalPath);
|
|
373
328
|
const dimStr = dims ? `${dims.width}×${dims.height}` : 'N/A';
|
|
329
|
+
emitStatusToast('success', `✂️ Fond supprimé · ${usedProvider}`, 'remove_background', { filePath: finalPath, freeTool: true });
|
|
374
330
|
const lines = [
|
|
375
331
|
`✂️ Background Removed`,
|
|
376
332
|
`━━━━━━━━━━━━━━━━━━━━━`,
|
package/package.json
CHANGED