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 }