make pauly shore great again
All checks were successful
Alexa skill build / build (push) Successful in 43s

This commit is contained in:
Emil Lerch 2026-02-04 20:13:47 -08:00
parent 9cf3035643
commit 56ae5d2039
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -3,7 +3,6 @@ const json = std.json;
const lambda = @import("lambda_runtime"); const lambda = @import("lambda_runtime");
const rinnai = @import("rinnai"); const rinnai = @import("rinnai");
const homeassistant = @import("homeassistant.zig"); const homeassistant = @import("homeassistant.zig");
const alexa = @import("alexa.zig");
const timezone = @import("timezone.zig"); const timezone = @import("timezone.zig");
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const builtin = @import("builtin"); const builtin = @import("builtin");
@ -38,6 +37,7 @@ pub fn main() !u8 {
return runLocal(allocator, config, args); return runLocal(allocator, config, args);
const Handler = struct { const Handler = struct {
// SAFETY: set 7 lines down...
var c: Config = undefined; var c: Config = undefined;
pub fn lambda_handler(alloc: std.mem.Allocator, event_data: []const u8) anyerror![]const u8 { pub fn lambda_handler(alloc: std.mem.Allocator, event_data: []const u8) anyerror![]const u8 {
@ -259,11 +259,15 @@ fn handleWeezTheJuice(allocator: std.mem.Allocator, config: Config) ![]const u8
null, // No timezone needed for toggle null, // No timezone needed for toggle
) catch |err| { ) catch |err| {
log.err("Home Assistant error: {}", .{err}); log.err("Home Assistant error: {}", .{err});
return buildAlexaResponse(allocator, "I had trouble weezin' the juice.", true); return buildAlexaResponse(allocator,
\\<voice name="Joey"><prosody rate="x-slow">Whoooaaa</prosody> <prosody volume="loud"><emphasis level="strong">buuuddy.</emphasis> <break time="300ms"/> I had some trouble weezin' the juice, bro.</prosody></voice>
, true);
}; };
defer allocator.free(result.speech); defer allocator.free(result.speech);
return buildAlexaResponse(allocator, "No weezin' the juice!", true); return buildAlexaResponse(allocator,
\\<voice name="Joey"><prosody rate="x-slow">Whoooaaa,</prosody> <prosody volume="loud"><emphasis level="strong">no</emphasis> weezin' the <emphasis level="strong">juice!</emphasis></prosody></voice>
, true);
} }
/// Parsed intent parameters for Home Assistant commands /// Parsed intent parameters for Home Assistant commands
@ -388,7 +392,7 @@ fn resolveLocalTimezone(allocator: std.mem.Allocator) ?i32 {
return timezone.getUtcOffset(allocator, tz_name); return timezone.getUtcOffset(allocator, tz_name);
} }
/// Build an Alexa skill response JSON /// Build an Alexa skill response JSON with SSML
fn buildAlexaResponse(allocator: std.mem.Allocator, speech: []const u8, end_session: bool) ![]const u8 { fn buildAlexaResponse(allocator: std.mem.Allocator, speech: []const u8, end_session: bool) ![]const u8 {
// Escape speech for JSON // Escape speech for JSON
var escaped_speech: std.ArrayList(u8) = .{}; var escaped_speech: std.ArrayList(u8) = .{};
@ -415,7 +419,7 @@ fn buildAlexaResponse(allocator: std.mem.Allocator, speech: []const u8, end_sess
} }
return try std.fmt.allocPrint(allocator, return try std.fmt.allocPrint(allocator,
\\{{"version":"1.0","response":{{"outputSpeech":{{"type":"PlainText","text":"{s}"}},"shouldEndSession":{s}}}}} \\{{"version":"1.0","response":{{"outputSpeech":{{"type":"SSML","ssml":"<speak>{s}</speak>"}},"shouldEndSession":{s}}}}}
, .{ escaped_speech.items, end_session_str }); , .{ escaped_speech.items, end_session_str });
} }
@ -606,8 +610,8 @@ test "buildAlexaResponse with speech and end session" {
try std.testing.expectEqualStrings("1.0", parsed.value.object.get("version").?.string); try std.testing.expectEqualStrings("1.0", parsed.value.object.get("version").?.string);
const resp = parsed.value.object.get("response").?.object; const resp = parsed.value.object.get("response").?.object;
try std.testing.expect(resp.get("shouldEndSession").?.bool == true); try std.testing.expect(resp.get("shouldEndSession").?.bool == true);
try std.testing.expectEqualStrings("PlainText", resp.get("outputSpeech").?.object.get("type").?.string); try std.testing.expectEqualStrings("SSML", resp.get("outputSpeech").?.object.get("type").?.string);
try std.testing.expectEqualStrings("Hello world", resp.get("outputSpeech").?.object.get("text").?.string); try std.testing.expectEqualStrings("<speak>Hello world</speak>", resp.get("outputSpeech").?.object.get("ssml").?.string);
} }
test "buildAlexaResponse with speech and keep session open" { test "buildAlexaResponse with speech and keep session open" {
@ -644,8 +648,8 @@ test "buildAlexaResponse escapes special characters" {
const parsed = try json.parseFromSlice(json.Value, allocator, response, .{}); const parsed = try json.parseFromSlice(json.Value, allocator, response, .{});
defer parsed.deinit(); defer parsed.deinit();
const text = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("text").?.string; const ssml = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expectEqualStrings("Say \"hello\"\nNew line", text); try std.testing.expectEqualStrings("<speak>Say \"hello\"\nNew line</speak>", ssml);
} }
test "handler returns error response for invalid JSON" { test "handler returns error response for invalid JSON" {
@ -657,8 +661,8 @@ test "handler returns error response for invalid JSON" {
const parsed = try json.parseFromSlice(json.Value, allocator, response, .{}); const parsed = try json.parseFromSlice(json.Value, allocator, response, .{});
defer parsed.deinit(); defer parsed.deinit();
const text = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("text").?.string; const ssml = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expectEqualStrings("I couldn't understand that request.", text); try std.testing.expectEqualStrings("<speak>I couldn't understand that request.</speak>", ssml);
} }
test "handler returns error for missing request field" { test "handler returns error for missing request field" {
@ -670,8 +674,8 @@ test "handler returns error for missing request field" {
const parsed = try json.parseFromSlice(json.Value, allocator, response, .{}); const parsed = try json.parseFromSlice(json.Value, allocator, response, .{});
defer parsed.deinit(); defer parsed.deinit();
const text = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("text").?.string; const ssml = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expectEqualStrings("Invalid request format.", text); try std.testing.expectEqualStrings("<speak>Invalid request format.</speak>", ssml);
} }
test "handler handles LaunchRequest" { test "handler handles LaunchRequest" {
@ -688,8 +692,8 @@ test "handler handles LaunchRequest" {
const resp = parsed.value.object.get("response").?.object; const resp = parsed.value.object.get("response").?.object;
try std.testing.expect(resp.get("shouldEndSession").?.bool == false); try std.testing.expect(resp.get("shouldEndSession").?.bool == false);
const text = resp.get("outputSpeech").?.object.get("text").?.string; const ssml = resp.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expect(std.mem.indexOf(u8, text, "hot water") != null); try std.testing.expect(std.mem.indexOf(u8, ssml, "hot water") != null);
} }
test "handler handles SessionEndedRequest" { test "handler handles SessionEndedRequest" {
@ -723,8 +727,8 @@ test "handler handles AMAZON.HelpIntent" {
const resp = parsed.value.object.get("response").?.object; const resp = parsed.value.object.get("response").?.object;
try std.testing.expect(resp.get("shouldEndSession").?.bool == false); try std.testing.expect(resp.get("shouldEndSession").?.bool == false);
const text = resp.get("outputSpeech").?.object.get("text").?.string; const ssml = resp.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expect(std.mem.indexOf(u8, text, "recirculation") != null); try std.testing.expect(std.mem.indexOf(u8, ssml, "recirculation") != null);
} }
test "handler handles AMAZON.StopIntent" { test "handler handles AMAZON.StopIntent" {
@ -741,8 +745,8 @@ test "handler handles AMAZON.StopIntent" {
const resp = parsed.value.object.get("response").?.object; const resp = parsed.value.object.get("response").?.object;
try std.testing.expect(resp.get("shouldEndSession").?.bool == true); try std.testing.expect(resp.get("shouldEndSession").?.bool == true);
const text = resp.get("outputSpeech").?.object.get("text").?.string; const ssml = resp.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expectEqualStrings("Okay, goodbye.", text); try std.testing.expectEqualStrings("<speak>Okay, goodbye.</speak>", ssml);
} }
test "handler handles AMAZON.CancelIntent" { test "handler handles AMAZON.CancelIntent" {
@ -773,8 +777,8 @@ test "handler handles unknown intent" {
const parsed = try json.parseFromSlice(json.Value, allocator, response, .{}); const parsed = try json.parseFromSlice(json.Value, allocator, response, .{});
defer parsed.deinit(); defer parsed.deinit();
const text = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("text").?.string; const ssml = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expectEqualStrings("I don't know how to do that.", text); try std.testing.expectEqualStrings("<speak>I don't know how to do that.</speak>", ssml);
} }
test "handler handles unknown request type" { test "handler handles unknown request type" {
@ -789,8 +793,8 @@ test "handler handles unknown request type" {
const parsed = try json.parseFromSlice(json.Value, allocator, response, .{}); const parsed = try json.parseFromSlice(json.Value, allocator, response, .{});
defer parsed.deinit(); defer parsed.deinit();
const text = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("text").?.string; const ssml = parsed.value.object.get("response").?.object.get("outputSpeech").?.object.get("ssml").?.string;
try std.testing.expectEqualStrings("I didn't understand that.", text); try std.testing.expectEqualStrings("<speak>I didn't understand that.</speak>", ssml);
} }
// ============================================================================= // =============================================================================