From 648aac86c20eb58a748a075c0a871803b9b4e7bf Mon Sep 17 00:00:00 2001 From: OpenPets Dev Date: Wed, 17 Jun 2026 06:08:53 +0000 Subject: [PATCH] feat(hardening): virtual-familiar plugin fix, TTS guard, stale placeholder cleanup - Update familiaros.virtual-familiar manifest id, permissions, and asset key to match the rebrand and the v3 SDK (familiar:* permissions). - Rename plugin asset virtual-pet.svg -> virtual-familiar.svg. - Migrate plugin code from ctx.pet to ctx.familiar and pet:clicked to familiar:clicked so speech/reactions actually work. - Update plugin locale strings from Pet to Familiar. - Add empty-text guard in speakTts to avoid speaking whitespace. - Remove stale "settings.tts.placeholder" key from all locale files., --- apps/desktop/src/i18n/locales/en.ts | 1 - apps/desktop/src/i18n/locales/es-419.ts | 1 - apps/desktop/src/i18n/locales/ja.ts | 1 - apps/desktop/src/i18n/locales/ko.ts | 1 - apps/desktop/src/i18n/locales/pt-BR.ts | 1 - apps/desktop/src/i18n/locales/zh-Hans.ts | 1 - apps/desktop/src/i18n/locales/zh-Hant.ts | 1 - apps/desktop/src/tts-service.ts | 1 + .../{virtual-pet.svg => virtual-familiar.svg} | 0 .../familiaros.plugin.json | 11 +++--- .../familiaros.virtual-familiar/index.js | 36 +++++++++---------- .../locales/en.json | 16 ++++----- .../familiaros.virtual-familiar/test.js | 8 ++--- 13 files changed, 36 insertions(+), 43 deletions(-) rename plugins/official/familiaros.virtual-familiar/assets/{virtual-pet.svg => virtual-familiar.svg} (100%) diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index cdda033b..16cb6331 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -279,7 +279,6 @@ export const en = { "settings.tts.eyebrow": "Voice", "settings.tts.title": "Text-to-Speech", "settings.tts.description": "Voice provider and controls for the familiar and floating chat.", - "settings.tts.placeholder": "TTS provider settings will appear here in the next build step.", "settings.tts.provider.title": "Provider", "settings.tts.provider.description": "Voice engine used by the familiar and floating chat.", "settings.tts.voice.title": "Voice", diff --git a/apps/desktop/src/i18n/locales/es-419.ts b/apps/desktop/src/i18n/locales/es-419.ts index 03a79124..c7b5c622 100644 --- a/apps/desktop/src/i18n/locales/es-419.ts +++ b/apps/desktop/src/i18n/locales/es-419.ts @@ -273,7 +273,6 @@ export const es419: Partial = { "settings.tts.eyebrow": "Voice", "settings.tts.title": "Text-to-Speech", "settings.tts.description": "Voice provider and controls for the familiar and floating chat.", - "settings.tts.placeholder": "TTS provider settings will appear here in the next build step.", "settings.plugins.audio.title": "Los complementos pueden reproducir sonido", "settings.plugins.audio.description": "Permite tonos, alertas y sonidos incluidos de los complementos.", "settings.plugins.voice.title": "Los complementos pueden hablar (voz)", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 172c8799..f1379454 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -273,7 +273,6 @@ export const ja: Partial = { "settings.tts.eyebrow": "Voice", "settings.tts.title": "Text-to-Speech", "settings.tts.description": "Voice provider and controls for the familiar and floating chat.", - "settings.tts.placeholder": "TTS provider settings will appear here in the next build step.", "settings.plugins.audio.title": "プラグインによる音の再生を許可", "settings.plugins.audio.description": "プラグインのチャイム、アラート、付属サウンドを許可します。", "settings.plugins.voice.title": "プラグインによる音声発話を許可", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index cebbffad..8604f326 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -273,7 +273,6 @@ export const ko: Partial = { "settings.tts.eyebrow": "Voice", "settings.tts.title": "Text-to-Speech", "settings.tts.description": "Voice provider and controls for the familiar and floating chat.", - "settings.tts.placeholder": "TTS provider settings will appear here in the next build step.", "settings.plugins.audio.title": "플러그인 소리 재생 허용", "settings.plugins.audio.description": "플러그인 알림음, 경고음, 번들 사운드를 허용합니다.", "settings.plugins.voice.title": "플러그인 음성 말하기 허용", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index ab5ee22d..049d9b3e 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -273,7 +273,6 @@ export const ptBR: Partial = { "settings.tts.eyebrow": "Voice", "settings.tts.title": "Text-to-Speech", "settings.tts.description": "Voice provider and controls for the familiar and floating chat.", - "settings.tts.placeholder": "TTS provider settings will appear here in the next build step.", "settings.plugins.audio.title": "Plugins podem reproduzir som", "settings.plugins.audio.description": "Permitir sinos, alertas e sons incluídos dos plugins.", "settings.plugins.voice.title": "Plugins podem falar (voz)", diff --git a/apps/desktop/src/i18n/locales/zh-Hans.ts b/apps/desktop/src/i18n/locales/zh-Hans.ts index 813e3eaf..6be0a534 100644 --- a/apps/desktop/src/i18n/locales/zh-Hans.ts +++ b/apps/desktop/src/i18n/locales/zh-Hans.ts @@ -273,7 +273,6 @@ export const zhHans: Partial = { "settings.tts.eyebrow": "Voice", "settings.tts.title": "Text-to-Speech", "settings.tts.description": "Voice provider and controls for the familiar and floating chat.", - "settings.tts.placeholder": "TTS provider settings will appear here in the next build step.", "settings.plugins.audio.title": "允许插件播放声音", "settings.plugins.audio.description": "允许插件播放提示音、警报和内置音效。", "settings.plugins.voice.title": "允许插件说话(语音)", diff --git a/apps/desktop/src/i18n/locales/zh-Hant.ts b/apps/desktop/src/i18n/locales/zh-Hant.ts index df6366fa..30ad9b7c 100644 --- a/apps/desktop/src/i18n/locales/zh-Hant.ts +++ b/apps/desktop/src/i18n/locales/zh-Hant.ts @@ -273,7 +273,6 @@ export const zhHant: Partial = { "settings.tts.eyebrow": "Voice", "settings.tts.title": "Text-to-Speech", "settings.tts.description": "Voice provider and controls for the familiar and floating chat.", - "settings.tts.placeholder": "TTS provider settings will appear here in the next build step.", "settings.plugins.audio.title": "允許外掛播放聲音", "settings.plugins.audio.description": "允許外掛的提示音、警示音與內建音效。", "settings.plugins.voice.title": "允許外掛說話(語音)", diff --git a/apps/desktop/src/tts-service.ts b/apps/desktop/src/tts-service.ts index cbd0a82d..5d2f68da 100644 --- a/apps/desktop/src/tts-service.ts +++ b/apps/desktop/src/tts-service.ts @@ -46,6 +46,7 @@ export async function fetchTtsVoiceList(provider: TtsProviderId): Promise { + if (!text || text.trim().length === 0) return; const prefs = getAppStateSnapshot().preferences; const provider = prefs.ttsProvider ?? "system"; diff --git a/plugins/official/familiaros.virtual-familiar/assets/virtual-pet.svg b/plugins/official/familiaros.virtual-familiar/assets/virtual-familiar.svg similarity index 100% rename from plugins/official/familiaros.virtual-familiar/assets/virtual-pet.svg rename to plugins/official/familiaros.virtual-familiar/assets/virtual-familiar.svg diff --git a/plugins/official/familiaros.virtual-familiar/familiaros.plugin.json b/plugins/official/familiaros.virtual-familiar/familiaros.plugin.json index 658c15af..2a19c263 100644 --- a/plugins/official/familiaros.virtual-familiar/familiaros.plugin.json +++ b/plugins/official/familiaros.virtual-familiar/familiaros.plugin.json @@ -1,6 +1,6 @@ { "manifestVersion": 3, - "id": "familiaros.virtual-pet", + "id": "familiaros.virtual-familiar", "name": "$t:plugin.name", "description": "$t:plugin.description", "version": "1.0.0", @@ -10,14 +10,13 @@ "entry": "index.js", "assets": { "icons": { - "virtual-pet": "assets/virtual-pet.svg" + "virtual-familiar": "assets/virtual-familiar.svg" } }, "permissions": [ - "pet:speak", - "pet:interact", - "pet:pin", - "pet:reaction", + "familiar:speak", + "familiar:interact", + "familiar:reaction", "schedule", "storage", "commands", diff --git a/plugins/official/familiaros.virtual-familiar/index.js b/plugins/official/familiaros.virtual-familiar/index.js index bd0a0206..a85e0070 100644 --- a/plugins/official/familiaros.virtual-familiar/index.js +++ b/plugins/official/familiaros.virtual-familiar/index.js @@ -1,6 +1,6 @@ -// Virtual Pet (familiaros.virtual-pet) — SDK v3 Virtual Pet companion. +// Virtual Familiar (familiaros.virtual-familiar) — SDK v3 Virtual Pet companion. -export const SCHEDULE_ID = "virtual-pet-tick"; +export const SCHEDULE_ID = "virtual-familiar-tick"; export function cleanState(state = {}) { const current = state && typeof state === "object" ? state : {}; @@ -142,7 +142,7 @@ export async function maybeNudge(ctx, state, now = Date.now()) { else if (isNeglected) speechKey = "nudge.neglected"; try { - await ctx.pet.speak(ctx.t(speechKey)); + await ctx.familiar.speak(ctx.t(speechKey)); } catch {} } } @@ -179,12 +179,12 @@ export async function feed(ctx, now = Date.now()) { await playActionSound(ctx); try { - await ctx.pet.react("celebrating", { showMessage: false }); + await ctx.familiar.react("celebrating", { showMessage: false }); if (xpInfo.leveledUp) { - await ctx.pet.speak(ctx.t("speech.levelup")); + await ctx.familiar.speak(ctx.t("speech.levelup")); } else { const idx = Math.floor(Math.random() * 4); - await ctx.pet.speak(ctx.t(`speech.feed.${idx}`)); + await ctx.familiar.speak(ctx.t(`speech.feed.${idx}`)); } } catch {} @@ -216,12 +216,12 @@ export async function play(ctx, now = Date.now()) { await playActionSound(ctx); try { - await ctx.pet.react("celebrating", { showMessage: false }); + await ctx.familiar.react("celebrating", { showMessage: false }); if (xpInfo.leveledUp) { - await ctx.pet.speak(ctx.t("speech.levelup")); + await ctx.familiar.speak(ctx.t("speech.levelup")); } else { const idx = Math.floor(Math.random() * 4); - await ctx.pet.speak(ctx.t(`speech.play.${idx}`)); + await ctx.familiar.speak(ctx.t(`speech.play.${idx}`)); } } catch {} @@ -253,12 +253,12 @@ export async function pet(ctx, now = Date.now()) { await playActionSound(ctx); try { - await ctx.pet.react("waving", { showMessage: false }); + await ctx.familiar.react("waving", { showMessage: false }); if (xpInfo.leveledUp) { - await ctx.pet.speak(ctx.t("speech.levelup")); + await ctx.familiar.speak(ctx.t("speech.levelup")); } else { const idx = Math.floor(Math.random() * 4); - await ctx.pet.speak(ctx.t(`speech.pet.${idx}`)); + await ctx.familiar.speak(ctx.t(`speech.pet.${idx}`)); } } catch {} @@ -289,12 +289,12 @@ export async function nap(ctx, now = Date.now()) { await playActionSound(ctx); try { - await ctx.pet.react("waiting", { showMessage: false }); + await ctx.familiar.react("waiting", { showMessage: false }); if (xpInfo.leveledUp) { - await ctx.pet.speak(ctx.t("speech.levelup")); + await ctx.familiar.speak(ctx.t("speech.levelup")); } else { const idx = Math.floor(Math.random() * 4); - await ctx.pet.speak(ctx.t(`speech.nap.${idx}`)); + await ctx.familiar.speak(ctx.t(`speech.nap.${idx}`)); } } catch {} @@ -308,7 +308,7 @@ export async function showStatus(ctx, now = Date.now()) { const mood = getMood(state, now); try { - await ctx.pet.speak(ctx.t(`speech.status.${mood}`)); + await ctx.familiar.speak(ctx.t(`speech.status.${mood}`)); } catch {} } @@ -340,10 +340,10 @@ export function register(FamiliarOSPlugin) { await reconcile(ctx); try { - ctx.events.on("pet:clicked", () => pet(ctx)); + ctx.events.on("familiar:clicked", () => pet(ctx)); } catch {} - const icon = ctx.assets.icon("virtual-pet"); + const icon = ctx.assets.icon("virtual-familiar"); await ctx.commands.register({ id: "status", title: "$t:command.status.title", description: "$t:command.status.description", icon, placement: "top", priority: 100 }, () => showStatus(ctx)); await ctx.commands.register({ id: "pet", title: "$t:command.pet.title", description: "$t:command.pet.description", icon, placement: "top", priority: 80 }, () => pet(ctx)); diff --git a/plugins/official/familiaros.virtual-familiar/locales/en.json b/plugins/official/familiaros.virtual-familiar/locales/en.json index 91277b47..1bb8bbe1 100644 --- a/plugins/official/familiaros.virtual-familiar/locales/en.json +++ b/plugins/official/familiaros.virtual-familiar/locales/en.json @@ -1,6 +1,6 @@ { - "plugin.name": "Pet Status", - "plugin.description": "Care for a little desktop companion and watch your bond grow through gentle everyday interactions.", + "plugin.name": "Familiar Status", + "plugin.description": "Care for your Familiar and watch your bond grow through gentle everyday interactions.", "hud.food": "Food", "hud.energy": "Energy", "hud.play": "Play", @@ -9,15 +9,15 @@ "config.sound.description": "Optionally play a sound for direct care actions like feeding, playing, petting, or resting.", "command.feed.title": "Feed Snack", - "command.feed.description": "Feed your pet a snack.", + "command.feed.description": "Feed your Familiar a snack.", "command.play.title": "Play Game", "command.play.description": "Play a quick game.", - "command.pet.title": "Pet", - "command.pet.description": "Give your pet some affection.", + "command.pet.title": "Pet Familiar", + "command.pet.description": "Give your Familiar some affection.", "command.nap.title": "Let Rest", - "command.nap.description": "Let your pet rest for 15 minutes.", - "command.status.title": "Check On Pet", - "command.status.description": "Check how your pet is feeling.", + "command.nap.description": "Let your Familiar rest for 15 minutes.", + "command.status.title": "Check On Familiar", + "command.status.description": "Check how your Familiar is feeling.", "menu.mood": "Mood", "menu.food": "Food", diff --git a/plugins/official/familiaros.virtual-familiar/test.js b/plugins/official/familiaros.virtual-familiar/test.js index 09fa7d8e..16bc05d1 100644 --- a/plugins/official/familiaros.virtual-familiar/test.js +++ b/plugins/official/familiaros.virtual-familiar/test.js @@ -1,4 +1,4 @@ -// Golden test for familiaros.virtual-pet. +// Golden test for familiaros.virtual-familiar. import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { @@ -79,7 +79,7 @@ Object.defineProperty(Date, "now", { assert.equal(stateSlept.affection, 50); } -const PERMISSIONS = ["pet:speak", "pet:interact", "pet:pin", "pet:reaction", "schedule", "storage", "commands", "audio", "events"]; +const PERMISSIONS = ["familiar:speak", "familiar:interact", "familiar:pin", "familiar:reaction", "schedule", "storage", "commands", "audio", "events"]; const LOCALES = { en: JSON.parse(await readFile(new URL("./locales/en.json", import.meta.url), "utf8")) }; // 2) Start / Reconcile logic @@ -171,7 +171,7 @@ const LOCALES = { en: JSON.parse(await readFile(new URL("./locales/en.json", imp await h.start(); // Emit click event - await h.emit("pet:clicked", {}); + await h.emit("familiar:clicked", {}); h.expectStored("state", (s) => s.affection === 65 && s.careCounts.petted === 1); h.expectSpoke(/Purr|nuzzles|Warm|soft/); h.expectNoErrors(); @@ -203,4 +203,4 @@ Object.defineProperty(Date, "now", { configurable: true, }); -console.log("familiaros.virtual-pet: all checks passed."); +console.log("familiaros.virtual-familiar: all checks passed.");