├── .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 --------------------------------------------------------------------------------