feat: Wave 11 — birthdays tracker pack
resources/commands/birthdays/ (2 commands):
- birthdays.add "запомни день рождения мама 15 марта" → memory key
"birthday.мама" = "15.03". Accepts both DD.MM and "DD <month>"
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.<name>" keeps the
data discoverable in /memory GUI.
Pack count: 99 → 100 functional packs.
Tests: 140 rust, unchanged.
This commit is contained in:
parent
87bb186e94
commit
05b75feee4
3 changed files with 185 additions and 0 deletions
80
resources/commands/birthdays/add.lua
Normal file
80
resources/commands/birthdays/add.lua
Normal file
|
|
@ -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. "<name> 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. "<name> DD <month_ru/en>"
|
||||||
|
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))
|
||||||
50
resources/commands/birthdays/command.toml
Normal file
50
resources/commands/birthdays/command.toml
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Birthday tracker — voice-add and check upcoming birthdays.
|
||||||
|
# Storage uses `jarvis.memory.remember` with keys "birthday.<name>" 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",
|
||||||
|
]
|
||||||
55
resources/commands/birthdays/next.lua
Normal file
55
resources/commands/birthdays/next.lua
Normal file
|
|
@ -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))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue