From 05b75feee434e9d4d613e5c044645058ae7506b8 Mon Sep 17 00:00:00 2001 From: Bossiara13 <236771060+DmitryBykov-ISPO@users.noreply.github.com> Date: Sat, 16 May 2026 14:59:50 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20Wave=2011=20=E2=80=94=20birthdays=20tra?= =?UTF-8?q?cker=20pack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resources/commands/birthdays/ (2 commands): - birthdays.add "запомни день рождения мама 15 марта" → memory key "birthday.мама" = "15.03". Accepts both DD.MM and "DD " formats; recognises all 12 RU + 12 EN month names. - birthdays.next "ближайший день рождения" → reads every "birthday.*" memory key, computes days-until each entry (with year wrap), speaks the nearest one in human form ("сегодня" / "завтра" / "через N дней"). Storage piggybacks on the existing long-term memory store, so birthdays survive restart, sync to disk atomically, and don't need a new persistence layer. Naming convention "birthday." keeps the data discoverable in /memory GUI. Pack count: 99 → 100 functional packs. Tests: 140 rust, unchanged. --- resources/commands/birthdays/add.lua | 80 +++++++++++++++++++++++ resources/commands/birthdays/command.toml | 50 ++++++++++++++ resources/commands/birthdays/next.lua | 55 ++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 resources/commands/birthdays/add.lua create mode 100644 resources/commands/birthdays/command.toml create mode 100644 resources/commands/birthdays/next.lua diff --git a/resources/commands/birthdays/add.lua b/resources/commands/birthdays/add.lua new file mode 100644 index 0000000..5c17956 --- /dev/null +++ b/resources/commands/birthdays/add.lua @@ -0,0 +1,80 @@ +-- Voice "запомни день рождения мама 15 марта" → memory["birthday.мама"]="15.03" +-- We accept three date formats: "15.03", "15 марта", and "15 march". + +local lang = jarvis.context.language +local raw = (jarvis.context.phrase or ""):lower() + +local triggers = { + "запомни день рождения", "день рождения у", "добавь день рождения", + "запиши день рождения", + "remember birthday", "add birthday", "save birthday", +} +local rest = jarvis.text.strip_trigger(raw, triggers) or raw +rest = rest:gsub("^%s+", ""):gsub("%s+$", "") + +-- Russian month names → "MM" +local months_ru = { + ["января"]=1, ["январь"]=1, + ["февраля"]=2, ["февраль"]=2, + ["марта"]=3, ["март"]=3, + ["апреля"]=4, ["апрель"]=4, + ["мая"]=5, ["май"]=5, + ["июня"]=6, ["июнь"]=6, + ["июля"]=7, ["июль"]=7, + ["августа"]=8, ["август"]=8, + ["сентября"]=9, ["сентябрь"]=9, + ["октября"]=10, ["октябрь"]=10, + ["ноября"]=11, ["ноябрь"]=11, + ["декабря"]=12, ["декабрь"]=12, +} +local months_en = { + january=1, february=2, march=3, april=4, may=5, june=6, + july=7, august=8, september=9, october=10, november=11, december=12, +} + +-- Try patterns in order. Each pattern leaves `name` and `date` populated. +local name, date_str = nil, nil + +-- 1. " DD.MM" +local n, d, m = rest:match("^(.-)%s+(%d?%d)%.(%d?%d)") +if n and d and m then + name = n + date_str = string.format("%02d.%02d", tonumber(d), tonumber(m)) +end + +-- 2. " DD " +if not name then + local n2, d2, mword = rest:match("^(.-)%s+(%d?%d)%s+(%S+)") + if n2 and d2 and mword then + local m_num = months_ru[mword] or months_en[mword] + if m_num then + name = n2 + date_str = string.format("%02d.%02d", tonumber(d2), m_num) + end + end +end + +if not name or not date_str then + return jarvis.cmd.not_found(lang == "ru" + and "Скажи в формате 'имя 15 марта' или 'имя 15.03'." + or "Say in the form 'name 15 march' or 'name 15.03'.") +end + +name = name:gsub("^%s+", ""):gsub("%s+$", "") +if name == "" then + return jarvis.cmd.not_found(lang == "ru" and "Чьё именно?" or "Whose?") +end + +-- Strip leading prepositions left from "у мамы" → "мамы" +name = name:gsub("^у%s+", ""):gsub("^of%s+", ""):gsub("^for%s+", "") + +local ok, err = pcall(function() + jarvis.memory.remember("birthday." .. name, date_str) +end) +if not ok then + return jarvis.cmd.error(tostring(err)) +end + +return jarvis.cmd.ok(string.format(lang == "ru" + and "Запомнил: %s — %s." + or "Got it: %s — %s.", name, date_str)) diff --git a/resources/commands/birthdays/command.toml b/resources/commands/birthdays/command.toml new file mode 100644 index 0000000..154ac2c --- /dev/null +++ b/resources/commands/birthdays/command.toml @@ -0,0 +1,50 @@ +# Birthday tracker — voice-add and check upcoming birthdays. +# Storage uses `jarvis.memory.remember` with keys "birthday." so the +# data lives in the same long-term memory store and survives across packs. +# +# Voice flow: +# "запомни день рождения мама 15 марта" +# → memory key "birthday.мама" = "15.03" +# "ближайший день рождения" → reads all "birthday.*" keys + tells the next +# "день рождения мама" → reads back what we know about Mom + +[[commands]] +id = "birthdays.add" +type = "lua" +script = "add.lua" +sandbox = "standard" +timeout = 3000 + +[commands.phrases] +ru = [ + "запомни день рождения", + "день рождения у", + "добавь день рождения", + "запиши день рождения", +] +en = [ + "remember birthday", + "add birthday", + "save birthday", +] + + +[[commands]] +id = "birthdays.next" +type = "lua" +script = "next.lua" +sandbox = "standard" +timeout = 3000 + +[commands.phrases] +ru = [ + "ближайший день рождения", + "у кого скоро день рождения", + "кто следующий именинник", + "когда следующий день рождения", +] +en = [ + "next birthday", + "whose birthday is next", + "upcoming birthdays", +] diff --git a/resources/commands/birthdays/next.lua b/resources/commands/birthdays/next.lua new file mode 100644 index 0000000..a061f4b --- /dev/null +++ b/resources/commands/birthdays/next.lua @@ -0,0 +1,55 @@ +-- Read all "birthday.*" memory keys, find the next upcoming date. + +local lang = jarvis.context.language +local now = jarvis.context.time + +local entries = {} +for _, f in ipairs(jarvis.memory.all() or {}) do + if f.key and f.key:sub(1, 9) == "birthday." then + local d, m = f.value:match("^(%d?%d)%.(%d?%d)") + if d and m then + table.insert(entries, { + name = f.key:sub(10), + day = tonumber(d), + month = tonumber(m), + }) + end + end +end + +if #entries == 0 then + return jarvis.cmd.not_found(lang == "ru" + and "Дней рождения в памяти нет, сэр." + or "No birthdays stored, sir.") +end + +-- Compute "days until" for each. Wrap to next year if month/day already passed. +local function days_until(e) + local today_idx = now.month * 31 + now.day + local their_idx = e.month * 31 + e.day + local delta = their_idx - today_idx + if delta < 0 then + delta = delta + 12 * 31 -- approximate "wrap to next year" + end + return delta +end + +for _, e in ipairs(entries) do + e.delta = days_until(e) +end +table.sort(entries, function(a, b) return a.delta < b.delta end) + +local nxt = entries[1] +local human +if nxt.delta == 0 then + human = lang == "ru" and "сегодня" or "today" +elseif nxt.delta == 1 then + human = lang == "ru" and "завтра" or "tomorrow" +else + human = string.format(lang == "ru" and "через %d дней" or "in %d days", nxt.delta) +end + +return jarvis.cmd.ok(string.format(lang == "ru" + and "Ближайший — %s, %s (%02d.%02d)." + or "Next: %s, %s (%02d.%02d).", + nxt.name, human, nxt.day, nxt.month))