├── .gitignore ├── README.rst ├── cookiecutter.json └── {{ cookiecutter.repo_name }} ├── .gitignore ├── README.rst ├── docs ├── Makefile └── source │ ├── conf.py │ └── index.rst ├── requirements.txt ├── speech_assets ├── custom_slot_types │ └── .gitkeep ├── intent_schema.json └── sample_utterances.txt ├── templates.yaml └── {{ cookiecutter.repo_name }}.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/ 8 | 9 | ## File-based project format: 10 | *.iws 11 | 12 | ## Plugin-specific files: 13 | 14 | # IntelliJ 15 | /out/ 16 | 17 | # mpeltonen/sbt-idea plugin 18 | .idea_modules/ 19 | 20 | # JIRA plugin 21 | atlassian-ide-plugin.xml 22 | 23 | # Crashlytics plugin (for Android Studio and IntelliJ) 24 | com_crashlytics_export_strings.xml 25 | crashlytics.properties 26 | crashlytics-build.properties 27 | fabric.properties 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Flask-Ask cookiecutter 2 | ====================== 3 | 4 | This cookiecutter creates a ready-to-roll Alexa Skill skeleton based on `Flask-Ask`_, an amazing framework by `John 5 | Wheeler`_. Flask-Ask is quite possibly the fastest way to get started with developing Alexa Skills, and this 6 | cookiecutter only makes it faster! 7 | 8 | What's in the box 9 | ----------------- 10 | 11 | - A Flask Python application, including handlers for session start, launch and a number of built-in ``AMAZON`` intents, such as ``Yes``, ``No``, and ``Help``. 12 | - A sample welcome intention that shows the way templating works in Flask-Ask, as well as a ``template.yaml`` template file with some examples. 13 | - A ``speech_assets`` folder, to hold your speech assets. 14 | - An (empty, but gitkeep'd) ``custom_slot_types`` folder to keep your list of custom slot values. 15 | - An ``intent_schema.json``, which you can use as your template for building up the intent schema. 16 | - An empty ``sample_utterances.txt`` file (since the intentions currently provided for don't need utterances...). 17 | - A ``requirements.txt`` file with all you need. 18 | - A ``README.rst``, explaining requirements and the setup process. 19 | 20 | Short of a partridge in a pear tree, this contains everything you might need to get cracking with your very own Alexa skill! Read on below for directions on getting started with `Flask-Ask`_. 21 | 22 | 23 | Setup 24 | ----- 25 | 26 | To create the scaffolded project skeleton in your project, all you need to do is 27 | 28 | 1. make sure you have ``cookiecutter`` installed (if not, go `here`_), 29 | 2. get ``cookiecutter`` to pull this repo and start scaffolding by entering the following command into your shell of choice: ``cookiecutter gh:chrisvoncsefalvay/cookiecutter-flask-ask`` , and 30 | 3. follow the instructions in the ``README.rst`` file in your brand new repo! 31 | 32 | That's all! 33 | 34 | 35 | Contribute 36 | ---------- 37 | 38 | Contributions are welcome! I'm also on the `Flask-Ask Gitter`_ a lot, so you are welcome to let me know just what I 39 | could do to make this an even better tool for Alexa developers! 40 | 41 | 42 | .. _here: https://github.com/audreyr/cookiecutter 43 | .. _John Wheeler: https://alexatutorial.com 44 | .. _Flask-Ask: https://alexatutorial.com/flask-ask 45 | .. _Flask-Ask Gitter: https://gitter.im/johnwheeler/flask-ask/ 46 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "skill_name": "My Flask-Ask skill", 3 | "skill_short_description": "An Alexa Skill to do some amazing stuff.", 4 | "repo_name": "my_flask_ask_skill", 5 | "author_name": "Your Name", 6 | "author_email_address": "your@email-address.io", 7 | "github_username": "username", 8 | "version": "0.1.0", 9 | "port": 5000 10 | } -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | .idea/tasks.xml 3 | .idea/dataSources/ 4 | .idea/dataSources.ids 5 | .idea/dataSources.xml 6 | .idea/dataSources.local.xml 7 | .idea/sqlDataSources.xml 8 | .idea/dynamic.xml 9 | .idea/uiDesigner.xml 10 | .idea/gradle.xml 11 | .idea/libraries 12 | .idea/mongoSettings.xml 13 | *.iws 14 | /out/ 15 | .idea_modules/ 16 | atlassian-ide-plugin.xml 17 | com_crashlytics_export_strings.xml 18 | crashlytics.properties 19 | crashlytics-build.properties 20 | fabric.properties 21 | __pycache__/ 22 | *.py[cod] 23 | *$py.class 24 | *.so 25 | .Python 26 | env/ 27 | build/ 28 | develop-eggs/ 29 | dist/ 30 | downloads/ 31 | eggs/ 32 | .eggs/ 33 | lib/ 34 | lib64/ 35 | parts/ 36 | sdist/ 37 | var/ 38 | *.egg-info/ 39 | .installed.cfg 40 | *.egg 41 | *.manifest 42 | *.spec 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *,cover 53 | .hypothesis/ 54 | *.mo 55 | *.pot 56 | *.log 57 | local_settings.py 58 | instance/ 59 | .webassets-cache 60 | .scrapy 61 | docs/_build/ 62 | target/ 63 | .ipynb_checkpoints 64 | .python-version 65 | celerybeat-schedule 66 | .env 67 | .venv/ 68 | venv/ 69 | ENV/ 70 | .spyderproject 71 | .ropeproject 72 | .Python 73 | [Bb]in 74 | [Ii]nclude 75 | [Ll]ib 76 | [Ll]ib64 77 | [Ll]ocal 78 | [Ss]cripts 79 | pyvenv.cfg 80 | .venv 81 | pip-selfcheck.json 82 | 83 | # An Ignore for scratch and test API outputs 84 | test_*.json 85 | test_*.xml 86 | -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/README.rst: -------------------------------------------------------------------------------- 1 | {{ cookiecutter.skill_name }} 2 | ============================= 3 | 4 | {{ cookiecutter.skill_short_description }} 5 | 6 | Setup 7 | ----- 8 | 9 | It is recommended to run this project in a virtualenv. If virtualenvs are unfamiliar to you, `this handy tutorial`_ 10 | might be a good place to start. 11 | 12 | #. Create a virtualenv for this project, and activate it. 13 | #. Use ``pip install -r requirements.txt`` to install the required Python packages. 14 | #. You will require ``ngrok`` to make your skill accessible to Alexa for testing. You can download ngrok `here`_. 15 | 16 | .. _here: https://ngrok.com/download 17 | .. _this handy tutorial: http://docs.python-guide.org/en/latest/dev/virtualenvs/ 18 | 19 | Quickstart 20 | ---------- 21 | 22 | Follow these easy steps to test your brand new Flask-Ask project. 23 | 24 | #. Launch the server by invoking ``python {{cookiecutter.repo_name}}.py``. 25 | #. With the server running, start ``ngrok http {{cookiecutter.port}}``. 26 | #. Configure your app on the `Alexa Developer Portal`_. `This video`_ by `John Wheeler`_ shows how to deploy your speech assets configuration to the `Alexa Developer Portal`_. 27 | #. That's all! If you are using a browser that supports WebRTC for micophone input (Chrome, Firefox or Opera), you may use `echosim`_ to test your script - simply log in with the same credentials you used to deploy your Skill. 28 | 29 | .. _Alexa Developer Portal: https://developer.amazon.com/alexa 30 | .. _This video: https://alexatutorial.com 31 | .. _John Wheeler: https://alexatutorial.com/flask-ask/ 32 | .. _echosim: http://www.echosim.io/ 33 | -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | 3 | # You can set these variables from the command line. 4 | SPHINXOPTS = 5 | SPHINXBUILD = sphinx-build 6 | SPHINXPROJ = {{ cookiecutter.repo_name }} 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # {{ cookiecutter.skill_name }} documentation build configuration file, 5 | # 6 | 7 | import os 8 | import sys 9 | sys.path.insert(0, os.path.abspath('.')) 10 | 11 | 12 | # -- General configuration ------------------------------------------------ 13 | 14 | import sphinx_rtd_theme 15 | 16 | extensions = ['sphinx.ext.autodoc', 'sphinx_autodoc_typehints', 'sphinx.ext.doctest', 'sphinx.ext.todo', 17 | 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 18 | 'sphinx.ext.githubpages'] 19 | 20 | # Add any paths that contain templates here, relative to this directory. 21 | templates_path = ['_templates'] 22 | 23 | source_suffix = '.rst' 24 | 25 | # The master toctree document. 26 | master_doc = 'index' 27 | 28 | # General information about the project. 29 | project = '{{ cookiecutter.skill_name }}' 30 | copyright = '2017, {{ cookiecutter.author_name }}' 31 | author = '{{ cookiecutter.author_name }}' 32 | 33 | # The version info for the project you're documenting, acts as replacement for 34 | # |version| and |release|, also used in various other places throughout the 35 | # built documents. 36 | # 37 | # The short X.Y version. 38 | version = '{{ cookiecutter.version }}' 39 | # The full version, including alpha/beta/rc tags. 40 | release = '' 41 | 42 | # The language for content autogenerated by Sphinx. Refer to documentation 43 | # for a list of supported languages. 44 | # 45 | # This is also used if you do content translation via gettext catalogs. 46 | # Usually you set "language" from the command line for these cases. 47 | language = None 48 | 49 | # List of patterns, relative to source directory, that match files and 50 | # directories to ignore when looking for source files. 51 | # This patterns also effect to html_static_path and html_extra_path 52 | exclude_patterns = [] 53 | 54 | # The name of the Pygments (syntax highlighting) style to use. 55 | pygments_style = 'sphinx' 56 | 57 | # If true, `todo` and `todoList` produce output, else they produce nothing. 58 | todo_include_todos = True 59 | 60 | # -- Options for HTML output ---------------------------------------------- 61 | 62 | # The theme to use for HTML and HTML Help pages. See the documentation for 63 | # a list of builtin themes. 64 | # 65 | html_theme = 'sphinx_rtd_theme' 66 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 67 | 68 | # Add any paths that contain custom static files (such as style sheets) here, 69 | # relative to this directory. They are copied after the builtin static files, 70 | # so a file named "default.css" will overwrite the builtin "default.css". 71 | html_static_path = ['_static'] 72 | 73 | # -- Options for HTMLHelp output ------------------------------------------ 74 | 75 | # Output file base name for HTML help builder. 76 | htmlhelp_basename = '{{ cookiecutter.repo_name}}_doc' 77 | 78 | # -- Options for LaTeX output --------------------------------------------- 79 | 80 | latex_elements = { 81 | # The paper size ('letterpaper' or 'a4paper'). 82 | # 83 | # 'papersize': 'letterpaper', 84 | 85 | # The font size ('10pt', '11pt' or '12pt'). 86 | # 87 | # 'pointsize': '10pt', 88 | 89 | # Additional stuff for the LaTeX preamble. 90 | # 91 | # 'preamble': '', 92 | 93 | # Latex figure (float) alignment 94 | # 95 | # 'figure_align': 'htbp', 96 | } 97 | 98 | # Grouping the document tree into LaTeX files. List of tuples 99 | # (source start file, target name, title, 100 | # author, documentclass [howto, manual, or own class]). 101 | latex_documents = [(master_doc, '{{ cookiecutter.repo_name }}.tex', '{{ cookiecutter.repo_name }} Documentation', 102 | '{{ cookiecutter.author_name }}', 'manual'), ] 103 | 104 | # -- Options for manual page output --------------------------------------- 105 | 106 | # One entry per manual page. List of tuples 107 | # (source start file, name, description, authors, manual section). 108 | man_pages = [(master_doc, 'projectname', '{{ cookiecutter.skill_name }} Documentation', [author], 1)] 109 | 110 | # -- Options for Texinfo output ------------------------------------------- 111 | 112 | # Grouping the document tree into Texinfo files. List of tuples 113 | # (source start file, target name, title, author, 114 | # dir menu entry, description, category) 115 | texinfo_documents = [(master_doc, '{{ cookiecutter.repo_name }}', '{{ cookiecutter.skill_name }} Documentation', author, 116 | '{{ cookiecutter.skill_short_description }}', '{{ cookiecutter.skill_short_description }}.', 117 | 'Miscellaneous'), ] 118 | -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to {{ cookiecutter.skill_name }} documentation! 2 | ======================================================= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Table of Contents 7 | 8 | 9 | Indices and tables 10 | ================== 11 | 12 | * :ref:`genindex` 13 | * :ref:`modindex` 14 | * :ref:`search` 15 | -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.9 2 | aniso8601==1.2.0 3 | autodoc==0.3 4 | Babel==2.3.4 5 | beautifulsoup4==4.5.3 6 | cffi==1.9.1 7 | click==6.7 8 | cryptography==1.7.2 9 | decorator==4.0.11 10 | docutils==0.13.1 11 | Flask==0.12 12 | Flask-Ask==0.8.7 13 | idna==2.2 14 | imagesize==0.7.1 15 | itsdangerous==0.24 16 | Jinja2==2.9.5 17 | MarkupSafe==0.23 18 | pyasn1==0.2.1 19 | pycparser==2.17 20 | Pygments==2.2.0 21 | pyOpenSSL==16.2.0 22 | python-dateutil==2.6.0 23 | pytz==2016.10 24 | PyYAML==3.12 25 | requests==2.13.0 26 | six==1.10.0 27 | snowballstemmer==1.2.1 28 | Sphinx==1.5.2 29 | sphinx-autodoc-typehints==1.1.0 30 | sphinx-rtd-theme==0.1.9 31 | waitress==1.0.2 32 | WebOb==1.7.1 33 | WebTest==2.0.25 34 | Werkzeug==0.11.15 35 | wheel==0.24.0 36 | -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/speech_assets/custom_slot_types/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisvoncsefalvay/cookiecutter-flask-ask/d81681293b34335f6b31ae1c16e8efbd212c125c/{{ cookiecutter.repo_name }}/speech_assets/custom_slot_types/.gitkeep -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/speech_assets/intent_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "intents": [ 3 | { 4 | "intent": "AMAZON.StopIntent" 5 | }, 6 | { 7 | "intent": "AMAZON.CancelIntent" 8 | }, 9 | { 10 | "intent": "AMAZON.HelpIntent" 11 | }, 12 | { 13 | "intent": "AMAZON.NoIntent" 14 | }, 15 | { 16 | "intent": "AMAZON.YesIntent" 17 | }, 18 | { 19 | "intent": "AMAZON.PreviousIntent" 20 | }, 21 | { 22 | "intent": "AMAZON.StartOverIntent" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/speech_assets/sample_utterances.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrisvoncsefalvay/cookiecutter-flask-ask/d81681293b34335f6b31ae1c16e8efbd212c125c/{{ cookiecutter.repo_name }}/speech_assets/sample_utterances.txt -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/templates.yaml: -------------------------------------------------------------------------------- 1 | welcome: | 2 | {{ cookiecutter.skill_name }}. 3 | 4 | welcome_re: | 5 | {{ cookiecutter.skill_name }}. How may I help you? 6 | 7 | welcome_card: | 8 | {{ cookiecutter.skill_name }} is an Alexa skill written by {{ cookiecutter.author_name }}. {{ cookiecutter.skill_short_description }} 9 | 10 | stop_bye: | 11 | Goodbye! 12 | 13 | cancel_bye: | 14 | OK, goodbye! 15 | 16 | help_text: | 17 | {{ cookiecutter.skill_name }} is an Alexa skill that does things. This is where the developer would normally explain how to do them. 18 | -------------------------------------------------------------------------------- /{{ cookiecutter.repo_name }}/{{ cookiecutter.repo_name }}.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # {{ cookiecutter.skill_name }} 4 | # By {{ cookiecutter.author_name }} <{{ cookiecutter.author_email_address }}> 5 | # 6 | # {{ cookiecutter.skill_short_description }} 7 | 8 | import logging 9 | from datetime import datetime 10 | from flask import Flask, json, render_template 11 | from flask_ask import Ask, request, session, question, statement 12 | 13 | __author__ = '{{ cookiecutter.author_name }}' 14 | __email__ = '{{ cookiecutter.author_email_address }}' 15 | 16 | 17 | app = Flask(__name__) 18 | ask = Ask(app, '/') 19 | logging.getLogger("flask_ask").setLevel(logging.DEBUG) 20 | 21 | # Session starter 22 | # 23 | # This intent is fired automatically at the point of launch (= when the session starts). 24 | # Use it to register a state machine for things you want to keep track of, such as what the last intent was, so as to be 25 | # able to give contextual help. 26 | 27 | @ask.on_session_started 28 | def start_session(): 29 | """ 30 | Fired at the start of the session, this is a great place to initialise state variables and the like. 31 | """ 32 | logging.debug("Session started at {}".format(datetime.now().isoformat())) 33 | 34 | # Launch intent 35 | # 36 | # This intent is fired automatically at the point of launch. 37 | # Use it as a way to introduce your Skill and say hello to the user. If you envisage your Skill to work using the 38 | # one-shot paradigm (i.e. the invocation statement contains all the parameters that are required for returning the 39 | # result 40 | 41 | @ask.launch 42 | def handle_launch(): 43 | """ 44 | (QUESTION) Responds to the launch of the Skill with a welcome statement and a card. 45 | 46 | Templates: 47 | * Initial statement: 'welcome' 48 | * Reprompt statement: 'welcome_re' 49 | * Card title: '{{ cookiecutter.skill_name }} 50 | * Card body: 'welcome_card' 51 | """ 52 | 53 | welcome_text = render_template('welcome') 54 | welcome_re_text = render_template('welcome_re') 55 | welcome_card_text = render_template('welcome_card') 56 | 57 | return question(welcome_text).reprompt(welcome_re_text).standard_card(title="{{ cookiecutter.skill_name }}", 58 | text=welcome_card_text) 59 | 60 | 61 | # Built-in intents 62 | # 63 | # These intents are built-in intents. Conveniently, built-in intents do not need you to define utterances, so you can 64 | # use them straight out of the box. Depending on whether you wish to implement these in your application, you may keep 65 | # or delete them/comment them out. 66 | # 67 | # More about built-in intents: http://d.pr/KKyx 68 | 69 | @ask.intent('AMAZON.StopIntent') 70 | def handle_stop(): 71 | """ 72 | (STATEMENT) Handles the 'stop' built-in intention. 73 | """ 74 | farewell_text = render_template('stop_bye') 75 | return statement(farewell_text) 76 | 77 | 78 | @ask.intent('AMAZON.CancelIntent') 79 | def handle_cancel(): 80 | """ 81 | (STATEMENT) Handles the 'cancel' built-in intention. 82 | """ 83 | farewell_text = render_template('cancel_bye') 84 | return statement(farewell_text) 85 | 86 | 87 | @ask.intent('AMAZON.HelpIntent') 88 | def handle_help(): 89 | """ 90 | (QUESTION) Handles the 'help' built-in intention. 91 | 92 | You can provide context-specific help here by rendering templates conditional on the help referrer. 93 | """ 94 | 95 | help_text = render_template('help_text') 96 | return question(help_text) 97 | 98 | 99 | @ask.intent('AMAZON.NoIntent') 100 | def handle_no(): 101 | """ 102 | (?) Handles the 'no' built-in intention. 103 | """ 104 | pass 105 | 106 | @ask.intent('AMAZON.YesIntent') 107 | def handle_yes(): 108 | """ 109 | (?) Handles the 'yes' built-in intention. 110 | """ 111 | pass 112 | 113 | 114 | @ask.intent('AMAZON.PreviousIntent') 115 | def handle_back(): 116 | """ 117 | (?) Handles the 'go back!' built-in intention. 118 | """ 119 | pass 120 | 121 | @ask.intent('AMAZON.StartOverIntent') 122 | def start_over(): 123 | """ 124 | (QUESTION) Handles the 'start over!' built-in intention. 125 | """ 126 | pass 127 | 128 | 129 | # Ending session 130 | # 131 | # This intention ends the session. 132 | 133 | @ask.session_ended 134 | def session_ended(): 135 | """ 136 | Returns an empty for `session_ended`. 137 | 138 | .. warning:: 139 | 140 | The status of this is somewhat controversial. The `official documentation`_ states that you cannot return a response 141 | to ``SessionEndedRequest``. However, if it only returns a ``200/OK``, the quit utterance (which is a default test 142 | utterance!) will return an error and the skill will not validate. 143 | 144 | """ 145 | return statement("") 146 | 147 | 148 | if __name__ == '__main__': 149 | app.run(debug=True) 150 | --------------------------------------------------------------------------------