├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build.gradle
├── docs
├── constants.md
├── contributing.md
├── functions.md
└── publish.md
├── examples
└── hello-world
│ ├── README.md
│ ├── build.gradle
│ ├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── src
│ └── main
│ ├── kotlin
│ └── de
│ │ └── mkammerer
│ │ └── alexa
│ │ └── samples
│ │ └── helloworld
│ │ ├── HelloWorldSpeechlet.kt
│ │ └── Main.kt
│ └── resources
│ └── logback.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
└── kotlin
│ └── de
│ └── mkammerer
│ └── aleksa
│ ├── Aleksa.kt
│ ├── BuiltInIntents.kt
│ ├── Constants.kt
│ ├── FeatureConfig.kt
│ ├── Functions.kt
│ ├── RootServlet.kt
│ ├── SpeechletV2Base.kt
│ ├── TlsConfig.kt
│ └── metrics
│ └── MetricsSpeechletV2.kt
└── test
├── kotlin
└── de
│ └── mkammerer
│ └── aleksa
│ └── AleksaTest.kt
└── resources
├── logback.xml
├── requests
├── request-1.json
├── request-2.json
└── request-3.json
└── tls.jks
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ
2 | .idea/
3 | out
4 | classes/
5 |
6 | # Gradle
7 | build/
8 | .gradle/
9 |
10 | # User settings
11 | gradle.properties
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ## Unreleased
6 |
7 | ## [1.2] - 2018-10-07
8 | ### Added
9 | - Add metrics
10 | ### Updated
11 | - Update Kotlin, Alexa Skill SDK, SLF4J, Jetty and Commons CLI
12 |
13 | ## [1.1] - 2017-09-24
14 | ### Added
15 | - Add support for TLS
16 |
17 | ### Updated
18 | - Update Kotlin, Alexa Skill SDK and Jetty
19 |
20 | ## [1.0] - 2017-02-11
21 | ### Added
22 | - Initial version
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aleksa
2 | Aleksa is a small framework for writing [Alexa Skills](https://developer.amazon.com/alexa-skills-kit) in Kotlin.
3 |
4 | ## Warning
5 |
6 | **This framework uses an old version of the Alexa SDK - skills built with it won't work with Alexa anymore.**
7 |
8 | ## Usage
9 |
10 | ### Maven
11 |
12 | ```xml
13 |
14 | de.mkammerer.aleksa
15 | aleksa
16 | 1.2
17 |
18 | ```
19 |
20 | ### Gradle
21 |
22 | ```
23 | compile group: 'de.mkammerer.aleksa', name: 'aleksa', version: '1.2'
24 | ```
25 |
26 | ## Features
27 |
28 | * Embedded Jetty server
29 | * Configurable via code or commandline flags
30 | * Supports hosting multiple skills in one application
31 | * Convenience functions for plaintext responses, SSML, repromts, slots, sessions and more
32 | * Dev mode which simplifies skill testing while development
33 | * TLS
34 | * Metrics
35 |
36 | ## Example
37 |
38 | Speechlet implementation:
39 |
40 | ```kotlin
41 | // Inherit from SpeechletV2Base, it implements SpeechletV2 and implements optional methods with empty bodies
42 | class HelloWorldSpeechlet : SpeechletV2Base() {
43 | override fun onIntent(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
44 | val intent = requestEnvelope.request.intent
45 |
46 | return when (intent.name) {
47 | // use the tell function to create a tell response
48 | "HelloWorldIntent" -> tell("Hello world")
49 | // The BuiltInIntents object contains the Alexa built-in intents
50 | BuiltInIntents.CANCEL, BuiltInIntents.STOP -> tell("Good bye")
51 | // use the ask function to create an ask response
52 | else -> ask("What do you want to do?")
53 | }
54 | }
55 |
56 | override fun onLaunch(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
57 | return ask("Hello world. What do you want to do?")
58 | }
59 | }
60 | ```
61 |
62 | Application:
63 |
64 | ```kotlin
65 | // Start with --help to see available commandline options
66 | fun main(args: Array) {
67 | // Create your speechlet
68 | val speechlet = HelloWorldSpeechlet()
69 | // Add the speechlet to Aleksa
70 | Aleksa.addSpeechlet(path = "/helloworld", applicationId = "[Your skill id]", speechlet = speechlet)
71 | // Start Aleksa with the commandline parameters of your application
72 | Aleksa.start(args)
73 | }
74 | ```
75 |
76 | Run it with `--interface 127.0.0.1 --port 8080 --dev`. Now you can test the
77 | skill with curl or some other tool at the url `http://127.0.0.1:8080/helloworld`.
78 |
79 | If you don't specify any commandline arguments, it binds to all interfaces on port 8080 and without dev mode.
80 | The dev mode disables request signature checking, timestamp checking and application id verification. It also shows some
81 | information on `/` to ease debugging infrastructure problems (reverse proxies, etc.).
82 |
83 | If you want metrics (statistics on how often your skills are executed), add `--metrics` and check the `/metrics` endpoint.
84 |
85 | For more examples see the [examples](examples) directory.
86 |
87 | ## Commandline parameters
88 |
89 | ```
90 | -d,--dev Enable development mode
91 | -h,--help Prints help
92 | -i,--interface Interface to bind to
93 | -ka,--key-alias Key alias. If not set, a key will be
94 | automatically selected
95 | -kpw,--key-password Key password. If not set, the keystore
96 | password will be used
97 | -ks,--keystore Location to the keystore
98 | -kspw,--keystore-password Keystore password
99 | -m,--metrics Enable metrics
100 | -p,--port Port to bind to
101 | ```
102 |
103 | ## Documentation
104 |
105 | * [Functions](docs/functions.md)
106 | * [Constants](docs/constants.md)
107 |
108 | ## License
109 |
110 | [LGPLv3](LICENSE)
111 |
112 | ## Contributing
113 |
114 | See [contributing guidelines](docs/contributing.md).
115 |
116 | ## Maintainer
117 |
118 | Moritz Kammerer ([phXql](https://github.com/phxql))
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | group 'de.mkammerer.aleksa'
2 | version '1.2'
3 |
4 | description 'Aleksa is a small framework for writing Alexa Skills in Kotlin'
5 |
6 | buildscript {
7 | ext.kotlin_version = '1.2.71'
8 | ext.jetty_version = '9.4.12.v20180830'
9 | ext.dokka_version = '0.9.16'
10 | ext.alexa_kit_version = '1.8.1'
11 | ext.commons_cli_version = '1.4'
12 | ext.slf4j_version = '1.7.25'
13 | ext.junit_version = '4.12'
14 | ext.logback_version = '1.2.3'
15 | ext.metrics_version = '4.0.3'
16 |
17 | repositories {
18 | mavenCentral()
19 | jcenter()
20 | }
21 |
22 | dependencies {
23 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
24 | classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}"
25 | }
26 | }
27 |
28 | apply plugin: 'kotlin'
29 | apply plugin: 'signing'
30 | apply plugin: 'maven'
31 | apply plugin: 'org.jetbrains.dokka'
32 |
33 | dependencies {
34 | compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_version
35 | compile group: 'org.eclipse.jetty', name: 'jetty-server', version: jetty_version
36 | compile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: jetty_version
37 | compile(group: 'com.amazon.alexa', name: 'alexa-skills-kit', version: alexa_kit_version) {
38 | exclude module: 'log4j'
39 | exclude module: 'slf4j-log4j12'
40 | }
41 | compile group: 'commons-cli', name: 'commons-cli', version: commons_cli_version
42 | compile group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version
43 | compile group: 'io.dropwizard.metrics', name: 'metrics-core', version: metrics_version
44 | compile group: 'io.dropwizard.metrics', name: 'metrics-servlets', version: metrics_version
45 |
46 | testCompile group: 'junit', name: 'junit', version: junit_version
47 | testCompile group: 'ch.qos.logback', name: 'logback-classic', version: logback_version
48 | }
49 |
50 | repositories {
51 | mavenCentral()
52 | }
53 |
54 | signing {
55 | sign configurations.archives
56 | required { gradle.taskGraph.hasTask("uploadArchives") }
57 | }
58 |
59 | uploadArchives {
60 | repositories {
61 | mavenDeployer {
62 | repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') {
63 | authentication(userName: project.properties['sonatype.username'], password: project.properties['sonatype.password'])
64 | }
65 | snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots') {
66 | authentication(userName: project.properties['sonatype.username'], password: project.properties['sonatype.password'])
67 | }
68 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
69 |
70 | enhancePom(pom)
71 | }
72 | }
73 | }
74 |
75 | task sourcesJar(type: Jar, dependsOn: classes) {
76 | classifier = 'sources'
77 | from sourceSets.main.kotlin
78 | }
79 |
80 | task javadocJar(type: Jar, dependsOn: 'dokkaJavadoc') {
81 | classifier = 'javadoc'
82 | from "$buildDir/javadoc"
83 | }
84 |
85 | task dokkaJar(type: Jar, dependsOn: 'dokka') {
86 | classifier = 'dokka'
87 | from "$buildDir/dokka"
88 | }
89 |
90 |
91 | artifacts {
92 | archives sourcesJar
93 | archives javadocJar
94 | archives dokkaJar
95 | }
96 |
97 | task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
98 | outputFormat = 'javadoc'
99 | outputDirectory = "$buildDir/javadoc"
100 | }
101 |
102 | /**
103 | * Adds license, developer and SCM tags to the POM file.
104 | * @param pom POM.
105 | */
106 | void enhancePom(MavenPom pom) {
107 | pom.project {
108 | url 'https://github.com/phxql/aleksa'
109 | name rootProject.name
110 | description project.description
111 | licenses {
112 | license {
113 | name 'GNU Lesser General Public License, Version 3'
114 | url 'https://www.gnu.org/licenses/lgpl-3.0.en.html'
115 | }
116 | }
117 | developers {
118 | developer {
119 | name 'Moritz Kammerer'
120 | email 'kammerer.moritz@gmail.com'
121 | url 'https://www.mkammerer.de/'
122 | }
123 | }
124 | scm {
125 | connection 'scm:git:git://github.com/phxql/aleksa.git'
126 | developerConnection 'scm:git:git://github.com/phxql/aleksa.git'
127 | url 'https://github.com/phxql/aleksa'
128 | }
129 | }
130 | }
--------------------------------------------------------------------------------
/docs/constants.md:
--------------------------------------------------------------------------------
1 | # Aleksa constants
2 |
3 | ## Built-in intents
4 |
5 | The object `de.mkammerer.aleksa.BuiltInIntents` contains the names of the built-in intents.
6 |
7 | ## SSML
8 |
9 | The object `de.mkammerer.aleksa.SSML` contains often used SSML constants, like breaks.
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Feel free to fork the project and create pull requests. Please use the `develop` branch for new features,
4 | I like to keep the `master` branch stable.
--------------------------------------------------------------------------------
/docs/functions.md:
--------------------------------------------------------------------------------
1 | # Aleksa helper functions
2 |
3 | ## Speech
4 |
5 | ```kotlin
6 | val speech: OutputSpeech = outputSpeech("Hello world")
7 | ```
8 |
9 | Creates an output speech from a given string. Automatically detects
10 | SSML and creates a `SsmlOutputSpeech` if necessary. SSML must start with
11 | ``
12 |
13 | ```kotlin
14 | val speech: PlainTextOutputSpeech = plaintext("Hello world")
15 | ```
16 |
17 | Creates a plaintext output speech from a given string.
18 |
19 | ```kotlin
20 | val speech: SsmlOutputSpeech = ssml("Hello world")
21 | ```
22 |
23 | Creates a SSML output speech from a given string.
24 |
25 | ## Intents
26 |
27 | ```kotlin
28 | // intent is read from the request
29 | val value: String? = slotValue(intent, "Name")
30 | ```
31 |
32 | Reads the slot `Name` from the intent. Returns `null` if either the slot or the slot value is missing.
33 |
34 | ## Responses
35 |
36 | ```kotlin
37 | val speech: OutputSpeech = outputSpeech("What's it gonna be?")
38 | val reprompt: Reprompt = reprompt(speech)
39 | ```
40 |
41 | Creates a reprompt from the given speech.
42 |
43 | ```kotlin
44 | val response: SpeechletResponse = tell("Hello world")
45 | ```
46 |
47 | Creates a tell response from the given string. You can use SSML in the string,
48 | just start it with ``.
49 |
50 | ```kotlin
51 | val response: SpeechletResponse = ask("What's your name?")
52 | ```
53 |
54 | Creates an ask response from the given string. You can use SSML in the string,
55 | just start it with ``. You can optionally provide a reprompt:
56 |
57 | ```kotlin
58 | val response: SpeechletResponse = ask("What's your name?", "Don't be shy, what's your name?")
59 | ```
60 |
61 | By default the reprompt is the same as the question.
62 |
63 | ## Session
64 |
65 | ```kotlin
66 | // Request is the request from the speechlet
67 | val value: String? = getSessionString(request, "name")
68 | ```
69 |
70 | Reads the value with the key `name` from the session. Returns null if the key
71 | doesn't exist or the value isn't a string.
72 |
73 | ## Miscellaneous
74 |
75 | ```kotlin
76 | val telephoneSSML: String = telephoneNumber("030 22732152")
77 | ```
78 |
79 | Returns the SSML for a digit-spoken telephone number with reasonable pauses
80 | between them. Filters all non-digit characters from the given string. The
81 | SSML doesn't include the `` tags.
--------------------------------------------------------------------------------
/docs/publish.md:
--------------------------------------------------------------------------------
1 | # Publish
2 |
3 | 1. Create `gradle.properties`, containing:
4 | ```
5 | signing.keyId=[ID of the GPG key]
6 | signing.password=[Password of the GPG key]
7 | signing.secretKeyRingFile=[Location of the secret key ring file, e.g. /home/moe/.gnupg/secring.gpg]
8 | sonatype.username=[Sonatype username]
9 | sonatype.password=[Sonatype password]
10 | ```
11 | 1. Update version in `build.gradle`
12 | 1. Update version in `CHANGELOG.md`
13 | 1. Update version in `README.md`
14 | 1. Merge `develop` in `master`
15 | 1. Tag current version in format `v[version]`
16 | 1. Run `./gradlew clean build uploadArchives`
17 | 1. Open [https://oss.sonatype.org/#stagingRepositories](https://oss.sonatype.org/#stagingRepositories)
18 | 1. Find the aleksa staging repo
19 | 1. Close the aleksa staging repo
20 | 1. Wait some time, then release the staging repo
21 |
--------------------------------------------------------------------------------
/examples/hello-world/README.md:
--------------------------------------------------------------------------------
1 | # Aleksa Hello World example
2 |
3 | * [HelloWorldSpeechlet.kt](src/main/kotlin/de/mkammerer/alexa/samples/helloworld/HelloWorldSpeechlet.kt) contains the speechlet implementation
4 | * [Main.kt](src/main/kotlin/de/mkammerer/alexa/samples/helloworld/Main.kt) contains the main function
5 | * [logback.xml](src/main/resources/logback.xml) contains the logger configuration
6 |
--------------------------------------------------------------------------------
/examples/hello-world/build.gradle:
--------------------------------------------------------------------------------
1 | group 'de.mkammerer.alexa.samples.helloworld'
2 | version '1.1'
3 |
4 | // Enable Kotlin compilation
5 | buildscript {
6 | ext.kotlin_version = '1.1.50'
7 | repositories {
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | }
14 | }
15 |
16 | // Add the application plugin to create a tar/zip which contains a launcher
17 | apply plugin: 'application'
18 | apply plugin: 'kotlin'
19 |
20 | // Set the main class name for the application plugin
21 | mainClassName = 'de.mkammerer.alexa.samples.helloworld.MainKt'
22 |
23 | dependencies {
24 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
25 |
26 | // Add Aleksa and a logging backend (logging is done with SLF4J API)
27 | compile group: 'de.mkammerer.aleksa', name: 'aleksa', version: '1.1'
28 | compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
29 | }
30 |
31 | repositories {
32 | mavenCentral()
33 | }
34 |
35 | task wrapper(type: Wrapper) {
36 | gradleVersion = '4.2'
37 | }
38 |
--------------------------------------------------------------------------------
/examples/hello-world/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phxql/aleksa/5d4b9a345e6515aa8dfb8370364ee6b730e50c6e/examples/hello-world/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/examples/hello-world/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Feb 11 10:33:21 CET 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
7 |
--------------------------------------------------------------------------------
/examples/hello-world/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/examples/hello-world/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/examples/hello-world/src/main/kotlin/de/mkammerer/alexa/samples/helloworld/HelloWorldSpeechlet.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.alexa.samples.helloworld
2 |
3 | import com.amazon.speech.json.SpeechletRequestEnvelope
4 | import com.amazon.speech.speechlet.IntentRequest
5 | import com.amazon.speech.speechlet.LaunchRequest
6 | import com.amazon.speech.speechlet.SpeechletResponse
7 | import de.mkammerer.aleksa.BuiltInIntents
8 | import de.mkammerer.aleksa.SpeechletV2Base
9 | import de.mkammerer.aleksa.ask
10 | import de.mkammerer.aleksa.tell
11 |
12 | // Inherit from SpeechletV2Base, it implements SpeechletV2 and implements optional methods with empty bodies
13 | class HelloWorldSpeechlet : SpeechletV2Base() {
14 | override fun onIntent(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
15 | val intent = requestEnvelope.request.intent
16 |
17 | return when (intent.name) {
18 | // use the tell function to create a tell response
19 | "HelloWorldIntent" -> tell("Hello world")
20 | // The BuiltInIntents object contains the Alexa built-in intents
21 | BuiltInIntents.CANCEL, BuiltInIntents.STOP -> tell("Good bye")
22 | // use the ask function to create an ask response
23 | else -> ask("What do you want to do?")
24 | }
25 | }
26 |
27 | override fun onLaunch(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
28 | return ask("Hello world. What do you want to do?")
29 | }
30 | }
--------------------------------------------------------------------------------
/examples/hello-world/src/main/kotlin/de/mkammerer/alexa/samples/helloworld/Main.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.alexa.samples.helloworld
2 |
3 | import de.mkammerer.aleksa.Aleksa
4 |
5 | // Start with --help to see available commandline options
6 | fun main(args: Array) {
7 | // Create your speechlet
8 | val speechlet = HelloWorldSpeechlet()
9 | // Add the speechlet to Aleksa
10 | Aleksa.addSpeechlet(path = "/helloworld", applicationId = "[Your skill id]", speechlet = speechlet)
11 | // Start Aleksa with the commandline parameters of your application
12 | Aleksa.start(args)
13 | }
--------------------------------------------------------------------------------
/examples/hello-world/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phxql/aleksa/5d4b9a345e6515aa8dfb8370364ee6b730e50c6e/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'aleksa'
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/Aleksa.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | import com.amazon.speech.Sdk
4 | import com.amazon.speech.speechlet.SpeechletV2
5 | import com.amazon.speech.speechlet.servlet.SpeechletServlet
6 | import com.codahale.metrics.MetricRegistry
7 | import com.codahale.metrics.servlets.MetricsServlet
8 | import de.mkammerer.aleksa.metrics.MetricsSpeechletV2
9 | import org.apache.commons.cli.DefaultParser
10 | import org.apache.commons.cli.HelpFormatter
11 | import org.apache.commons.cli.Options
12 | import org.eclipse.jetty.http.HttpVersion
13 | import org.eclipse.jetty.server.*
14 | import org.eclipse.jetty.servlet.ServletHandler
15 | import org.eclipse.jetty.servlet.ServletHolder
16 | import org.eclipse.jetty.util.resource.Resource
17 | import org.eclipse.jetty.util.ssl.SslContextFactory
18 | import org.slf4j.LoggerFactory
19 |
20 | /**
21 | * Aleksa.
22 | */
23 | object Aleksa {
24 | private val logger = LoggerFactory.getLogger(Aleksa::class.java)
25 |
26 | private val metricRegistry = MetricRegistry()
27 | private var server: Server? = null
28 | private val speechletRegistrations = mutableListOf()
29 |
30 | /**
31 | * Default interface to bind to.
32 | */
33 | private const val DEFAULT_INTERFACE = "0.0.0.0"
34 | /**
35 | * Default port to bind to.
36 | */
37 | private const val DEFAULT_PORT = 8080
38 | /**
39 | * Default value of dev mode.
40 | */
41 | private const val DEFAULT_DEV = false
42 | /**
43 | * Root path.
44 | */
45 | private const val ROOT_PATH = "/"
46 | /**
47 | * Metrics path.
48 | */
49 | private const val METRICS_PATH = "/metrics"
50 |
51 | private val options = Options()
52 |
53 | init {
54 | options.addOption("i", "interface", true, "Interface to bind to")
55 | options.addOption("p", "port", true, "Port to bind to")
56 | options.addOption("d", "dev", false, "Enable development mode")
57 | options.addOption("ks", "keystore", true, "Location to the keystore")
58 | options.addOption("kspw", "keystore-password", true, "Keystore password")
59 | options.addOption("kpw", "key-password", true, "Key password. If not set, the keystore password will be used")
60 | options.addOption("ka", "key-alias", true, "Key alias. If not set, a key will be automatically selected")
61 | options.addOption("m", "metrics", false, "Enable metrics")
62 |
63 | options.addOption("h", "help", false, "Prints help")
64 | }
65 |
66 | /**
67 | * Starts Aleksa with the given [commandline arguments][args].
68 | *
69 | * Run with --help to get a list of available commandline options.
70 | */
71 | fun start(args: Array) {
72 | val cli = DefaultParser().parse(options, args)
73 | val theInterface = cli.getOptionValue("interface", DEFAULT_INTERFACE)
74 | val port = cli.getOptionValue("port", DEFAULT_PORT.toString()).toInt()
75 | val dev = cli.hasOption("dev")
76 | val help = cli.hasOption("help")
77 |
78 | val keystore = cli.getOptionValue("keystore")
79 | val tlsConfig = if (keystore == null) null else {
80 | val keystorePassword = cli.getOptionValue("keystore-password") ?: ""
81 | val keyPassword = cli.getOptionValue("key-password") ?: keystorePassword
82 | val keyAlias = cli.getOptionValue("key-alias")
83 | TlsConfig(keystore, keystorePassword, keyPassword, keyAlias)
84 | }
85 | val featureConfig = FeatureConfig(
86 | metrics = cli.hasOption("metrics")
87 | )
88 |
89 | if (help) {
90 | printHelp()
91 | } else {
92 | start(theInterface, port, dev, tlsConfig, featureConfig)
93 | }
94 | }
95 |
96 | /**
97 | * Starts Aleksa.
98 | *
99 | * [theInterface] sets the network interface to bind to (use 0.0.0.0 for all interfaces), [port] sets the
100 | * port to bind to. Enabling [dev] mode will disable request signature checking, timestamp checking and
101 | * application id verification. [tlsConfig] configures TLS. If set to null, no TLS will be used.
102 | * [featureConfig] enables additional features.
103 | */
104 | fun start(theInterface: String = DEFAULT_INTERFACE, port: Int = DEFAULT_PORT, dev: Boolean = DEFAULT_DEV, tlsConfig: TlsConfig? = null, featureConfig: FeatureConfig? = null) {
105 | run(theInterface, port, dev, tlsConfig, featureConfig)
106 | }
107 |
108 | /**
109 | * Adds a new [speechlet] which serves the skill with the given [applicationId] under the given [path].
110 | */
111 | fun addSpeechlet(path: String, applicationId: String, speechlet: SpeechletV2) {
112 | speechletRegistrations.add(SpeechletRegistration(path, applicationId, speechlet))
113 | }
114 |
115 | /**
116 | * Blocks the current thread until [stop] is called.
117 | */
118 | fun join() {
119 | server?.join()
120 | }
121 |
122 | /**
123 | * Stops Aleksa.
124 | */
125 | fun stop() {
126 | val server = this.server ?: return
127 | server.stop()
128 | speechletRegistrations.clear()
129 | this.server = null
130 | logger.info("Stopped")
131 | }
132 |
133 | /**
134 | * Prints the commandline help to stdout.
135 | */
136 | fun printHelp() {
137 | val formatter = HelpFormatter()
138 | formatter.printHelp("aleksa", options)
139 | }
140 |
141 | private fun run(theInterface: String, port: Int, dev: Boolean, tlsConfig: TlsConfig? = null, featureConfig: FeatureConfig?) {
142 | if (speechletRegistrations.isEmpty()) throw IllegalStateException("No speechlets registered. Use the addSpeechlet method to register at least one speechlet")
143 | if (server != null) throw IllegalStateException("Already running")
144 |
145 | setProperties(dev)
146 |
147 | val server = Server()
148 |
149 | val connector = if (tlsConfig == null) {
150 | ServerConnector(server)
151 | } else {
152 | configureTls(tlsConfig, server)
153 | }
154 | connector.host = theInterface
155 | connector.port = port
156 | server.connectors = arrayOf(connector)
157 |
158 | val servletHandler = ServletHandler()
159 |
160 | installSpeechlets(featureConfig, servletHandler)
161 | enableDevMode(dev, servletHandler)
162 | enableMetrics(featureConfig, servletHandler)
163 |
164 | server.handler = servletHandler
165 | server.start()
166 | this.server = server
167 |
168 | logger.info("Running on {}:{}", theInterface, port)
169 |
170 | }
171 |
172 | private fun installSpeechlets(featureConfig: FeatureConfig?, servletHandler: ServletHandler) {
173 | for (speechletRegistration in speechletRegistrations) {
174 | logger.info("Registering {} on {}", speechletRegistration.speechlet, speechletRegistration.path)
175 | val speechletServlet = SpeechletServlet()
176 |
177 | val speechlet = addMetricsToSpeechlet(speechletRegistration.speechlet, featureConfig)
178 | speechletServlet.setSpeechlet(speechlet)
179 | servletHandler.addServletWithMapping(ServletHolder(speechletServlet), speechletRegistration.path)
180 | }
181 | }
182 |
183 | private fun enableMetrics(featureConfig: FeatureConfig?, servletHandler: ServletHandler) {
184 | if (areMetricsEnabled(featureConfig)) {
185 | val hasOverriddenMetrics = speechletRegistrations.any { it.path == METRICS_PATH }
186 | if (hasOverriddenMetrics) {
187 | logger.warn("Can't add metrics, because a speechlet is running on $METRICS_PATH")
188 | } else {
189 | servletHandler.addServletWithMapping(ServletHolder(MetricsServlet(metricRegistry)), METRICS_PATH)
190 | logger.info("Metrics available on $METRICS_PATH")
191 | }
192 | }
193 | }
194 |
195 | private fun enableDevMode(dev: Boolean, servletHandler: ServletHandler) {
196 | if (dev) {
197 | val hasRoot = speechletRegistrations.any { it.path == ROOT_PATH }
198 | if (!hasRoot) {
199 | logger.debug("Installing root servlet on $ROOT_PATH")
200 | servletHandler.addServletWithMapping(ServletHolder(RootServlet), ROOT_PATH)
201 | }
202 |
203 | logger.info("DEV mode active")
204 | }
205 | }
206 |
207 | private fun areMetricsEnabled(featureConfig: FeatureConfig?) = featureConfig?.metrics == true
208 |
209 | /**
210 | * Add metrics to the given [speechlet], if enabled in the [featureConfig].
211 | */
212 | private fun addMetricsToSpeechlet(speechlet: SpeechletV2, featureConfig: FeatureConfig?): SpeechletV2 {
213 | return if (areMetricsEnabled(featureConfig)) MetricsSpeechletV2(speechlet, metricRegistry) else speechlet
214 | }
215 |
216 | private fun configureTls(tlsConfig: TlsConfig, server: Server): ServerConnector {
217 | val httpConfiguration = HttpConfiguration()
218 | httpConfiguration.addCustomizer(SecureRequestCustomizer())
219 | val sslContextFactory = SslContextFactory()
220 | sslContextFactory.keyStoreResource = Resource.newResource(tlsConfig.keystore)
221 | sslContextFactory.setKeyStorePassword(tlsConfig.keystorePassword)
222 | sslContextFactory.setKeyManagerPassword(tlsConfig.keyPassword)
223 | sslContextFactory.certAlias = tlsConfig.alias
224 |
225 | return ServerConnector(server, SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), HttpConnectionFactory(httpConfiguration))
226 | }
227 |
228 | /**
229 | * Sets the System properties for the [SpeechletServlet].
230 | *
231 | * If running in [dev] mode, timestamp tolerance, application id and request signature checking is disabled.
232 | */
233 | private fun setProperties(dev: Boolean) {
234 | if (dev) {
235 | System.setProperty(Sdk.DISABLE_REQUEST_SIGNATURE_CHECK_SYSTEM_PROPERTY, "true")
236 | System.setProperty(Sdk.TIMESTAMP_TOLERANCE_SYSTEM_PROPERTY, "")
237 | System.setProperty(Sdk.SUPPORTED_APPLICATION_IDS_SYSTEM_PROPERTY, "")
238 | } else {
239 | val applicationIds = speechletRegistrations.map { it.applicationId }.joinToString(",")
240 |
241 | System.setProperty(Sdk.DISABLE_REQUEST_SIGNATURE_CHECK_SYSTEM_PROPERTY, "false")
242 | System.setProperty(Sdk.TIMESTAMP_TOLERANCE_SYSTEM_PROPERTY, "150")
243 | System.setProperty(Sdk.SUPPORTED_APPLICATION_IDS_SYSTEM_PROPERTY, applicationIds)
244 | }
245 | }
246 |
247 | private data class SpeechletRegistration(val path: String, val applicationId: String, val speechlet: SpeechletV2)
248 | }
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/BuiltInIntents.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | /**
4 | * Built-in intents.
5 | *
6 | * See https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/standard-intents.
7 | */
8 | object BuiltInIntents {
9 | const val CANCEL = "AMAZON.CancelIntent"
10 | const val HELP = "AMAZON.HelpIntent"
11 | const val LOOP_OFF = "AMAZON.LoopOffIntent"
12 | const val LOOP_ON = "AMAZON.LoopOnIntent"
13 | const val NEXT = "AMAZON.NextIntent"
14 | const val NO = "AMAZON.NoIntent"
15 | const val PAUSE = "AMAZON.PauseIntent"
16 | const val PREVIOUS = "AMAZON.PreviousIntent"
17 | const val REPEAT = "AMAZON.RepeatIntent"
18 | const val RESUME = "AMAZON.ResumeIntent"
19 | const val SHUFFLE_OFF = "AMAZON.ShuffleOffIntent"
20 | const val SHUFFLE_ON = "AMAZON.ShuffleOnIntent"
21 | const val START_OVER = "AMAZON.StartOverIntent"
22 | const val STOP = "AMAZON.StopIntent"
23 | const val YES = "AMAZON.YesIntent"
24 | }
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/Constants.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | /**
4 | * SSML constants.
5 | */
6 | object SSML {
7 | /**
8 | * Breaks.
9 | */
10 | object Breaks {
11 | const val WEAK = ""
12 | const val MEDIUM = ""
13 | const val STRONG = ""
14 | }
15 | }
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/FeatureConfig.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | /**
4 | * Enables additional features of Aleksa.
5 | *
6 | * - [metrics]: True if metrics should be collected and made available under /metrics
7 | */
8 | data class FeatureConfig(
9 | val metrics: Boolean
10 | )
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/Functions.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | import com.amazon.speech.json.SpeechletRequestEnvelope
4 | import com.amazon.speech.slu.Intent
5 | import com.amazon.speech.speechlet.SpeechletResponse
6 | import com.amazon.speech.ui.OutputSpeech
7 | import com.amazon.speech.ui.PlainTextOutputSpeech
8 | import com.amazon.speech.ui.Reprompt
9 | import com.amazon.speech.ui.SsmlOutputSpeech
10 |
11 | /**
12 | * Creates an output speech from the given [text].
13 | *
14 | * Automatically detects SSML.
15 | */
16 | fun outputSpeech(text: String): OutputSpeech {
17 | return if (isSsml(text)) ssml(text) else plaintext(text)
18 | }
19 |
20 | /**
21 | * Creates plaintext speech from the given [text].
22 | */
23 | fun plaintext(text: String): PlainTextOutputSpeech {
24 | val result = PlainTextOutputSpeech()
25 | result.text = text
26 | return result
27 | }
28 |
29 | /**
30 | * Creates SSML speech from the given [ssml].
31 | */
32 | fun ssml(ssml: String): SsmlOutputSpeech {
33 | val result = SsmlOutputSpeech()
34 | result.ssml = ssml
35 | return result
36 | }
37 |
38 | /**
39 | * Creates a reprompt from the given [speech].
40 | */
41 | fun reprompt(speech: OutputSpeech): Reprompt {
42 | val reprompt = Reprompt()
43 | reprompt.outputSpeech = speech
44 | return reprompt
45 | }
46 |
47 | /**
48 | * Extracts the value of the slot with the given [name][slotName] from the [intent].
49 | */
50 | fun slotValue(intent: Intent, slotName: String): String? {
51 | val slot = intent.getSlot(slotName) ?: return null
52 | return slot.value
53 | }
54 |
55 | /**
56 | * Creates a tell response with the given [text].
57 | *
58 | * Automatically detects SSML.
59 | */
60 | fun tell(text: String): SpeechletResponse {
61 | return SpeechletResponse.newTellResponse(outputSpeech(text))
62 | }
63 |
64 | /**
65 | * Creates a ask response with the given [question] and a given [reprompt]. If the [reprompt] isn't set, it uses [question] as reprompt.
66 | *
67 | * Automatically detects SSML.
68 | */
69 | fun ask(question: String, reprompt: String = question): SpeechletResponse {
70 | return SpeechletResponse.newAskResponse(outputSpeech(question), reprompt(outputSpeech(reprompt)))
71 | }
72 |
73 | /**
74 | * Determines if the given [text] is SSML.
75 | */
76 | private fun isSsml(text: String): Boolean {
77 | return text.startsWith("")
78 | }
79 |
80 | /**
81 | * Reads the session value with the given [key] from the request.
82 | *
83 | * Returns null if the key doesn't exist or if the value assigned to key isn't a string.
84 | */
85 | fun getSessionString(request: SpeechletRequestEnvelope<*>, key: String): String? {
86 | val value = request.session.getAttribute(key) ?: return null
87 | return value as String
88 | }
89 |
90 | /**
91 | * Returns the SSML for a digit-spoken [telephone number][number] with reasonable pauses between them.
92 | *
93 | * Filters all non-digit characters from the given string.
94 | */
95 | fun telephoneNumber(number: String): String {
96 | return number
97 | .filter(Char::isDigit)
98 | .map { digit -> "$digit ${SSML.Breaks.WEAK}" }
99 | .joinToString(separator = "")
100 | }
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/RootServlet.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | import java.time.Instant
4 | import javax.servlet.http.HttpServlet
5 | import javax.servlet.http.HttpServletRequest
6 | import javax.servlet.http.HttpServletResponse
7 |
8 | /**
9 | * Is registered in DEV mode on "/".
10 | */
11 | object RootServlet : HttpServlet() {
12 | override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
13 | resp.writer.write("Aleksa running: ")
14 | resp.writer.write(Instant.now().toString())
15 | resp.writer.write("\n")
16 | }
17 | }
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/SpeechletV2Base.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | import com.amazon.speech.json.SpeechletRequestEnvelope
4 | import com.amazon.speech.speechlet.*
5 |
6 | /**
7 | * Abstract class which a speechlet can use.
8 | *
9 | * Implenents [onSessionEnded] and [onSessionStarted] with empty methods.
10 | */
11 | abstract class SpeechletV2Base : SpeechletV2 {
12 | override fun onSessionEnded(requestEnvelope: SpeechletRequestEnvelope) {
13 | }
14 |
15 | override fun onSessionStarted(requestEnvelope: SpeechletRequestEnvelope) {
16 | }
17 |
18 | abstract override fun onIntent(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse
19 |
20 | abstract override fun onLaunch(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse
21 | }
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/TlsConfig.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | /**
4 | * TLS config.
5 | *
6 | * The [keystore] points to the keystore file on disk. The [keystorePassword] specifies the password for the keystore,
7 | * [keyPassword] specifies the password for the key. [alias] sets the alias of the keypair which should be used. If null,
8 | * a matching keypair is chosen automatically.
9 | */
10 | data class TlsConfig(
11 | val keystore: String,
12 | val keystorePassword: String,
13 | val keyPassword: String = keystorePassword,
14 | val alias: String? = null
15 | )
--------------------------------------------------------------------------------
/src/main/kotlin/de/mkammerer/aleksa/metrics/MetricsSpeechletV2.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa.metrics
2 |
3 | import com.amazon.speech.json.SpeechletRequestEnvelope
4 | import com.amazon.speech.speechlet.*
5 | import com.codahale.metrics.MetricRegistry
6 |
7 | /**
8 | * Decorator for a [SpeechletV2] which collects metrics.
9 | */
10 | class MetricsSpeechletV2(
11 | private val delegate: SpeechletV2,
12 | private val metricRegistry: MetricRegistry
13 | ) : SpeechletV2 {
14 | private val sessionsStarted = metricRegistry.timer("sessions-started")
15 | private val sessionsEnded = metricRegistry.timer("sessions-ended")
16 | private val totalIntentsHandled = metricRegistry.timer("total-intents-handled")
17 | private val launches = metricRegistry.timer("launches")
18 |
19 | override fun onSessionStarted(requestEnvelope: SpeechletRequestEnvelope) {
20 | sessionsStarted.time {
21 | delegate.onSessionStarted(requestEnvelope)
22 | }
23 | }
24 |
25 | override fun onSessionEnded(requestEnvelope: SpeechletRequestEnvelope) {
26 | sessionsEnded.time {
27 | delegate.onSessionEnded(requestEnvelope)
28 | }
29 | }
30 |
31 | override fun onIntent(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
32 | return totalIntentsHandled.timeSupplier {
33 | val intent = requestEnvelope.request.intent.name
34 | metricRegistry.timer("intent-handled:$intent").timeSupplier {
35 | delegate.onIntent(requestEnvelope)
36 | }
37 | }
38 | }
39 |
40 | override fun onLaunch(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
41 | return launches.timeSupplier {
42 | delegate.onLaunch(requestEnvelope)
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/src/test/kotlin/de/mkammerer/aleksa/AleksaTest.kt:
--------------------------------------------------------------------------------
1 | package de.mkammerer.aleksa
2 |
3 | import com.amazon.speech.json.SpeechletRequestEnvelope
4 | import com.amazon.speech.speechlet.IntentRequest
5 | import com.amazon.speech.speechlet.LaunchRequest
6 | import com.amazon.speech.speechlet.SpeechletResponse
7 | import org.eclipse.jetty.util.resource.Resource
8 | import org.junit.Test
9 |
10 | class AleksaTest {
11 | @Test
12 | fun test() {
13 | val tlsConfig = TlsConfig(
14 | Resource.newClassPathResource("/tls.jks").toString(),
15 | "keystore-pw", "key-pw",
16 | "alias-2"
17 | )
18 |
19 | val featureConfig = FeatureConfig(
20 | metrics = true
21 | )
22 |
23 | Aleksa.addSpeechlet("/dummy", "12345", DummySpeechlet)
24 | Aleksa.start("localhost", 9999, true, tlsConfig, featureConfig)
25 | Aleksa.stop()
26 | }
27 |
28 | @Test
29 | fun testCommandline() {
30 | Aleksa.addSpeechlet("/dummy", "12345", DummySpeechlet)
31 | Aleksa.start(arrayOf(
32 | "--interface", "localhost",
33 | "--port", "9999",
34 | "--dev",
35 | "--metrics",
36 | "--keystore", Resource.newClassPathResource("/tls.jks").toString(),
37 | "--keystore-password", "keystore-pw",
38 | "--key-password", "key-pw",
39 | "--key-alias", "alias-2"
40 | ))
41 | Aleksa.stop()
42 | }
43 | }
44 |
45 | private object DummySpeechlet : SpeechletV2Base() {
46 | override fun onIntent(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
47 | return tell("Dummy")
48 | }
49 |
50 | override fun onLaunch(requestEnvelope: SpeechletRequestEnvelope): SpeechletResponse {
51 | return ask("Dummy")
52 | }
53 | }
54 |
55 | fun main(args: Array) {
56 | Aleksa.addSpeechlet("/dummy", "12345", DummySpeechlet)
57 | Aleksa.start(args)
58 | Aleksa.join()
59 | }
--------------------------------------------------------------------------------
/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/test/resources/requests/request-1.json:
--------------------------------------------------------------------------------
1 | {
2 | "session": {
3 | "new": true,
4 | "sessionId": "SessionId.b36adac7-f242-4a84-b28e-c8819464c5b0",
5 | "application": {
6 | "applicationId": "12345"
7 | },
8 | "attributes": {},
9 | "user": {
10 | "userId": "amzn1.ask.account.foobar"
11 | }
12 | },
13 | "request": {
14 | "type": "IntentRequest",
15 | "requestId": "EdwRequestId.3163481a-e9dc-4e5b-bb22-7d1203962e11",
16 | "intent": {
17 | "name": "DummyIntent",
18 | "slots": {
19 | }
20 | },
21 | "locale": "de-DE",
22 | "timestamp": "2017-12-02T13:41:00Z"
23 | },
24 | "context": {
25 | "AudioPlayer": {
26 | "playerActivity": "IDLE"
27 | },
28 | "System": {
29 | "application": {
30 | "applicationId": "amzn1.ask.skill.12345"
31 | },
32 | "user": {
33 | "userId": "amzn1.ask.account.foobar"
34 | },
35 | "device": {
36 | "supportedInterfaces": {}
37 | }
38 | }
39 | },
40 | "version": "1.0"
41 | }
--------------------------------------------------------------------------------
/src/test/resources/requests/request-2.json:
--------------------------------------------------------------------------------
1 | {
2 | "session": {
3 | "new": true,
4 | "sessionId": "SessionId.b36adac7-f242-4a84-b28e-c8819464c5b0",
5 | "application": {
6 | "applicationId": "12345"
7 | },
8 | "attributes": {},
9 | "user": {
10 | "userId": "amzn1.ask.account.foobar"
11 | }
12 | },
13 | "request": {
14 | "type": "IntentRequest",
15 | "requestId": "EdwRequestId.3163481a-e9dc-4e5b-bb22-7d1203962e11",
16 | "intent": {
17 | "name": "FoobarIntent",
18 | "slots": {
19 | }
20 | },
21 | "locale": "de-DE",
22 | "timestamp": "2017-12-02T13:41:00Z"
23 | },
24 | "context": {
25 | "AudioPlayer": {
26 | "playerActivity": "IDLE"
27 | },
28 | "System": {
29 | "application": {
30 | "applicationId": "amzn1.ask.skill.12345"
31 | },
32 | "user": {
33 | "userId": "amzn1.ask.account.foobar"
34 | },
35 | "device": {
36 | "supportedInterfaces": {}
37 | }
38 | }
39 | },
40 | "version": "1.0"
41 | }
--------------------------------------------------------------------------------
/src/test/resources/requests/request-3.json:
--------------------------------------------------------------------------------
1 | {
2 | "session": {
3 | "new": true,
4 | "sessionId": "SessionId.b36adac7-f242-4a84-b28e-c8819464c5b0",
5 | "application": {
6 | "applicationId": "12345"
7 | },
8 | "attributes": {},
9 | "user": {
10 | "userId": "amzn1.ask.account.foobar"
11 | }
12 | },
13 | "request": {
14 | "type": "IntentRequest",
15 | "requestId": "EdwRequestId.3163481a-e9dc-4e5b-bb22-7d1203962e11",
16 | "intent": {
17 | },
18 | "locale": "de-DE",
19 | "timestamp": "2017-12-02T13:41:00Z"
20 | },
21 | "context": {
22 | "AudioPlayer": {
23 | "playerActivity": "IDLE"
24 | },
25 | "System": {
26 | "application": {
27 | "applicationId": "amzn1.ask.skill.12345"
28 | },
29 | "user": {
30 | "userId": "amzn1.ask.account.foobar"
31 | },
32 | "device": {
33 | "supportedInterfaces": {}
34 | }
35 | }
36 | },
37 | "version": "1.0"
38 | }
--------------------------------------------------------------------------------
/src/test/resources/tls.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phxql/aleksa/5d4b9a345e6515aa8dfb8370364ee6b730e50c6e/src/test/resources/tls.jks
--------------------------------------------------------------------------------