diff --git a/resources/commands/daily_quote/command.toml b/resources/commands/daily_quote/command.toml new file mode 100644 index 0000000..71ab9a9 --- /dev/null +++ b/resources/commands/daily_quote/command.toml @@ -0,0 +1,19 @@ +# Daily quote — random motivational quote. + +[[commands]] +id = "daily_quote" +type = "lua" +script = "quote.lua" +sandbox = "full" +timeout = 15000 + +[commands.phrases] +ru = [ + "цитата дня", + "мотивирующая цитата", + "скажи цитату", + "вдохнови меня", + "цитата", +] +en = ["quote of the day", "motivational quote", "inspire me"] +ua = ["цитата дня", "надихни мене"] diff --git a/resources/commands/daily_quote/quote.lua b/resources/commands/daily_quote/quote.lua new file mode 100644 index 0000000..8109352 --- /dev/null +++ b/resources/commands/daily_quote/quote.lua @@ -0,0 +1,31 @@ +-- "Цитата дня" — random quote. +-- Source: zenquotes.io (free, no key, returns JSON array) +local data = jarvis.http.json("https://zenquotes.io/api/random") +if not data or type(data) ~= "table" or #data == 0 then + -- Fallback to LLM if API fails + local llm_quote = jarvis.llm({ + { role = "system", content = "Выдай одну короткую мотивирующую цитату. По-русски. Без вступлений." }, + { role = "user", content = "Цитата дня." } + }, { max_tokens = 100, temperature = 0.9 }) + if llm_quote and llm_quote ~= "" then + return jarvis.cmd.ok(llm_quote) + end + return jarvis.cmd.error("Не получилось получить цитату.") +end + +local q = data[1] +local quote_en = q.q or "" +local author = q.a or "Аноним" +if quote_en == "" then + return jarvis.cmd.error("Пустая цитата.") +end + +-- Translate to Russian via LLM (the API returns English). +local translated = jarvis.llm({ + { role = "system", content = "Переведи английскую цитату на русский. Без вступлений. Только перевод." }, + { role = "user", content = quote_en } +}, { max_tokens = 200, temperature = 0.2 }) + +local text = (translated and translated ~= "") and translated or quote_en + +return jarvis.cmd.ok(text .. " — " .. author .. ".") diff --git a/resources/commands/now_playing/command.toml b/resources/commands/now_playing/command.toml new file mode 100644 index 0000000..64ee16c --- /dev/null +++ b/resources/commands/now_playing/command.toml @@ -0,0 +1,24 @@ +# Windows Media Session — what's currently playing. +# Uses Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager +# via PowerShell. Works with Spotify, YouTube, Foobar, Yandex Music, anything +# that exposes SMTC. + +[[commands]] +id = "now_playing" +type = "lua" +script = "now.lua" +sandbox = "full" +timeout = 8000 + +[commands.phrases] +ru = [ + "что играет", + "что сейчас играет", + "что за музыка", + "что за песня", + "какой трек", + "какая песня", + "что слушаю", +] +en = ["what's playing", "current song", "now playing"] +ua = ["що грає", "що зараз грає"] diff --git a/resources/commands/now_playing/now.lua b/resources/commands/now_playing/now.lua new file mode 100644 index 0000000..dc98a9a --- /dev/null +++ b/resources/commands/now_playing/now.lua @@ -0,0 +1,46 @@ +-- "Что играет" — query Windows SMTC. +-- Requires Windows 10 1803+ (Media Session API). +local ps = [[ +[Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager,Windows.Media.Control,ContentType=WindowsRuntime] | Out-Null +$mgr = [Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager]::RequestAsync() +while ($mgr.Status -eq 0) { Start-Sleep -Milliseconds 50 } +$session = $mgr.GetResults().GetCurrentSession() +if (-not $session) { Write-Output 'NONE'; exit } +$propsTask = $session.TryGetMediaPropertiesAsync() +while ($propsTask.Status -eq 0) { Start-Sleep -Milliseconds 50 } +$props = $propsTask.GetResults() +$artist = $props.Artist +$title = $props.Title +if ([string]::IsNullOrEmpty($artist) -and [string]::IsNullOrEmpty($title)) { + Write-Output 'EMPTY' + exit +} +Write-Output ("{0}|{1}" -f $artist, $title) +]] + +local res = jarvis.system.exec(string.format( + 'powershell -NoProfile -ExecutionPolicy Bypass -Command "%s"', + ps:gsub('"', '\\"'):gsub("\r?\n", "; ") +)) + +if not res.success then + return jarvis.cmd.error("Не получилось получить данные.") +end + +local out = (res.stdout or ""):gsub("^%s+", ""):gsub("%s+$", "") +if out == "NONE" then + return jarvis.cmd.not_found("Сейчас ничего не играет.") +end +if out == "EMPTY" or out == "|" then + return jarvis.cmd.not_found("Что-то играет, но без названия.") +end + +local artist, title = out:match("^([^|]*)|(.*)$") +if not title or title == "" then + return jarvis.cmd.not_found("Трек без названия.") +end + +if artist and artist ~= "" then + return jarvis.cmd.ok(string.format("Сейчас играет: %s — %s.", artist, title)) +end +return jarvis.cmd.ok(string.format("Сейчас играет: %s.", title)) diff --git a/resources/commands/world_clock/command.toml b/resources/commands/world_clock/command.toml new file mode 100644 index 0000000..aeef0f2 --- /dev/null +++ b/resources/commands/world_clock/command.toml @@ -0,0 +1,18 @@ +# World clock — query time in another city/timezone. + +[[commands]] +id = "world_clock" +type = "lua" +script = "time_in.lua" +sandbox = "full" +timeout = 10000 + +[commands.phrases] +ru = [ + "сколько времени в", + "который час в", + "сколько часов в", + "время в", +] +en = ["time in", "what time in"] +ua = ["котра година у", "скільки часу в"] diff --git a/resources/commands/world_clock/time_in.lua b/resources/commands/world_clock/time_in.lua new file mode 100644 index 0000000..dd561ec --- /dev/null +++ b/resources/commands/world_clock/time_in.lua @@ -0,0 +1,95 @@ +-- "Сколько времени в Токио" — uses worldtimeapi.org public API. +local phrase = (jarvis.context.phrase or "") +local city = jarvis.text.strip_trigger(phrase:lower(), { + "сколько времени в", + "который час в", + "сколько часов в", + "время в", + "time in", + "what time in", + "котра година у", + "скільки часу в", +}) +city = city:gsub("^[%s,:%.]+", ""):gsub("%s+$", "") + +if city == "" then + return jarvis.cmd.error("В каком городе?") +end + +-- Map common Russian city names → timezone slugs. +local TZ_MAP = { + -- Russia + ["москва"] = "Europe/Moscow", + ["спб"] = "Europe/Moscow", + ["петербург"] = "Europe/Moscow", + ["санкт-петербург"] = "Europe/Moscow", + ["новосибирск"] = "Asia/Novosibirsk", + ["екатеринбург"] = "Asia/Yekaterinburg", + ["казань"] = "Europe/Moscow", + ["владивосток"] = "Asia/Vladivostok", + ["хабаровск"] = "Asia/Vladivostok", + ["омск"] = "Asia/Omsk", + ["красноярск"] = "Asia/Krasnoyarsk", + -- World + ["лондон"] = "Europe/London", + ["нью-йорк"] = "America/New_York", + ["нью йорк"] = "America/New_York", + ["сан-франциско"] = "America/Los_Angeles", + ["сан франциско"] = "America/Los_Angeles", + ["лос-анджелес"] = "America/Los_Angeles", + ["лос анджелес"] = "America/Los_Angeles", + ["токио"] = "Asia/Tokyo", + ["пекин"] = "Asia/Shanghai", + ["шанхай"] = "Asia/Shanghai", + ["гонконг"] = "Asia/Hong_Kong", + ["сингапур"] = "Asia/Singapore", + ["сидней"] = "Australia/Sydney", + ["дубай"] = "Asia/Dubai", + ["париж"] = "Europe/Paris", + ["берлин"] = "Europe/Berlin", + ["мадрид"] = "Europe/Madrid", + ["рим"] = "Europe/Rome", + ["амстердам"] = "Europe/Amsterdam", + ["варшава"] = "Europe/Warsaw", + ["прага"] = "Europe/Prague", + ["стамбул"] = "Europe/Istanbul", + ["киев"] = "Europe/Kyiv", + ["минск"] = "Europe/Minsk", + ["ереван"] = "Asia/Yerevan", + ["тбилиси"] = "Asia/Tbilisi", + ["алматы"] = "Asia/Almaty", + ["астана"] = "Asia/Almaty", + ["баку"] = "Asia/Baku", + ["ташкент"] = "Asia/Tashkent", + ["сеул"] = "Asia/Seoul", + ["мехико"] = "America/Mexico_City", + ["рио"] = "America/Sao_Paulo", + ["буэнос-айрес"] = "America/Argentina/Buenos_Aires", +} + +local tz = nil +for stem, zone in pairs(TZ_MAP) do + if city:find(stem, 1, true) then + tz = zone + break + end +end + +if not tz then + return jarvis.cmd.not_found("Не знаю такого города. Попробуйте Москва, Токио, Лондон.") +end + +-- worldtimeapi.org has rate limits but is free with no key. +local url = "https://worldtimeapi.org/api/timezone/" .. tz +local data = jarvis.http.json(url) +if not data or not data.datetime then + return jarvis.cmd.error("Не получилось узнать время.") +end + +-- datetime is ISO 8601: "2026-05-15T14:32:11.123456+09:00" +local hh, mm = data.datetime:match("T(%d%d):(%d%d)") +if not hh then + return jarvis.cmd.error("Не понял ответ сервиса.") +end + +return jarvis.cmd.ok(string.format("В %s сейчас %s:%s.", city, hh, mm))