opencode-pollinations-plugin 6.3.0 → 6.4.0
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/README.de.md +2 -2
- package/README.es.md +2 -2
- package/README.fr.md +2 -2
- package/README.it.md +2 -2
- package/README.md +1 -1
- package/README.zh.md +2 -2
- package/dist/locales/de.json +3 -1
- package/dist/locales/en.json +3 -1
- package/dist/locales/es.json +3 -1
- package/dist/locales/fr.json +3 -1
- package/dist/locales/it.json +3 -1
- package/dist/locales/zh.json +3 -1
- package/dist/server/commands.js +69 -1
- package/package.json +1 -1
package/README.de.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin für OpenCode (v6.
|
|
1
|
+
# 🌸 Pollinations AI Plugin für OpenCode (v6.4.0)
|
|
2
2
|
|
|
3
|
-
## ✨ Neu in v6.
|
|
3
|
+
## ✨ Neu in v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Quests & Gamification** : `polli_quests` + `/poll quests` — sieh deine Quests und das **einlösbare kostenlose Pollen**. Die Nutzung des Plugins schließt Quests rückwirkend ab!
|
|
6
6
|
- 🆓 **Kostenlose Bild- & Videogenerierung (ohne Schlüssel!)** : `gen_edit_image_free` (erstellen + bearbeiten, ~20/Tag) und `gen_video_free` (Text→Video + Bild/Audio, ~5/Tag), für jedes Modell.
|
package/README.es.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin para OpenCode (v6.
|
|
1
|
+
# 🌸 Pollinations AI Plugin para OpenCode (v6.4.0)
|
|
2
2
|
|
|
3
|
-
## ✨ Novedades v6.
|
|
3
|
+
## ✨ Novedades v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Misiones y Gamificación** : `polli_quests` + `/poll quests` — mira tus misiones y el **Pollen gratis por reclamar**. ¡Usar el plugin completa misiones retroactivamente!
|
|
6
6
|
- 🆓 **Imagen y Vídeo gratis (¡sin clave!)** : `gen_edit_image_free` (generar + editar, ~20/día) y `gen_video_free` (texto→vídeo + imagen/audio, ~5/día), para cualquier modelo.
|
package/README.fr.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Plugin Pollinations AI pour OpenCode (v6.
|
|
1
|
+
# 🌸 Plugin Pollinations AI pour OpenCode (v6.4.0)
|
|
2
2
|
|
|
3
|
-
## ✨ Nouveautés v6.
|
|
3
|
+
## ✨ Nouveautés v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Quêtes & Gamification** : `polli_quests` + `/poll quests` — voyez vos quêtes et le **Pollen gratuit à réclamer**. Utiliser le plugin complète des quêtes rétroactivement !
|
|
6
6
|
- 🆓 **Image & Vidéo gratuites (sans clé !)** : `gen_edit_image_free` (génération + édition, ~20/j) et `gen_video_free` (texte→vidéo + image/audio, ~5/j), pour n'importe quel modèle.
|
package/README.it.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin per OpenCode (v6.
|
|
1
|
+
# 🌸 Pollinations AI Plugin per OpenCode (v6.4.0)
|
|
2
2
|
|
|
3
|
-
## ✨ Novità v6.
|
|
3
|
+
## ✨ Novità v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Quest e Gamification** : `polli_quests` + `/poll quests` — guarda le tue quest e il **Pollen gratuito da riscattare**. Usare il plugin completa quest retroattivamente!
|
|
6
6
|
- 🆓 **Immagini e Video gratis (senza chiave!)** : `gen_edit_image_free` (genera + modifica, ~20/giorno) e `gen_video_free` (testo→video + immagine/audio, ~5/giorno), per qualsiasi modello.
|
package/README.md
CHANGED
package/README.zh.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI 插件 - OpenCode版 (v6.
|
|
1
|
+
# 🌸 Pollinations AI 插件 - OpenCode版 (v6.4.0)
|
|
2
2
|
|
|
3
|
-
## ✨ v6.
|
|
3
|
+
## ✨ v6.4.0 新功能
|
|
4
4
|
|
|
5
5
|
- 🎯 **任务与游戏化** :`polli_quests` + `/poll quests` — 查看你的任务和**可领取的免费 Pollen**。使用本插件会追溯性地完成多个任务!
|
|
6
6
|
- 🆓 **免费图像和视频 (无需密钥!)** :`gen_edit_image_free` (生成 + 编辑,约 20/天) 和 `gen_video_free` (文本→视频 + 图像/音频,约 5/天),适用于任何模型。
|
package/dist/locales/de.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|--------|-------|--------|--------|\n",
|
|
63
63
|
"no_history": "*Keine Aktivität in diesem Zeitraum.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` erfordert einen API-Schlüssel.\n",
|
|
65
|
-
"hint_full": "💡 Verwenden Sie `/poll usage full` für Details."
|
|
65
|
+
"hint_full": "💡 Verwenden Sie `/poll usage full` für Details.",
|
|
66
|
+
"source_split": "- **Diese Periode verbraucht:** 🎁 Quest {tier} · 💎 Bezahlt {pack}\n",
|
|
67
|
+
"split": "- **Guthaben:** {total} 🌻 · 🎁 Quest ~{quest} · 💎 Bezahlt ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/en.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|-------|------|------|--------|\n",
|
|
63
63
|
"no_history": "*No activity in the current period.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` requires a connected API key.\n",
|
|
65
|
-
"hint_full": "💡 Use `/poll usage full` for details (requires key)."
|
|
65
|
+
"hint_full": "💡 Use `/poll usage full` for details (requires key).",
|
|
66
|
+
"source_split": "- **Consumed this period:** 🎁 Quest {tier} · 💎 Paid {pack}\n",
|
|
67
|
+
"split": "- **Balance:** {total} 🌻 · 🎁 Quest ~{quest} · 💎 Paid ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/es.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|--------|--------|-------|--------|\n",
|
|
63
63
|
"no_history": "*Sin actividad en el período actual.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` requiere una clave API conectada.\n",
|
|
65
|
-
"hint_full": "💡 Usa `/poll usage full` para ver detalles (requiere clave)."
|
|
65
|
+
"hint_full": "💡 Usa `/poll usage full` para ver detalles (requiere clave).",
|
|
66
|
+
"source_split": "- **Consumido este período:** 🎁 Misiones {tier} · 💎 Pagado {pack}\n",
|
|
67
|
+
"split": "- **Saldo:** {total} 🌻 · 🎁 Misiones ~{quest} · 💎 Pagado ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/fr.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|--------|------|------|--------|\n",
|
|
63
63
|
"no_history": "*Aucune activité dans la période en cours.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` nécessite une clé API connectée.\n",
|
|
65
|
-
"hint_full": "💡 Utilisez `/poll usage full` pour les détails (nécessite une clé)."
|
|
65
|
+
"hint_full": "💡 Utilisez `/poll usage full` pour les détails (nécessite une clé).",
|
|
66
|
+
"source_split": "- **Consommé cette période :** 🎁 Quêtes {tier} · 💎 Payé {pack}\n",
|
|
67
|
+
"split": "- **Solde :** {total} 🌻 · 🎁 Quêtes ~{quest} · 💎 Payé ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Fallbacks actuels:\n- {free}\n- {enter}",
|
package/dist/locales/it.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|---------|-------|-------|-------|\n",
|
|
63
63
|
"no_history": "*Nessuna attività in questo periodo.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` richiede una chiave API.\n",
|
|
65
|
-
"hint_full": "💡 Usa `/poll usage full` per i dettagli."
|
|
65
|
+
"hint_full": "💡 Usa `/poll usage full` per i dettagli.",
|
|
66
|
+
"source_split": "- **Consumato questo periodo:** 🎁 Quest {tier} · 💎 Pagato {pack}\n",
|
|
67
|
+
"split": "- **Saldo:** {total} 🌻 · 🎁 Quest ~{quest} · 💎 Pagato ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/zh.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|-------|------|------|--------|\n",
|
|
63
63
|
"no_history": "*当前周期内无活动。*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` 需要已连接的 API 密钥。\n",
|
|
65
|
-
"hint_full": "💡 使用 `/poll usage full` 查看详情 (需要密钥)。"
|
|
65
|
+
"hint_full": "💡 使用 `/poll usage full` 查看详情 (需要密钥)。",
|
|
66
|
+
"source_split": "- **本期已消耗:** 🎁 任务 {tier} · 💎 付费 {pack}\n",
|
|
67
|
+
"split": "- **余额:** {total} 🌻 · 🎁 任务 ~{quest} · 💎 付费 ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "当前回退:\n- {free}\n- {enter}",
|
package/dist/server/commands.js
CHANGED
|
@@ -7,6 +7,27 @@ import { ModelRegistry } from './models/index.js';
|
|
|
7
7
|
import { t } from '../locales/index.js';
|
|
8
8
|
import { formatTierTable } from './tier-info.js';
|
|
9
9
|
import { buildQuestsReport } from '../tools/pollinations/polli_quests.js';
|
|
10
|
+
// GET helper for /account/* JSON endpoints (used for quest-reward reconstruction).
|
|
11
|
+
function fetchAccountJson(path, apiKey) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const req = https.request({
|
|
14
|
+
hostname: 'gen.pollinations.ai', path, method: 'GET',
|
|
15
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'User-Agent': 'opencode-pollinations-plugin' },
|
|
16
|
+
}, (res) => {
|
|
17
|
+
let data = '';
|
|
18
|
+
res.on('data', c => data += c);
|
|
19
|
+
res.on('end', () => { try {
|
|
20
|
+
resolve(JSON.parse(data));
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
reject(e);
|
|
24
|
+
} });
|
|
25
|
+
});
|
|
26
|
+
req.on('error', reject);
|
|
27
|
+
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout')); });
|
|
28
|
+
req.end();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
10
31
|
function checkEndpoint(ep, key) {
|
|
11
32
|
return new Promise((resolve) => {
|
|
12
33
|
const req = https.request({
|
|
@@ -241,7 +262,48 @@ export async function handleUsageCommand(args) {
|
|
|
241
262
|
response += t('commands.usage.tier', { emoji: quota.tierEmoji, tier: quota.tier.toUpperCase(), limit: quota.tierLimit });
|
|
242
263
|
response += t('commands.usage.quota', { remaining: formatPollen(quota.tierLimit - quota.tierRemaining), limit: formatPollen(quota.tierLimit) });
|
|
243
264
|
response += t('commands.usage.usage_bar', { bar: progressBar(quota.tierLimit - quota.tierRemaining, quota.tierLimit) });
|
|
244
|
-
|
|
265
|
+
// Reconstructed Quest/Paid split by CROSS-REFERENCING exact data (no magic field needed):
|
|
266
|
+
// • claimed quest rewards (tier bucket) ← /account/quests (exact)
|
|
267
|
+
// • tier consumption since the first claim ← /account/usage meter_source=='tier' (exact)
|
|
268
|
+
// • current-hour floor remaining (≤ tierLimit) ← quota (the only fuzzy term, ignored)
|
|
269
|
+
// Quest remaining ≈ claimedQuestTier − tierConsumedSinceClaim (+ floor); Paid = Total − Quest.
|
|
270
|
+
const total = quota.tierRemaining + quota.walletBalance; // true balance
|
|
271
|
+
let claimedQuestTier = 0;
|
|
272
|
+
let firstClaimMs = Infinity;
|
|
273
|
+
let tierConsumedSinceClaim = 0;
|
|
274
|
+
if (config.apiKey && config.keyHasAccessToProfile !== false) {
|
|
275
|
+
try {
|
|
276
|
+
const qres = await fetchAccountJson('/account/quests', config.apiKey);
|
|
277
|
+
for (const q of (qres?.quests || [])) {
|
|
278
|
+
const r = q.reward;
|
|
279
|
+
if (r && r.claimedAt && r.balanceBucket === 'tier') {
|
|
280
|
+
claimedQuestTier += (r.pollenAmount || 0);
|
|
281
|
+
const cms = new Date(r.claimedAt).getTime();
|
|
282
|
+
if (!isNaN(cms) && cms < firstClaimMs)
|
|
283
|
+
firstClaimMs = cms;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Sum tier-metered spend since the first claim (what actually ate the Quest stash).
|
|
287
|
+
if (claimedQuestTier > 0 && isFinite(firstClaimMs)) {
|
|
288
|
+
const ures = await fetchAccountJson('/account/usage?limit=100', config.apiKey);
|
|
289
|
+
for (const e of (ures?.usage || [])) {
|
|
290
|
+
const ts = new Date(String(e.timestamp).replace(' ', 'T') + (String(e.timestamp).includes('Z') ? '' : 'Z')).getTime();
|
|
291
|
+
if (e.meter_source === 'tier' && !isNaN(ts) && ts >= firstClaimMs) {
|
|
292
|
+
tierConsumedSinceClaim += (e.cost_usd || 0);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch { /* fall back gracefully if unreachable */ }
|
|
298
|
+
}
|
|
299
|
+
// Quest stash = claimed − consumed, plus the current hourly floor still available.
|
|
300
|
+
const questPollen = Math.max(0, claimedQuestTier - tierConsumedSinceClaim) + quota.tierRemaining;
|
|
301
|
+
const paidPollen = Math.max(0, total - questPollen);
|
|
302
|
+
response += t('commands.usage.split', {
|
|
303
|
+
quest: questPollen.toFixed(2),
|
|
304
|
+
paid: paidPollen.toFixed(2),
|
|
305
|
+
total: total.toFixed(2),
|
|
306
|
+
});
|
|
245
307
|
response += t('commands.usage.reset', { date: resetDate, duration: durationStr });
|
|
246
308
|
if (isFull && config.apiKey) {
|
|
247
309
|
if (config.keyHasAccessToProfile === false) {
|
|
@@ -254,6 +316,12 @@ export async function handleUsageCommand(args) {
|
|
|
254
316
|
const stats = calculateCurrentPeriodStats(usageData, lastReset, quota.tierLimit);
|
|
255
317
|
response += t('commands.usage.period_detail', { time: lastReset.toLocaleTimeString() });
|
|
256
318
|
response += t('commands.usage.total_reqs', { reqs: stats.totalRequests, inTok: formatTokens(stats.inputTokens), outTok: formatTokens(stats.outputTokens) });
|
|
319
|
+
// Exact consumption split by meter_source (tier = Quest Pollen, pack = Paid).
|
|
320
|
+
// This is the ONLY reliable split the API exposes (remaining split is dashboard/cookie-only).
|
|
321
|
+
response += t('commands.usage.source_split', {
|
|
322
|
+
tier: formatPollen(stats.tierUsed),
|
|
323
|
+
pack: formatPollen(stats.packUsed),
|
|
324
|
+
});
|
|
257
325
|
response += t('commands.usage.table_head1');
|
|
258
326
|
response += t('commands.usage.table_head2');
|
|
259
327
|
const sorted = Array.from(stats.models.entries()).sort((a, b) => b[1].cost - a[1].cost);
|
package/package.json
CHANGED