1 /++ 2 + Authors: Stephan Dilly, lastname dot firstname at gmail dot com 3 + Copyright: MIT 4 +/ 5 module ask.alexaskill; 6 7 import vibe.d; 8 9 import ask.types; 10 import ask.locale; 11 import ask.baseintent; 12 13 /// annotation to mark an intent callback, use name to specify the exact intent name as specified in the intent schema 14 struct CustomIntent 15 { 16 /// 17 string name; 18 } 19 20 /++ 21 + Abstract base class to inherit your skill from. 22 + 23 + There are two ways to implement a alexa intent: 24 + * add a @CustomIntent annotation to a method in your skill class 25 + * create an intent class inheriting from `BaseIntent` and register it using `addIntent` 26 +/ 27 abstract class AlexaSkill(T) : ITextManager 28 { 29 /// 30 private AlexaText[] localeText; 31 /// 32 private BaseIntent[] intents; 33 34 /++ 35 + constructor that requires the loca table as input 36 + 37 + params: 38 + text = loca table to use for that request 39 + 40 + see_also: 41 + `AlexaText`, `LocaParser` 42 +/ 43 public this(in AlexaText[] text) 44 { 45 localeText = text; 46 } 47 48 /// 49 public int runInEventLoop(AlexaEvent event, AlexaContext context, Duration timeout = 2.seconds) 50 { 51 import std.stdio : writeln, stderr; 52 53 runTask({ 54 scope (exit) 55 exitEventLoop(); 56 57 stderr.writefln("execute request: %s", event.request.type); 58 59 auto result = executeEvent(event, context); 60 61 writeln(serializeToJson(result).toPrettyString()); 62 }); 63 64 setTimer(timeout, { 65 writeln("{}"); 66 stderr.writeln("intent timeout"); 67 exitEventLoop(); 68 }); 69 70 return runEventLoop(); 71 } 72 73 /// 74 package AlexaResult executeEvent(AlexaEvent event, AlexaContext context) 75 { 76 AlexaResult result; 77 78 if (event.request.type == AlexaRequest.Type.LaunchRequest) 79 result = onLaunch(event, context); 80 else if (event.request.type == AlexaRequest.Type.IntentRequest) 81 result = onIntent(event, context); 82 else if (event.request.type == AlexaRequest.Type.SessionEndedRequest) 83 onSessionEnd(event, context); 84 85 return result; 86 } 87 88 /++ 89 + adds an intent handler 90 + 91 + see_also: 92 + `BaseIntent` 93 +/ 94 public void addIntent(BaseIntent intent) 95 { 96 intents ~= intent; 97 intent.textManager = this; 98 } 99 100 /++ 101 + returns the localized text string depending on the loaded locale database 102 + 103 + see_also: 104 + `this`, `ITextManager` 105 +/ 106 string getText(int _key) const pure nothrow 107 { 108 return localeText[_key].text; 109 } 110 111 /// see https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-standard-request-types-reference#launchrequest 112 protected AlexaResult onLaunch(AlexaEvent, AlexaContext) 113 { 114 throw new Exception("not implemented"); 115 } 116 117 /// see https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-standard-request-types-reference#intentrequest 118 private AlexaResult onIntent(AlexaEvent event, AlexaContext context) 119 { 120 import std.traits : hasUDA, getUDAs; 121 122 foreach (i, member; __traits(derivedMembers, T)) 123 { 124 enum isPublic = __traits(getProtection, __traits(getMember, cast(T) this, member)) == "public"; 125 126 static if (isPublic && hasUDA!(__traits(getMember, T, member), CustomIntent)) 127 { 128 enum name = getUDAs!(__traits(getMember, T, member), CustomIntent)[0].name; 129 130 if (event.request.intent.name == name) 131 { 132 mixin("return (cast(T)this)." ~ member ~ "(event, context);"); 133 } 134 } 135 } 136 137 return tryRegisteredIntents(event, context); 138 } 139 140 /// 141 private AlexaResult tryRegisteredIntents(AlexaEvent event, AlexaContext context) 142 { 143 import std.stdio : stderr; 144 145 const eventIntent = event.request.intent.name; 146 147 foreach (baseIntent; intents) 148 { 149 if (baseIntent.name == eventIntent) 150 return baseIntent.onIntent(event, context); 151 } 152 153 stderr.writefln("onIntent did not match: %s", eventIntent); 154 return AlexaResult(); 155 } 156 157 /// see https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/custom-standard-request-types-reference#sessionendedrequest 158 protected void onSessionEnd(AlexaEvent, AlexaContext) 159 { 160 throw new Exception("not implemented"); 161 } 162 }