Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assistant api v2 #33

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
*.gem
*.gem
samples/*
73 changes: 45 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Currently upgrading

I am currently upgrading this gem to work with Google Assistant API v2. Until that is complete, the master branch will not be fully functional. See [v1.0.0](https://github.com/armilam/google-assistant-ruby/tree/v1.0.0) of this gem for the version that works with Google Assistant API v1.

# Google Assistant Ruby

Write Google Assistant actions in Ruby.

GoogleAssistant parses Google Assistant requests and provides the framework to respond appropriately. It works with the Google Assistant API v1.
GoogleAssistant parses Google Assistant requests and provides the framework to respond appropriately. It works with the Google Assistant API v2.

## Installation

Expand Down Expand Up @@ -33,8 +37,8 @@ class GoogleAssistantController < ApplicationController
assistant_response = GoogleAssistant.respond_to(params, response) do |assistant|
assistant.intent.main do
assistant.ask(
prompt: "<speak>Hi there! Say something, please.</speak>",
no_input_prompt: [
"<speak>Hi there! Say something, please.</speak>",
[
"<speak>If you said something, I didn't hear you.</speak>",
"<speak>Did you say something?</speak>"
]
Expand Down Expand Up @@ -88,8 +92,8 @@ Request user input by sending an `ask` response. Send a prompt and a set of foll
```rb
assistant.intent.main do
assistant.ask(
prompt: "<speak>Hi there! Say something, please.</speak>", # The voice prompt the user will hear.
no_input_prompt: [
"<speak>Hi there! Say something, please.</speak>", # The voice prompt the user will hear.
[
"<speak>If you said something, I didn't hear you.</speak>", # You can provide a number of "no input prompts". A random
"<speak>Did you say something?</speak>" # one will be spoken if the user takes too long to respond.
]
Expand Down Expand Up @@ -142,8 +146,8 @@ GoogleAssistant.respond_to(params, response) do |assistant|
assistant.conversation.state = "asking favorite color"

assistant.ask(
prompt: "What is your favorite color?",
no_input_prompt: ["What did you say your favorite color is?"]
"What is your favorite color?",
["What did you say your favorite color is?"]
)
end

Expand All @@ -154,8 +158,8 @@ GoogleAssistant.respond_to(params, response) do |assistant|
assistant.conversation.state = "asking lucky number"

assistant.ask(
prompt: "What is your lucky number?",
no_input_prompt: ["What did you say your lucky number is?"]
"What is your lucky number?",
["What did you say your lucky number is?"]
)
elsif assistant.conversation.state == "asking lucky number"
favorite_color = assistant.conversation.data["favorite_color"]
Expand Down Expand Up @@ -188,7 +192,7 @@ Request the user's name. This will result in a prompt to the user like:
```rb
assistant.intent.main do
# Request the user's name
assistant.ask_for_permission(context: "So that I can address you by name", permissions: GoogleAssistant::Permission::NAME)
assistant.ask_for_permission("So that I can address you by name", GoogleAssistant::Permission::NAME)
end

assistant.intent.permission do
Expand All @@ -211,7 +215,7 @@ Request the device's zip code and city. This will result in a prompt to the user
```rb
assistant.intent.main do
# Request the device's zip code and city
assistant.ask_for_permission(context: "To provide weather information for where you live", permissions: GoogleAssistant::Permission::DEVICE_COARSE_LOCATION)
assistant.ask_for_permission("To provide weather information for where you live", GoogleAssistant::Permission::DEVICE_COARSE_LOCATION)
end

assistant.intent.permission do
Expand All @@ -233,7 +237,7 @@ Request the device's precise location. This will result in a prompt to the user
```rb
assistant.intent.main do
# Request the device's precise location
assistant.ask_for_permission(context: "So that I can find out where you sleep at night", permissions: GoogleAssistant::Permission::DEVICE_PRECISE_LOCATION)
assistant.ask_for_permission("So that I can find out where you sleep at night", GoogleAssistant::Permission::DEVICE_PRECISE_LOCATION)
end

assistant.intent.permission do
Expand All @@ -260,24 +264,38 @@ You can use any hosting platform.
3. Deploy your app to the web. Heroku is a good choice. See [Heroku's documentation](https://devcenter.heroku.com/articles/getting-started-with-ruby#introduction) for more info on how to do this.
4. Add an `action.json` file at the root of your project.


```json
{
"versionLabel": "1.0.0",
"agentInfo": {
"languageCode": "en-US",
"projectId": "your-google-project-id",
"manifest": {
"displayName": "My Google Assistant",
"invocationName": "my action",
"shortDescription": "I made an assistant",
"longDescription": "This assistant makes use of the google_assistant Ruby gem. It's very neat.",
"sampleInvocation": [
"talk to my action"
],
"voiceName": "male_1"
},
"actions": [
{
"initialTrigger": {
"intent": "assistant.intent.action.MAIN"
"description": "The main intent",
"name": "MAIN",
"fulfillment": {
"conversationName": "my_action"
},
"httpExecution": {
"url": "https://yourapp.domain.com/path-to-your-assistant"
"intent": {
"name": "actions.intent.MAIN"
}
}
]
],
"conversations": {
"my_action": {
"name": "my_action",
"url": "https://yourapp.domain.com/path-to-your-assistant",
"fulfillmentApiVersion": 2
}
}
}
```

Expand All @@ -293,7 +311,7 @@ You can use any hosting platform.

## Authentication

You can require users to log in to your service before using your assistant. Read about it in [Google's documentation](https://developers.google.com/actions/develop/identity/oauth2-code-flow). The basic flow is this:
You can require users to log in to your service before using your assistant. Read about it in [Google's documentation](https://developers.google.com/actions/identity/account-linking#account_linking_at_invocation_time). The basic flow is this:

1. User tries to talk to your assistant
2. Google tells the user they need to sign in, which they can do via the Home app on their phone
Expand All @@ -302,15 +320,14 @@ You can require users to log in to your service before using your assistant. Rea
5. Google stores the user's Oauth access and refresh tokens
6. For each subsequent request the user makes to your assistant, Google sends the user's access token so you can identify the user

In order to set this up in your assistant, the basic instructions are as follows. Read Google's documentation for the full details.
When you have everything set up, you can find the user's access token for your app in `assistant.user.access_token`.

#### Account linking during the conversation

1. Implement Oauth in your application
2. Set up an Oauth client in the [Google Developer Console](https://console.developers.google.com)
3. In the application's `action.json` file, set up account linking according to [Google's Instructions](https://developers.google.com/actions/develop/identity/account-linking#enabling_account_linking)
4. Use `assistant.user.access_token` to identify the user
This gem doesn't yet support account linking during the conversation. This would be neat to have. See [#32](https://github.com/armilam/google-assistant-ruby/issues/32).

## More information

Check out Google's instructions at https://developers.google.com/actions/develop/sdk/getting-started for more detail on writing and testing a Google Assistant action.
Check out Google's instructions at https://developers.google.com/actions/sdk/ for more detail on writing and testing a Google Assistant action.

Check out https://github.com/armilam/google_assistant_example for a simple example of this gem in action.
8 changes: 4 additions & 4 deletions lib/google_assistant/argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class Argument

def self.from(opts)
case opts["name"]
when "permission_granted"
when "PERMISSION"
PermissionArgument.new(opts)
when "text"
when "TEXT"
TextArgument.new(opts)
else
Argument.new(opts)
Expand All @@ -17,8 +17,8 @@ def self.from(opts)

def initialize(opts)
@name = opts["name"]
@raw_text = opts["raw_text"]
@text_value = opts["text_value"]
@raw_text = opts["rawText"]
@text_value = opts["textValue"]
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/google_assistant/assistant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize(params, response)
def respond_to(&block)
yield(self)

response.headers["Google-Assistant-API-Version"] = "v1"
response.headers["Google-Assistant-API-Version"] = "v2"

intent.call
end
Expand Down
4 changes: 2 additions & 2 deletions lib/google_assistant/conversation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class Type
attr_reader :id, :type, :dialog_state

def initialize(opts)
@id = opts["conversation_id"]
@id = opts["conversationId"]
@type = opts["type"]
@dialog_state = DialogState.new(opts["conversation_token"])
@dialog_state = DialogState.new(opts["conversationToken"])
end

def state
Expand Down
4 changes: 2 additions & 2 deletions lib/google_assistant/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ def city
end

def zip_code
location["zip_code"]
location["zipCode"]
end

def formatted_address
location["formatted_address"]
location["formattedAddress"]
end

def latitude
Expand Down
6 changes: 3 additions & 3 deletions lib/google_assistant/intent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ module GoogleAssistant
class StandardIntents

# Assistant fires MAIN intent for queries like [talk to $action].
MAIN = "assistant.intent.action.MAIN"
MAIN = "actions.intent.MAIN"

# Assistant fires TEXT intent when action issues ask intent.
TEXT = "assistant.intent.action.TEXT"
TEXT = "actions.intent.TEXT"

# Assistant fires PERMISSION intent when action invokes askForPermission.
PERMISSION = "assistant.intent.action.PERMISSION"
PERMISSION = "actions.intent.PERMISSION"
end

class Intent
Expand Down
2 changes: 1 addition & 1 deletion lib/google_assistant/response/ask_for_permission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def to_json

expected_intent = build_expected_intent(StandardIntents::PERMISSION, permissions, context)
expected_inputs = build_expected_inputs(expected_intent: expected_intent)
response[:expected_inputs] = expected_inputs
response[:expectedInputs] = expected_inputs

response
end
Expand Down
23 changes: 11 additions & 12 deletions lib/google_assistant/response/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def initialize(conversation = nil)

def to_json(expect_user_response)
response = {}
response[:conversation_token] = conversation.dialog_state.to_json if conversation&.dialog_state
response[:expect_user_response] = expect_user_response
response[:conversationToken] = conversation.dialog_state.to_json if conversation&.dialog_state
response[:expectUserResponse] = expect_user_response

response
end
Expand All @@ -32,17 +32,17 @@ def build_input_prompt(prompt, no_input_prompts)
end

{
initial_prompts: initial_prompts,
no_input_prompts: no_input_prompts
initialPrompts: initial_prompts,
noInputPrompts: no_input_prompts
}
end

def build_expected_inputs(prompt: "placeholder", no_input_prompts: [], expected_intent:)
prompt = build_input_prompt(prompt, no_input_prompts)

expected_inputs = [{
input_prompt: prompt,
possible_intents: [expected_intent]
inputPrompt: prompt,
possibleIntents: [expected_intent]
}]
end

Expand All @@ -56,11 +56,10 @@ def build_expected_intent(intent, permissions = nil, context = nil)
raise GoogleAssistant::InvalidPermission if permissions.empty?
raise GoogleAssistant::InvalidPermission unless GoogleAssistant::Permission.valid?(permissions)

expected_intent[:input_value_spec] = {
permission_value_spec: {
opt_context: context,
permissions: permissions
}
expected_intent[:inputValueData] = {
"@type": "type.googleapis.com/google.actions.v2.PermissionValueSpec",
optContext: context,
permissions: permissions
}
end

Expand All @@ -70,7 +69,7 @@ def build_expected_intent(intent, permissions = nil, context = nil)
private

def prompt_type(text)
is_ssml?(text) ? :ssml : :text_to_speech
is_ssml?(text) ? :ssml : :textToSpeech
end

def is_ssml?(text)
Expand Down
2 changes: 1 addition & 1 deletion lib/google_assistant/response/input_prompt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def to_json

expected_intent = build_expected_intent(StandardIntents::TEXT)
expected_inputs = build_expected_inputs(prompt: prompt, no_input_prompts: no_input_prompts, expected_intent: expected_intent)
response[:expected_inputs] = expected_inputs
response[:expectedInputs] = expected_inputs

response
end
Expand Down
4 changes: 2 additions & 2 deletions lib/google_assistant/response/speech_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def to_json
speech_response = if is_ssml?(message)
{ ssml: message }
else
{ text_to_speech: message }
{ textToSpeech: message }
end
response[:final_response] = { speech_response: speech_response }
response[:finalResponse] = { speechResponse: speech_response }

response
end
Expand Down
11 changes: 6 additions & 5 deletions lib/google_assistant/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ class User
attr_reader :id, :profile, :access_token

def initialize(opts)
@id = opts["user_id"]
@id = opts["userId"]
@profile = opts["profile"] || {}
@access_token = opts["access_token"]
@access_token = opts["accessToken"]
@locale = opts["locale"]
end

def display_name
profile["display_name"]
profile["displayName"]
end

def given_name
profile["given_name"]
profile["givenName"]
end

def family_name
profile["family_name"]
profile["familyName"]
end
end
end
2 changes: 1 addition & 1 deletion lib/google_assistant/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module GoogleAssistant
VERSION = "1.0.0"
VERSION = "2.0.0"
end
Loading