diff --git a/.gitignore b/.gitignore index 72364f9..f073741 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,6 @@ ENV/ # Rope project settings .ropeproject + +Archive.zip +.DS_Store diff --git a/.lamvery.api.yml b/.lamvery.api.yml deleted file mode 100644 index 087fa9b..0000000 --- a/.lamvery.api.yml +++ /dev/null @@ -1,36 +0,0 @@ -api_id: -stage: dev -cors: - origin: '*' - methods: - - GET - - OPTION - headers: - - Content-Type - - X-Amz-Date - - Authorization - - X-Api-Key -configuration: - swagger: '2.0' - info: - title: Sample API - schemes: - - https - paths: - /: - get: - produces: - - application/json - parameters: - - name: sample - in: query - required: false - type: string - responses: - '200': - description: 200 response - schema: - $ref: '#/definitions/Sample' - definitions: - Sample: - type: object diff --git a/.lamvery.event.yml b/.lamvery.event.yml deleted file mode 100644 index 01c57e8..0000000 --- a/.lamvery.event.yml +++ /dev/null @@ -1 +0,0 @@ -rules: diff --git a/.lamvery.exclude.yml b/.lamvery.exclude.yml deleted file mode 100644 index 0f365da..0000000 --- a/.lamvery.exclude.yml +++ /dev/null @@ -1,12 +0,0 @@ -- ^\..+$ -- ^.+\.o$ -- ^.+\.zip$ -- ^.+\.c$ -- ^.+\.h$ -- ^Makefile$ -- ^README.md$ -- ^readme.txt$ -- ^history$ -- ^intents.json$ -- ^utterences.txt$ -- ^alexa-types/.*$ diff --git a/.lamvery.hook.yml b/.lamvery.hook.yml deleted file mode 100644 index 99cc599..0000000 --- a/.lamvery.hook.yml +++ /dev/null @@ -1,3 +0,0 @@ -build: - pre: [] - post: [] diff --git a/.lamvery.secret.yml b/.lamvery.secret.yml deleted file mode 100644 index 839944e..0000000 --- a/.lamvery.secret.yml +++ /dev/null @@ -1,3 +0,0 @@ -key_id: -cipher_texts: {} -secret_files: {} diff --git a/.lamvery.yml b/.lamvery.yml deleted file mode 100644 index 527f5d0..0000000 --- a/.lamvery.yml +++ /dev/null @@ -1,13 +0,0 @@ -profile: personal -region: us-east-1 -versioning: false -default_alias: null -clean_build: true -configuration: - name: AlexaZork - runtime: python2.7 - role: arn:aws:iam::ACCOUNTID:role/service-role/AlexaZork2 - handler: lambda_function.lambda_handler - description: Zork for Alexa - timeout: 10 - memory_size: 128 diff --git a/alexa-types/LOCATION b/Alexa Skill/alexa-types/LOCATION similarity index 100% rename from alexa-types/LOCATION rename to Alexa Skill/alexa-types/LOCATION diff --git a/alexa-types/NOUN b/Alexa Skill/alexa-types/NOUN similarity index 100% rename from alexa-types/NOUN rename to Alexa Skill/alexa-types/NOUN diff --git a/alexa-types/SWITCH b/Alexa Skill/alexa-types/SWITCH similarity index 100% rename from alexa-types/SWITCH rename to Alexa Skill/alexa-types/SWITCH diff --git a/alexa-types/USING b/Alexa Skill/alexa-types/USING similarity index 100% rename from alexa-types/USING rename to Alexa Skill/alexa-types/USING diff --git a/alexa-types/VERB b/Alexa Skill/alexa-types/VERB similarity index 100% rename from alexa-types/VERB rename to Alexa Skill/alexa-types/VERB diff --git a/intents.json b/Alexa Skill/intents.json similarity index 100% rename from intents.json rename to Alexa Skill/intents.json diff --git a/utterences.txt b/Alexa Skill/utterences.txt similarity index 100% rename from utterences.txt rename to Alexa Skill/utterences.txt diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/dtextc.dat b/Lamdba/dtextc.dat similarity index 100% rename from dtextc.dat rename to Lamdba/dtextc.dat diff --git a/lambda_function.py b/Lamdba/lambda_function.py similarity index 77% rename from lambda_function.py rename to Lamdba/lambda_function.py index cf7e19a..27d7255 100644 --- a/lambda_function.py +++ b/Lamdba/lambda_function.py @@ -7,14 +7,22 @@ import subprocess import zlib import boto3 +from botocore.exceptions import ClientError +import sys +#Read in the configuration # bucket to store save games -BUCKET = 'alexazork' +BUCKET = os.environ['BUCKET'] +# Access key for IAM user to access S3 bucket +ACCESS_KEY = os.environ['ACCESS_KEY'] +# Secret key for IAM user to access S3 bucket +SECRET_KEY = os.environ['SECRET_KEY'] +print('Bucket is ', BUCKET) def run_zork(input, save=None, look=False): - '''Handle I/O to Zork binary, after copying it to a working directory.''' - + #'''Handle I/O to Zork binary, after copying it to a working directory.''' + # if we have a save file, restore it where Zork expects it if save: with open('/tmp/dsave.dat', 'wb') as out: @@ -34,20 +42,23 @@ def run_zork(input, save=None, look=False): shutil.copyfile('/var/task/zork', '/tmp/zork') shutil.copyfile('/var/task/dtextc.dat', '/tmp/dtextc.dat') - os.chmod('/tmp/zork', 0755) + os.chmod('/tmp/zork', 0o755) os.chdir('/tmp') print('sending input to zork:', json.dumps({'input': input})) # run Zork binary and construct pipes cmd = subprocess.Popen(['/tmp/zork'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) - + # send and retrieve I/O - stdout, stderr = cmd.communicate(input) + stdout, stderr = cmd.communicate(input.encode('ascii')) + + stdout = stdout.decode('ascii') + stderr = stderr.decode('ascii') print('got output:', json.dumps({'stdout': stdout.split('\n'), 'stderr': stderr})) print('return code', str(cmd.returncode)) - + # we need to do some formatting of the vanilla Zork output message = stdout.replace('\n>', '\n') # get rid of prompts message = message.split('\n', 1)[1] # first line is header junk @@ -59,7 +70,7 @@ def run_zork(input, save=None, look=False): def get_save(): - '''Return an encoded string representing the current save file.''' + #'''Return an encoded string representing the current save file.''' if os.path.exists('/tmp/dsave.dat'): with open('/tmp/dsave.dat', 'rb') as save: return base64.b64encode(zlib.compress(save.read())) @@ -68,7 +79,7 @@ def get_save(): def build_speechlet_response(title, output, reprompt_text, should_end_session): - ''''Build JSON structure for Alexa response.'''' + #''''Build JSON structure for Alexa response.'''' return { 'outputSpeech': { 'type': 'PlainText', @@ -98,28 +109,41 @@ def build_response(session_attributes, speechlet_response): def get_save_key(session): - '''Returns path to save location in S3 for a user.'''' + #'''Returns path to save location in S3 for a user.'''' user_id = session['user']['userId'] + print('user id is ', user_id) return 'saves/' + user_id def get_save_from_s3(save_key, s3): - ''''Download save game from S3.'''' + #''''Download save game from S3.'''' + print('Download save game from S3') try: key = s3.get_object(Bucket=BUCKET, Key=save_key) saved_game = key['Body'].read() + except ClientError as e: + if e.response['Error']['Code'] == 'EntityAlreadyExists': + print('User already exists') + else: + print('Unexpected error:',e) + saved_game = None except: + print('Unable to get saved game') + print("Unexpected error:", sys.exc_info()[0]) saved_game = None return saved_game def get_welcome_response(session): - '''Welcome the user to Zork!''' + #'''Welcome the user to Zork!''' session_attributes = session.get('attributes', {}) save_key = get_save_key(session) - s3 = boto3.client('s3') + s3 = boto3.client('s3', + aws_access_key_id=ACCESS_KEY, + aws_secret_access_key=SECRET_KEY) + saved_game = get_save_from_s3(save_key, s3) card_title = "Welcome" @@ -137,7 +161,7 @@ def get_welcome_response(session): def handle_session_end_request(): - '''Say goodbye to the user.''' + #'''Say goodbye to the user.''' card_title = "Session Ended" speech_output = "Thank you for visiting Zork. " \ "Have a nice day! " @@ -148,15 +172,17 @@ def handle_session_end_request(): def do_zork(intent, session, command=None): - '''General purpose Zork commands. - - Handles save/resume as well as action sorting on input. - ''' + #'''General purpose Zork commands. + + #Handles save/resume as well as action sorting on input. + #''' card_title = intent['name'] session_attributes = {} should_end_session = False - s3 = boto3.client('s3') + s3 = boto3.client('s3', + aws_access_key_id=ACCESS_KEY, + aws_secret_access_key=SECRET_KEY) save_key = get_save_key(session) saved_game = get_save_from_s3(save_key, s3) @@ -190,9 +216,11 @@ def do_zork(intent, session, command=None): def reset_game(intent, session): - '''Resets a user's game by deleting their save file.''' + #'''Resets a user's game by deleting their save file.''' save_key = get_save_key(session) - s3 = boto3.client('s3') + s3 = boto3.client('s3', + aws_access_key_id=ACCESS_KEY, + aws_secret_access_key=SECRET_KEY) try: s3.delete_object(Bucket=BUCKET, Key=save_key) @@ -211,9 +239,9 @@ def on_session_started(session_started_request, session): def on_launch(launch_request, session): - """ Called when the user launches the skill without specifying what they - want - """ + #""" Called when the user launches the skill without specifying what they + #want + #""" print("on_launch requestId=" + launch_request['requestId'] + ", sessionId=" + session['sessionId']) @@ -222,7 +250,7 @@ def on_launch(launch_request, session): def on_intent(intent_request, session): - """ Called when the user specifies an intent for this skill """ + #""" Called when the user specifies an intent for this skill """ print("on_intent requestId=" + intent_request['requestId'] + ", sessionId=" + session['sessionId']) @@ -248,27 +276,27 @@ def on_intent(intent_request, session): def on_session_ended(session_ended_request, session): - """ Called when the user ends the session. - - Is not called when the skill returns should_end_session=true - """ + #""" Called when the user ends the session. + # + #Is not called when the skill returns should_end_session=true + #""" print("on_session_ended requestId=" + session_ended_request['requestId'] + ", sessionId=" + session['sessionId']) # add cleanup logic here def lambda_handler(event, context): - """ Route the incoming request based on type (LaunchRequest, IntentRequest, - etc.) The JSON body of the request is provided in the event parameter. - """ + #""" Route the incoming request based on type (LaunchRequest, IntentRequest, + #etc.) The JSON body of the request is provided in the event parameter. + #""" print("event.session.application.applicationId=" + event['session']['application']['applicationId']) - """ - Uncomment this if statement and populate with your skill's application ID to - prevent someone else from configuring a skill that sends requests to this - function. - """ + #""" + #Uncomment this if statement and populate with your skill's application ID to + #prevent someone else from configuring a skill that sends requests to this + #function. + #""" # if (event['session']['application']['applicationId'] != # "amzn1.echo-sdk-ams.app.[unique-value-here]"): # raise ValueError("Invalid Application ID") diff --git a/zork b/Lamdba/zork similarity index 100% rename from zork rename to Lamdba/zork diff --git a/zorkreadme.txt b/Lamdba/zorkreadme.txt similarity index 100% rename from zorkreadme.txt rename to Lamdba/zorkreadme.txt diff --git a/README.md b/README.md index e452193..2c1eff0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,148 @@ # AlexaZork -Zork for your Amazon Alexa device + +Zork for your Amazon Alexa device. + +This project was made by Team DevOps Dungeoneers (Jacob Foster, Matt Thompson, and Myles Loffler) and published on [hackster.io](https://www.hackster.io/devops-dungeoneers/classic-zork-494ff1). + +The initial project was updated with documentation and installation instructions, as well as with changes to the Lambda function to get it to run. + +# Notes +The version of Zork is based on the MIT Dungeon game. If you are looking at walkthroughs and maps online, many of them are for Infocom version. + +![MIT Zork Dungeon Map](https://i.redd.it/6cxj790ozfjy.jpg) + + +# Files + +The files in this project are defined below. They are organized by their target (Alexa Skill, Lamdba Function, and S3 storage). + +## Alexa Skill + +These files are used to create the Alexa Skill and will be used in the Alexa Skill developer portal. https://developer.amazon.com/edw/home.html#/skills. + +* **intents.json** - This file contains the intents for the Alexa Skill and will be pasted in to the Interaction Model Intent Schema. +* **utterances.txt** - This file contains the possible utterances and is used to configure the Alexa Skill in the "Sample Utterances" section. + +### alexa-types +These file represent the types used to configured the Alexa Skill. Each type file contains the list of possible values. +* **LOCATION** +* **NOUN** +* **SWITCH** +* **USING** +* **VERB** + +## Lamda +These files are used to build the Lambda function that takes the utterances from the Alexa Skill and processes the command against the Zork game binary. +* **dtextc.dac** - Holds text strings and initialization information for the game. +* **zork** - This is the compiled Zork game binary. +* **lambda_function.py** - This is the Python Lambda function. +* **zork.properties.sample** - Sample zork.properties file. +* **zorkreadme.txt** - This is the readme for Zork. + + +# Installation Instructions + +In order to get the game working on Alexa, you will need to deploy an Alexa Skill, a Lamdba function, and an S3 bucket. Additionally, you will create an Identify and Access Management (IAM) user that will be used by your Lamdba function to access the S3 bucket. The installation instructions will go in reverse order to simplify the configuration of previous steps and to allow for the Alexa Skill to be tested when it is deployed. + +# Create IAM User + +First, create a new IAM User. Full instructions on the process can be found here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html. +1. Log in to the [AWS Console](https://console.aws.amazon.com/). +2. Navigate to *Identify and Access Management (IAM)*. +3. Click *Users*. +4. Click *Add User*. +5. Give your user a name and check the **Programmatic Access** box. +6. Click *Next: Permissions*. +7. Click *Next: Review*. +8. Click *Create User*. +9. Copy the **User ARN**, **Access key ID**, **Secret access key** values. + +# Create S3 Bucket + +Next, we're going to create the S3 bucket that will be used to save the game progress. + +1. Log in to the [AWS Console](https://console.aws.amazon.com/). +2. Navigate to *S3*. +3. Click *Create Bucket*. +4. Give it a name and region and click *Next*. +5. You do not need to change the properties. Click *Next*. +6. Leave the permissions as the default values and click *Next*. +7. Click *Create Bucket*. +8. From the dashboard, click on the name of the bucket you just created. +9. Click the *Permissions* tab. +10. Click the *Buckey Policy* button. +11. Copy the text from the **polcy.sample** file in the S3 folder. +12. Update the **"AWS": "arn:aws:iam::1483726191252:user/youriamuser"** line to reflect the **User ARN** for the IAM user you created above. +13. Update the **"Resource": "arn:aws:s3:::yourbuckename/*"** line to reflect the name of the S3 bucket you created. + +# Create Lamdba Function + +Next, we're going to create the Lamdba function. This is the Python function that takes input from the Alexa Skill, executes it against the Zork binary, and returns the output back to the Alexa Skill. It needs to be configured to point to your S3 bucket which will be done through *environment variables*. + +1. Log in to the [AWS Lamdba Console](https://console.aws.amazon.com/lambda/). +2. Click *Create a Lambda Function*. +3. Select the **Blank Function** template. +4. On the **Configure triggers** screen, click the empty box next to the Lamdba icon and select **Alexa Skills Kit** and click *Next*. +5. Give your function a name, description, and select **Python 2.7** as the runtime. +6. Create the following three environment variables in your function by typing the key and value in the **Environment variables** section: + - **BUCKET** - Specify your S3 bucket name. + - **SECRET_KEY** - Specify the secret key for your IAM user. + - **ACCESS_KEY** - Specify the access key for your IAM user. +7. Create a zip file with the **dtextc.dat, lamdba_function.py, zork, and zorkreadme.txt** files. +8. In the *Code entry type* drop down, select **Upload a .zip file**. +9. Click *Upload* and select the zip file. +10. Select **lambda_basic_execution** in the **Existing role** drop down and click *Next*. +11. Click *Create function*. + +**Note: Leave this AWS Lamdba browser window open. It will be useful for troubleshooting. You may also want to click on the Monitoring tab of your new function and click the *View logs in CloudWatch* link so that you can monitor the error logs.** + +## Create Alexa Skill + +Finally, we're going to create the Alexa Skill. + +1. Log in to the [Alexa Skill Developer Console](https://developer.amazon.com/edw/home.html#/skills). +2. Click *Add a New Skill*. +3. Give the skill a **name** and **invocation name** and click *Save*. +4. Click the *Interaction model* tab. +5. Paste the contents of the *intents.json* file in to the **Intent Schema** box. +6. Paste the contents of the *utterances.txt* file in to the **Sample Utterances** box. +7. Next, for each of the files in the *alexa-types* folder, under **Custome slot types**: + - Click *Add slot type*. + - Use the name of the file (e.g., LOCATION) as the type name. + - Paste the contents of the file in to the **Enter Values** box. + - Click *Add*. +8. Once all the types are added, click the *Configuration* tab. +9. Get the ARN value from your Lambda function, select **AWS Lamdba ARN** as the type and paste your ARN value in to the **Service Endpoint** box. +10. Click *Next*. + +# Testing + +## Testing From The Alexa Skill Console + +At this point, you should be full configured. On the *Test* tab of the Alexa Skill, you can enter "look" in the **Enter utterance** box and click "Ask". You should see the request and response. + +## Testing From Alexa + +If you have enabled it on the *Test* tab of your Alexa Skill, you can invoke your skill and test Zork on your Alexa. + +# Troubleshooting +## Save Game and S3 Interaction +The game depends on the save game functionality working in order to sequence events. The leaflet test is a good example. When you open the mailbox and take the leaflet, if you succeed, +that means the sequencing is working. If you try to take the leaflet and the game responds that it can't find the leaflet, it's likely because the save game isn't working and the step that opens the mailbox was forgotten. You can verify by again opening the mailbox. + +Check through the logs of CloudWatch to see if your Lamdba function is failing on the S3 save or load process. You can also check your bucket in S3 to verify that a file was created inside of a **saves** folder. + +# Change Log +## Initial Release - December 1, 2016 +Initial release by Team DevOps Dungeoneers. + +## 0.0.1 - April 18, 2017 +### Added +- Installation instructions and project information to the README.md + +### Changed +- Updated file structure to create folders for the different targets (Alexa, Lamdba, S3). +- Updated Lamdba function to use environment variables + +### Fixed +- Fixed lamdba function. diff --git a/S3/policy.sample b/S3/policy.sample new file mode 100644 index 0000000..f9fbbdc --- /dev/null +++ b/S3/policy.sample @@ -0,0 +1,19 @@ +{ + "Version": "2012-10-17", + "Id": "Policy1483726191252", + "Statement": [ + { + "Sid": "Stmt1483726191252", + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::1483726191252:user/youriamuser" + }, + "Action": [ + "s3:DeleteObject", + "s3:GetObject", + "s3:PutObject" + ], + "Resource": "arn:aws:s3:::yourbuckename/*" + } + ] +}