├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── agent.zip ├── firebase.json ├── functions ├── .eslintrc.json ├── index.js ├── locales │ ├── en.json │ └── fr.json ├── package.json ├── strings.js ├── types.js └── utils.js └── public ├── 404.html ├── audio ├── NumberGenieEarcon_ColdWind.wav ├── NumberGenieEarcon_SteamOnly.wav ├── NumberGenieEarcons_Steam.wav └── NumberGenieEarcons_YouWin.wav ├── images ├── COLD.gif ├── COOL.gif ├── HOT.gif ├── INTRO.gif ├── WARM.gif └── WIN.gif └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | *.DS_Store 4 | *.log 5 | .firebaserc 6 | .firebase 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 13 | * If you work for a company that wants to allow you to contribute your work, 14 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 15 | 16 | Follow either of the two links above to access the appropriate CLA and 17 | instructions for how to sign and return it. Once we receive it, we'll be able to 18 | accept your pull requests. 19 | 20 | ## Contributing A Patch 21 | 22 | 1. Submit an issue describing your proposed change to the repo in question. 23 | 1. The repo owner will respond to your issue promptly. 24 | 1. If your proposed change is accepted, and you haven't already done so, sign a 25 | Contributor License Agreement (see details above). 26 | 1. Fork the desired repo, develop and test your code changes. 27 | 1. Ensure that your code adheres to the existing style in the sample to which 28 | you are contributing. Refer to the 29 | [Google Cloud Platform Samples Style Guide](https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 30 | recommended coding standards for this organization. 31 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 32 | 1. Submit a pull request. 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Actions on Google: Number Genie Sample 2 | 3 | *:warning: Warning: Conversational Actions will be deprecated on June 13, 2023. For more information, 4 | see [Conversational Actions Sunset](https://goo.gle/ca-sunset).* 5 | 6 | This sample demonstrates Actions on Google features for use on Google Assistant, 7 | including how to steer conversation in a numeric guessing game through the use 8 | of localization (French and English), deep links, fallbacks, contexts, alongside rich responses -- using the [Node.js client library](https://github.com/actions-on-google/actions-on-google-nodejs) 9 | and deployed on [Cloud Functions for Firebase](https://firebase.google.com/docs/functions/). 10 | 11 | This Action uses the [i18n-node](https://github.com/mashpie/i18n-node) library to provide 12 | responses in both French and English, which are listed in the `locales` directory. 13 | In each function execution, the `strings.js` `setLocale()` function is called to 14 | set the i18n-node locale based on the user locale in the incoming request. Prompts are then 15 | selected by i18n-node from the available languages, defaulting to `en` if the 16 | user's language is unavailable. 17 | 18 | :warning: This code sample was built using Dialogflow. We now recommend using [Actions Builder or the Actions SDK](https://developers.google.com/assistant/conversational/overview) to develop, test, and deploy Conversational Actions. 19 | 20 | ## Setup Instructions 21 | ### Prerequisites 22 | 1. Node.js and NPM 23 | + We recommend installing using [NVM](https://github.com/creationix/nvm) 24 | 1. Install the [Firebase CLI](https://developers.google.com/assistant/actions/dialogflow/deploy-fulfillment) 25 | + We recommend using version 6.5.0, `npm install -g firebase-tools@6.5.0` 26 | + Run `firebase login` with your Google account 27 | 28 | ### Configuration 29 | #### Actions Console 30 | 1. From the [Actions on Google Console](https://console.actions.google.com/), New project (this will become your *Project ID*) > **Create project** > under **More options** > **Conversational** 31 | 1. From the top menu under **Develop** > **Actions** (left nav) > **Add your first action** > **BUILD** (this will bring you to the Dialogflow console) > Select language and time zone > **CREATE**. 32 | 1. In the Dialogflow console, go to **Settings** ⚙ > **Export and Import** > **Restore from zip** using the `agent.zip` in this sample's directory. 33 | 34 | #### Firebase Deployment 35 | 1. On your local machine, in the `functions` directory, run `npm install` 36 | 1. Run `firebase deploy --project {PROJECT_ID}` to deploy the function 37 | + To find your **Project ID**: In [Dialogflow console](https://console.dialogflow.com/) under **Settings** ⚙ > **General** tab > **Project ID**. 38 | 39 | #### Dialogflow Console 40 | 1. Return to the [Dialogflow Console](https://console.dialogflow.com) > select **Fulfillment** > **Enable** Webhook > Set **URL** to the **Function URL** that was returned after the deploy command > **SAVE**. 41 | ``` 42 | Function URL (dialogflowFirebaseFulfillment): https://${REGION}-${PROJECT_ID}.cloudfunctions.net/dialogflowFirebaseFulfillment 43 | ``` 44 | 1. From the left navigation menu, click **Integrations** > **Integration Settings** under Google Assistant > Enable **Auto-preview changes** > **Test** to open the Actions on Google simulator then say or type `Talk to my test app`. 45 | 46 | ### Running this Sample 47 | + You can test your Action on any Google Assistant-enabled device on which the Assistant is signed into the same account used to create this project. Just say or type, “OK Google, talk to my test app”. 48 | + You can also use the Actions on Google Console simulator to test most features and preview on-device behavior. 49 | 50 | ## References & Issues 51 | + Questions? Go to [StackOverflow](https://stackoverflow.com/questions/tagged/actions-on-google), [Assistant Developer Community on Reddit](https://www.reddit.com/r/GoogleAssistantDev/) or [Support](https://developers.google.com/assistant/support). 52 | + For bugs, please report an issue on Github. 53 | + Actions on Google [Documentation](https://developers.google.com/assistant) 54 | + Actions on Google [Codelabs](https://codelabs.developers.google.com/?cat=Assistant) 55 | + [Webhook Boilerplate Template](https://github.com/actions-on-google/dialogflow-webhook-boilerplate-nodejs) for Actions on Google 56 | 57 | ## Make Contributions 58 | Please read and follow the steps in the [CONTRIBUTING.md](CONTRIBUTING.md). 59 | 60 | ## License 61 | See [LICENSE](LICENSE). 62 | 63 | ## Terms 64 | Your use of this sample is subject to, and by using or downloading the sample files you agree to comply with, the [Google APIs Terms of Service](https://developers.google.com/terms/). 65 | -------------------------------------------------------------------------------- /agent.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions-on-google/dialogflow-number-genie-nodejs/91ec262d75060fd2755b719d1cd3c08d0a03cda1/agent.zip -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "public" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /functions/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 7, 4 | "sourceType": "script" 5 | }, 6 | "extends": ["google"], 7 | "env": { 8 | "node": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, Google, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const {dialogflow} = require('actions-on-google'); 18 | const functions = require('firebase-functions'); 19 | const strings = require('./strings'); 20 | const {Utils} = require('./utils'); 21 | 22 | /** 23 | * Dialogflow Parameters 24 | * {@link https://dialogflow.com/docs/actions-and-parameters#parameters} 25 | */ 26 | const Parameters = { 27 | NUMBER: 'number', 28 | GUESS: 'guess', 29 | }; 30 | /** 31 | * Dialogflow Contexts 32 | * {@link https://dialogflow.com/docs/contexts} 33 | */ 34 | const Contexts = { 35 | GAME: 'game', 36 | YES_NO: 'yes_no', 37 | DONE_YES_NO: 'done_yes_no', 38 | }; 39 | 40 | const STEAM_SOUND_GAP = 5; 41 | 42 | /** 43 | * @typedef {Object} HintsType 44 | * @prop {Hint} HIGHER 45 | * @prop {Hint} LOWER 46 | * @prop {Hint} NONE 47 | */ 48 | /** @type {HintsType} */ 49 | const Hints = { 50 | HIGHER: 'higher', 51 | LOWER: 'lower', 52 | NONE: 'none', 53 | }; 54 | 55 | const app = dialogflow({debug: true}); 56 | 57 | app.middleware((conv) => { 58 | strings.setLocale(conv.user.locale); 59 | conv.utils = new Utils(conv); 60 | }); 61 | 62 | app.intent('start_game', (conv) => { 63 | conv.data.answer = 64 | strings.getRandomNumber(strings.numbers.min, strings.numbers.max); 65 | conv.data.guessCount = 0; 66 | conv.data.fallbackCount = 0; 67 | conv.data.steamSoundCount = 0; 68 | conv.utils 69 | .ask(strings.prompts.welcome, strings.numbers.min, strings.numbers.max); 70 | }); 71 | 72 | app.intent('provide_guess', (conv, params) => { 73 | const answer = conv.data.answer; 74 | const guess = parseInt(conv.parameters[Parameters.GUESS]); 75 | const diff = Math.abs(guess - answer); 76 | conv.data.guessCount++; 77 | conv.data.fallbackCount = 0; 78 | // Check for duplicate guesses 79 | if (typeof conv.data.previousGuess === 'number' && 80 | guess === conv.data.previousGuess) { 81 | conv.data.duplicateCount++; 82 | if (conv.data.duplicateCount === 1) { 83 | if (!conv.data.hint || conv.data.hint === Hints.NONE) { 84 | return conv.utils.ask(strings.prompts.sameGuess3, guess); 85 | } 86 | return conv.utils.ask(strings.prompts.sameGuess, guess, conv.data.hint); 87 | } 88 | return conv.utils.close(strings.prompts.sameGuess2, guess); 89 | } 90 | conv.data.duplicateCount = 0; 91 | // Check if user isn't following hints 92 | if (conv.data.hint) { 93 | if (conv.data.hint === Hints.HIGHER && guess <= conv.data.previousGuess) { 94 | return conv.utils 95 | .ask(strings.prompts.wrongHigher, conv.data.previousGuess); 96 | } 97 | if (conv.data.hint === Hints.LOWER && guess >= conv.data.previousGuess) { 98 | return conv.utils 99 | .ask(strings.prompts.wrongLower, conv.data.previousGuess); 100 | } 101 | } 102 | // Handle boundaries with special prompts 103 | if (answer !== guess) { 104 | if (guess === strings.numbers.min) { 105 | conv.data.hint = Hints.HIGHER; 106 | conv.data.previousGuess = guess; 107 | return conv.utils.ask(strings.prompts.min, strings.numbers.min); 108 | } 109 | if (guess === strings.numbers.max) { 110 | conv.data.hint = Hints.LOWER; 111 | conv.data.previousGuess = guess; 112 | return conv.utils.ask(strings.prompts.max, strings.numbers.max); 113 | } 114 | } 115 | // Give different responses based on distance from number 116 | if (diff > 75) { 117 | // Guess is far away from number 118 | if (answer > guess) { 119 | conv.data.hint = Hints.HIGHER; 120 | conv.data.previousGuess = guess; 121 | return conv.utils.ask(strings.prompts.reallyColdHigh, guess); 122 | } 123 | conv.data.hint = Hints.LOWER; 124 | conv.data.previousGuess = guess; 125 | return conv.utils.ask(strings.prompts.reallyColdLow, guess); 126 | } 127 | if (diff === 4) { 128 | // Guess is getting closer 129 | if (answer > guess) { 130 | conv.data.hint = Hints.NONE; 131 | conv.data.previousGuess = guess; 132 | return conv.utils.ask(strings.prompts.highClose); 133 | } 134 | conv.data.hint = Hints.NONE; 135 | conv.data.previousGuess = guess; 136 | return conv.utils.ask(strings.prompts.lowClose); 137 | } 138 | if (diff === 3) { 139 | // Guess is even closer 140 | if (answer > guess) { 141 | conv.data.hint = Hints.HIGHER; 142 | conv.data.previousGuess = guess; 143 | if (conv.data.steamSoundCount-- <= 0) { 144 | conv.data.steamSoundCount = STEAM_SOUND_GAP; 145 | return conv.utils.ask(strings.prompts.highestSteam); 146 | } 147 | return conv.utils.ask(strings.prompts.highest); 148 | } 149 | conv.data.hint = Hints.LOWER; 150 | conv.data.previousGuess = guess; 151 | if (conv.data.steamSoundCount-- <= 0) { 152 | conv.data.steamSoundCount = STEAM_SOUND_GAP; 153 | return conv.utils.ask(strings.prompts.lowestSteam); 154 | } 155 | return conv.utils.ask(strings.prompts.lowest); 156 | } 157 | if (diff <= 10 && diff > 4) { 158 | // Guess is nearby number 159 | if (answer > guess) { 160 | conv.data.hint = Hints.HIGHER; 161 | conv.data.previousGuess = guess; 162 | return conv.utils.ask(strings.prompts.higher, guess); 163 | } 164 | conv.data.hint = Hints.LOWER; 165 | conv.data.previousGuess = guess; 166 | return conv.utils.ask(strings.prompts.lower, guess); 167 | } 168 | // Give hints on which direction to go 169 | if (answer > guess) { 170 | const previousHint = conv.data.hint; 171 | conv.data.hint = Hints.HIGHER; 172 | conv.data.previousGuess = guess; 173 | if (previousHint && previousHint === Hints.HIGHER && diff <= 2) { 174 | // Very close to number 175 | if (conv.data.steamSoundCount-- <= 0) { 176 | conv.data.steamSoundCount = STEAM_SOUND_GAP; 177 | return conv.utils.ask(strings.prompts.reallyHotHigh2Steam); 178 | } 179 | if (diff <= 1) { 180 | return conv.utils.ask(strings.prompts.reallyHotHigh); 181 | } 182 | return conv.utils.ask(strings.prompts.reallyHotHigh2); 183 | } 184 | return conv.utils.ask(strings.prompts.high, guess); 185 | } 186 | if (answer < guess) { 187 | const previousHint = conv.data.hint; 188 | conv.data.hint = Hints.LOWER; 189 | conv.data.previousGuess = guess; 190 | if (previousHint && previousHint === Hints.LOWER && diff <= 2) { 191 | // Very close to number 192 | if (conv.data.steamSoundCount-- <= 0) { 193 | conv.data.steamSoundCount = STEAM_SOUND_GAP; 194 | return conv.utils.ask(strings.prompts.reallyHotLow2Steam); 195 | } 196 | if (diff <= 1) { 197 | return conv.utils.ask(strings.prompts.reallyHotLow); 198 | } 199 | return conv.utils.ask(strings.prompts.reallyHotLow2); 200 | } 201 | return conv.utils.ask(strings.prompts.low, guess); 202 | } 203 | // Guess is same as number 204 | const guessCount = conv.data.guessCount; 205 | conv.data.hint = Hints.NONE; 206 | conv.data.previousGuess = -1; 207 | conv.contexts.set(Contexts.YES_NO, 5); 208 | conv.data.guessCount = 0; 209 | if (guessCount >= 10) { 210 | return conv.utils.ask(strings.prompts.winManyTries, answer); 211 | } 212 | conv.utils.ask(strings.prompts.win, answer); 213 | }); 214 | 215 | app.intent('quit_game', (conv) => { 216 | conv.utils.close(strings.prompts.reveal, conv.data.answer); 217 | }); 218 | 219 | app.intent('play-again-yes', (conv) => { 220 | conv.data.answer = 221 | strings.getRandomNumber(strings.numbers.min, strings.numbers.max); 222 | conv.data.guessCount = 0; 223 | conv.data.fallbackCount = 0; 224 | conv.data.steamSoundCount = 0; 225 | conv.utils.ask(strings.prompts.re, strings.numbers.min, strings.numbers.max); 226 | }); 227 | 228 | app.intent('play_again_no', (conv) => { 229 | conv.contexts.set(Contexts.GAME, 1); 230 | conv.utils.close(strings.prompts.quit); 231 | }); 232 | 233 | const defaultFallback = (conv) => { 234 | console.log(conv.data.fallbackCount); 235 | if (typeof conv.data.fallbackCount !== 'number') { 236 | conv.data.fallbackCount = 0; 237 | } 238 | conv.data.fallbackCount++; 239 | // Provide two prompts before ending game 240 | if (conv.data.fallbackCount === 1) { 241 | conv.contexts.set(Contexts.DONE_YES_NO, 5); 242 | return conv.utils.ask(strings.prompts.fallback); 243 | } 244 | conv.utils.close(strings.prompts.fallback2); 245 | }; 246 | 247 | app.intent('Default Fallback Intent', defaultFallback); 248 | 249 | app.intent('Unknown-deeplink', (conv) => { 250 | const answer = 251 | strings.getRandomNumber(strings.numbers.min, strings.numbers.max); 252 | conv.data.answer = answer; 253 | conv.data.guessCount = 0; 254 | conv.data.fallbackCount = 0; 255 | conv.data.steamSoundCount = 0; 256 | conv.contexts.set(Contexts.GAME, 1); 257 | const text = conv.query; 258 | if (!text) { 259 | return defaultFallback(conv); 260 | } 261 | // Handle "talk to number genie about frogs" by counting 262 | // number of letters in the word as the guessed number 263 | const numberOfLetters = text.length; 264 | if (numberOfLetters < answer) { 265 | return conv.utils.ask( 266 | strings.prompts.deeplink, 267 | text.toUpperCase(), 268 | numberOfLetters, 269 | numberOfLetters 270 | ); 271 | } 272 | if (numberOfLetters > answer) { 273 | return conv.utils.ask( 274 | strings.prompts.deeplink2, 275 | text.toUpperCase(), 276 | numberOfLetters, 277 | numberOfLetters 278 | ); 279 | } 280 | conv.data.hint = Hints.NONE; 281 | conv.data.previousGuess = -1; 282 | conv.contexts.set(Contexts.YES_NO, 5); 283 | conv.utils.ask( 284 | strings.prompts.deeplink3, 285 | text.toUpperCase(), 286 | numberOfLetters, 287 | answer 288 | ); 289 | }); 290 | 291 | app.intent('deep_link_number', (conv) => { 292 | conv.data.guessCount = 0; 293 | conv.data.fallbackCount = 0; 294 | conv.data.steamSoundCount = 0; 295 | conv.contexts.set(Contexts.GAME, 1); 296 | // Easter egg to set the answer for demos 297 | // Handle "talk to number genie about 55" 298 | conv.data.answer = parseInt(conv.parameters[Parameters.NUMBER]); 299 | // Check if answer is in bounds 300 | if (conv.data.answer >= strings.numbers.min && 301 | conv.data.answer <= strings.numbers.max) { 302 | return conv.utils 303 | .ask(strings.prompts.welcome, strings.numbers.min, strings.numbers.max); 304 | } 305 | // Give a different prompt if answer is out of bounds 306 | conv.data.answer = 307 | strings.getRandomNumber(strings.numbers.min, strings.numbers.max); 308 | conv.utils.ask( 309 | strings.prompts.outOfBoundsDeeplink, 310 | strings.numbers.min, 311 | strings.numbers.max 312 | ); 313 | }); 314 | 315 | app.intent('cancel', (conv) => { 316 | conv.close(`Ok, until next time then!`); 317 | }); 318 | 319 | app.intent('done_yes', (conv) => { 320 | conv.contexts.set(Contexts.GAME, 1); 321 | conv.utils.close(strings.prompts.quit); 322 | }); 323 | 324 | app.intent('done_no', (conv) => { 325 | conv.data.fallbackCount = 0; 326 | conv.utils.ask(strings.prompts.reAnother); 327 | }); 328 | 329 | app.intent('repeat', (conv) => { 330 | const lastResponse = conv.data.lastResponse; 331 | if (lastResponse) { 332 | // Currently does not use repeat prompt 333 | return conv.utils.sendCompiled(lastResponse); 334 | } 335 | conv.utils.ask(strings.prompts.another); 336 | }); 337 | 338 | app.intent('no_input', (conv) => { 339 | conv.ask(strings.general.noInput[+conv.arguments.get('REPROMPT_COUNT')]); 340 | }); 341 | 342 | app.fallback((conv) => { 343 | conv.ask(`I didn't hear a number. What's your guess?`); 344 | }); 345 | 346 | exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app); 347 | -------------------------------------------------------------------------------- /functions/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": { 3 | "cold": { 4 | "url": "COLD.gif", 5 | "altText": "cold genie", 6 | "cardText": [ 7 | "Freezing like an ice cave in Antarctica?", 8 | "I can't feel my face anymore", 9 | "Hurry, before I turn into an icicle" 10 | ] 11 | }, 12 | "cool": { 13 | "url": "COOL.gif", 14 | "altText": "cool genie", 15 | "cardText": [ 16 | "Colder than a water droplet at 0°C", 17 | "Did you feel that cold gust of wind?", 18 | "Let me grab a coat. This may take awhile." 19 | ] 20 | }, 21 | "hot": { 22 | "url": "HOT.gif", 23 | "altText": "hot genie", 24 | "cardText": [ 25 | "Hotter than the sun's corona", 26 | "It's getting hot. Buy me a slushie?", 27 | "I may melt before you solve this one" 28 | ] 29 | }, 30 | "intro": { 31 | "url": "INTRO.gif", 32 | "altText": "mystical crystal ball", 33 | "cardText": [ 34 | "Are you a mind reader?", 35 | "Can you read my mind?", 36 | "Few have cracked this one on the first try" 37 | ] 38 | }, 39 | "warm": { 40 | "url": "WARM.gif", 41 | "altText": "warm genie", 42 | "cardText": [ 43 | "Warmer than a California summer", 44 | "It's getting warm. Hand me my shades.", 45 | "You're hot on the trail, but not quite there" 46 | ] 47 | }, 48 | "win": { 49 | "url": "WIN.gif", 50 | "altText": "celebrating genie", 51 | "cardText": [ 52 | "Like an eagle soaring to new heights!", 53 | "Balloons are for winners!", 54 | "Ready for another go?" 55 | ] 56 | } 57 | }, 58 | "general": { 59 | "noInput": [ 60 | "I didn't hear a number", 61 | "If you're still there, what's your guess?", 62 | "We can stop here. Let's play again soon." 63 | ] 64 | }, 65 | "suggestions": { 66 | "confirm": [ 67 | "Yeah", 68 | "No" 69 | ], 70 | "done": [ 71 | "I'm done" 72 | ] 73 | }, 74 | "variants": { 75 | "reveal": [ 76 | "Ok, I was thinking of %s.", 77 | "Sure, I'll tell you the number anyway. It was %s." 78 | ], 79 | "revealBye": [ 80 | "See you later.", 81 | "Talk to you later." 82 | ], 83 | "quit": [ 84 | "Alright, talk to you later then.", 85 | "OK, till next time.", 86 | "See you later.", 87 | "OK, I'm already thinking of a number for next time." 88 | ], 89 | "another": [ 90 | "What's your next guess?", 91 | "Have another guess?", 92 | "Try another." 93 | ], 94 | "low": [ 95 | "It's lower than %s." 96 | ], 97 | "high": [ 98 | "It's higher than %s." 99 | ], 100 | "lowClose": [ 101 | "It's so close, but not quite!" 102 | ], 103 | "highClose": [ 104 | "It's so close, but not quite!" 105 | ], 106 | "lower": [ 107 | "You're getting warm. It's lower than %s.", 108 | "Take another guess that's lower than %s.", 109 | "It's so close, but it's lower than %s." 110 | ], 111 | "higher": [ 112 | "You're getting warm. It's higher than %s.", 113 | "Warmer. It's also higher than %s.", 114 | "It's so close, but it's higher than %s." 115 | ], 116 | "lowest": [ 117 | "You're piping hot! But it's still lower.", 118 | "You're hot as lava! Go lower.", 119 | "Almost there! A bit lower." 120 | ], 121 | "highest": [ 122 | "You're piping hot! But it's still higher.", 123 | "You're hot as lava! Go higher.", 124 | "Almost there! A bit higher." 125 | ], 126 | "correct": [ 127 | "Well done! It is indeed %s.", 128 | "Congratulations, that's it! I was thinking of %s.", 129 | "Well done! It is indeed %s.", 130 | "You got it! It's %s." 131 | ], 132 | "again": [ 133 | "Wanna play again?", 134 | "Want to try again?", 135 | "Hey, should we do that again?" 136 | ], 137 | "greeting": [ 138 | "Let's play!", 139 | "Welcome!", 140 | "Hi!" 141 | ], 142 | "invocation": [ 143 | "I'm thinking of a number from %s to %s." 144 | ], 145 | "invocationGuess": [ 146 | "What's your first guess?" 147 | ], 148 | "re": [ 149 | "Great!", 150 | "Awesome!", 151 | "Cool!", 152 | "Okay, let's play again.", 153 | "Okay, here we go again.", 154 | "Alright, one more time with feeling." 155 | ], 156 | "reinvocation": [ 157 | "I'm thinking of a new number from %s to %s." 158 | ], 159 | "reinvocationAnother": [ 160 | "What's your guess?" 161 | ], 162 | "wrongLower": [ 163 | "Clever, but no. It's still lower than %s.", 164 | "Nice try, but it's still lower than %s." 165 | ], 166 | "wrongHigher": [ 167 | "Clever, but no. It's still higher than %s.", 168 | "Nice try, but it's still higher than %s." 169 | ], 170 | "reallyColdLow": [ 171 | "You're ice cold. It's way lower than %s.", 172 | "You're freezing cold. It's a lot lower than %s." 173 | ], 174 | "reallyColdHigh": [ 175 | "You're ice cold. It’s way higher than %s.", 176 | "You're freezing cold. It's a lot higher than %s." 177 | ], 178 | "reallyHotLow": [ 179 | "Almost there.", 180 | "Very close." 181 | ], 182 | "reallyHotLow2": [ 183 | "Keep going.", 184 | "It's so close, you're almost there." 185 | ], 186 | "reallyHotHigh": [ 187 | "Almost there.", 188 | "It's so close." 189 | ], 190 | "reallyHotHigh2": [ 191 | "Keep going.", 192 | { 193 | "speech": "Very close, you're almost there.", 194 | "displayText": "Very close.", 195 | "cardText": "You're almost there." 196 | } 197 | ], 198 | "sameGuess": [ 199 | "It's still not %s. Guess %s." 200 | ], 201 | "sameGuess2": [ 202 | "Maybe it'll be %s the next time. Let’s play again soon." 203 | ], 204 | "sameGuess3": [ 205 | "It's still not %s. Guess again." 206 | ], 207 | "min": [ 208 | "I see what you did there." 209 | ], 210 | "max": [ 211 | "Oh, good strategy. Start at the top." 212 | ], 213 | "minFollow": [ 214 | "But no, it's higher than %s." 215 | ], 216 | "maxFollow": [ 217 | "But no, it’s lower than %s." 218 | ], 219 | "manyTries": [ 220 | { 221 | "speech": "Yes! It's %s. Nice job!", 222 | "displayText": "Yes! It's %s.", 223 | "cardText": "Nice job!" 224 | } 225 | ], 226 | "manyTriesAgain": [ 227 | "How about one more round?" 228 | ], 229 | "fallback": [ 230 | "Are you done playing Number Genie?" 231 | ], 232 | "fallback2": [ 233 | "Since I'm still having trouble, I'll stop here. Let’s play again soon." 234 | ], 235 | "deeplink": [ 236 | "%s has %s letters. The number I'm thinking of is higher. Have another guess?", 237 | "%s is a great guess. It has %s letters, but I'm thinking of a higher number. What's your next guess?" 238 | ], 239 | "deeplink2": [ 240 | "%s has %s letters. The number I'm thinking of is lower. Have another guess?", 241 | "%s is a great first guess. It has %s letters, but the number I'm thinking of is lower. Guess again!" 242 | ], 243 | "deeplink3": [ 244 | "Wow! You're a true Number Genie! %s has %s letters and the number I was thinking of was %s! Well done!", 245 | "Amazing! You're a real Number Genie! %s has %s letters and the number I was thinking of was %s. Great job!" 246 | ], 247 | "repeat": [ 248 | "Sure. %s.", 249 | "OK. %s." 250 | ], 251 | "outOfBoundsDeeplink": [ 252 | "Woah there! I can't use that number. I've picked another for you." 253 | ] 254 | } 255 | } -------------------------------------------------------------------------------- /functions/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": { 3 | "cold": { 4 | "url": "COLD.gif", 5 | "altText": "Génie froid", 6 | "cardText": [ 7 | "Je me gèle comme un glaçon en Antartique", 8 | "Je ne sens plus mon visage", 9 | "Dépêchez-vous avant que je ne me transforme en glaçon" 10 | ] 11 | }, 12 | "cool": { 13 | "url": "COOL.gif", 14 | "altText": "Génie froid", 15 | "cardText": [ 16 | "Plus froid qu’une goûte d’eau à 0°C", 17 | "Vous avez senti cette rafale de vent froid ?", 18 | "Je vais aller me chercher un manteau. Ca va prendre un moment." 19 | ] 20 | }, 21 | "hot": { 22 | "url": "HOT.gif", 23 | "altText": "Génie chaud", 24 | "cardText": [ 25 | "Plus chaud que la couronne solaire", 26 | "Ca se réchauffe. Achetez moi un granité ?", 27 | "J’ai le temps de fondre avant que vous ne le trouviez" 28 | ] 29 | }, 30 | "intro": { 31 | "url": "INTRO.gif", 32 | "altText": "Boule de crystal mystique", 33 | "cardText": [ 34 | "Êtes-vous télépathe ?", 35 | "Vous lisez dans mon esprit ?", 36 | "Rares sont ceux qui l’ont trouvé du premier coup !" 37 | ] 38 | }, 39 | "warm": { 40 | "url": "WARM.gif", 41 | "altText": "Génie chaud", 42 | "cardText": [ 43 | "Plus chaud qu’un été californien", 44 | "Ca se réchauffe. Passez moi mes lunettes de soleil", 45 | "Vous vous réchauffez, mais vous n’y êtes pas encore" 46 | ] 47 | }, 48 | "win": { 49 | "url": "WIN.gif", 50 | "altText": "Génie content", 51 | "cardText": [ 52 | "Comme un aigle s’envolant vers de nouvelles hauteurs !", 53 | "Des ballons pour le vainqueur !", 54 | "On remet ça ?" 55 | ] 56 | } 57 | }, 58 | "general": { 59 | "noInput": [ 60 | "Je n'ai pas entendu le nombre.", 61 | "Vous êtes toujours là, vous pensez à quel nombre ?", 62 | "Nous pouvons nous arrêter ici. Rejouons bientôt ensemble." 63 | ] 64 | }, 65 | "suggestions": { 66 | "confirm": [ 67 | "Oui", 68 | "Non" 69 | ], 70 | "done": [ 71 | "J’arrête" 72 | ] 73 | }, 74 | "variants": { 75 | "reveal": [ 76 | "D'accord. Je pensais au nombre %s.", 77 | "Pas de problème. Je vais tout de même vous dire le nombre. C'était %s." 78 | ], 79 | "revealBye": [ 80 | "A bientôt.", 81 | "Revenez vite !" 82 | ], 83 | "quit": [ 84 | "D'accord, on se revoit bientôt.", 85 | "Ca marche, à la prochaine fois.", 86 | "A bientôt.", 87 | "OK, je pense déjà à un autre nombre pour la prochaine fois." 88 | ], 89 | "another": [ 90 | "Essayez encore.", 91 | "Essayez un autre numéro.", 92 | "Quel est votre prochain nombre ?" 93 | ], 94 | "low": [ 95 | "C'est plus petit que %s.", 96 | "C’est moins élevé que %s." 97 | ], 98 | "high": [ 99 | "C'est plus grand que %s." 100 | ], 101 | "lowClose": [ 102 | "On se rapproche, mais c’est pas encore tout à fait ça !" 103 | ], 104 | "highClose": [ 105 | "On se rapproche, mais c’est pas encore tout à fait ça !" 106 | ], 107 | "lower": [ 108 | "On se rapproche. C'est plus petit que %s.", 109 | "On se rapproche. Essayer avec un nombre plus petit que %s.", 110 | "Vous êtes si près, mais c'est plus petit que %s." 111 | ], 112 | "higher": [ 113 | "Vous vous rapprochez. C'est plus grand que %s. Essayez encore.", 114 | "Vous chauffez. C'est plus élevé que %s. Un autre nombre ?", 115 | "Vous êtes si proche, mais c'est plus grand que %s." 116 | ], 117 | "lowest": [ 118 | "Vous brûlez ! Mais c'est toujours plus petit.", 119 | "Vous êtes brûlant. Allez encore plus petit.", 120 | "Vous y êtes presque. Encore un peu plus petit." 121 | ], 122 | "highest": [ 123 | "Vous brûlez. Mais c'est plus grand.", 124 | "Vous êtes brûlant. Essayez plus grand.", 125 | "Vous y êtes presque. Encore un peu plus grand." 126 | ], 127 | "correct": [ 128 | "Bravo! C'est bien %s.", 129 | "Félicitations, c'est ça. Je pensais au nombre %s.", 130 | "Bingo! Vous avez trouvé ! C'est %s." 131 | ], 132 | "again": [ 133 | "Vous voulez jouer encore ?", 134 | "Vous voulez retenter votre chance ?", 135 | "On recommence une autre partie ?" 136 | ], 137 | "greeting": [ 138 | "Jouons !", 139 | "Bienvenue !", 140 | "Bonjour !" 141 | ], 142 | "invocation": [ 143 | "Je pense à un nombre entre %s et %s." 144 | ], 145 | "invocationGuess": [ 146 | "Quel est votre premier essai ?" 147 | ], 148 | "re": [ 149 | "Génial!", 150 | "Super!!", 151 | "Cool!", 152 | "D'accord, jouons à nouveau.", 153 | "OK, c'est reparti.", 154 | "D'accord, on remet ça." 155 | ], 156 | "reinvocation": [ 157 | "Je pense à un nouveau nombre entre %s et %s." 158 | ], 159 | "reinvocationAnother": [ 160 | "A quel nombre je pense ?" 161 | ], 162 | "wrongLower": [ 163 | "Malin, mais non. C'est toujours plus petit que %s.", 164 | "Bien joué, mais c'est toujours plus petit que %s." 165 | ], 166 | "wrongHigher": [ 167 | "Malin, mais non. C'est toujours plus grand que %s.", 168 | "Bien joué, mais c'est toujours plus grand que %s." 169 | ], 170 | "reallyColdLow": [ 171 | "Froid comme la glace. C'est bien plus petit que %s.", 172 | "Vous gelez là. C'est bien beaucoup plus petit que %s." 173 | ], 174 | "reallyColdHigh": [ 175 | "Froid comme la glace. C'est bien plus grand que %s.", 176 | "Vous gelez là. C'est beaucoup plus grand que %s." 177 | ], 178 | "reallyHotLow": [ 179 | "Vous y êtes presque.", 180 | "Si proche." 181 | ], 182 | "reallyHotLow2": [ 183 | "Continuez.", 184 | "Si proche, vous y êtes presque." 185 | ], 186 | "reallyHotHigh": [ 187 | "Presque.", 188 | "Si proche." 189 | ], 190 | "reallyHotHigh2": [ 191 | "Continuez.", 192 | "Si proche, vous y êtes presque." 193 | ], 194 | "sameGuess": [ 195 | "C'est toujours pas %s. Essayez %s." 196 | ], 197 | "sameGuess2": [ 198 | "Peut-être que ce sera le nombre %s la prochaine fois. Rejouons bientôt ensemb" 199 | ], 200 | "sameGuess3": [ 201 | "C'est toujours pas %s. Essayez encore." 202 | ], 203 | "min": [ 204 | "Je vois ce que vous essayez de faire." 205 | ], 206 | "max": [ 207 | "Oh, bonne stratégie. Essayez par le plus élevé." 208 | ], 209 | "minFollow": [ 210 | "Mais non. C'est toujours plus grand que %s." 211 | ], 212 | "maxFollow": [ 213 | "Mais non, c'est plus petit que %s." 214 | ], 215 | "manyTries": [ 216 | { 217 | "speech": "Oui, c'est %s. Bien vu !", 218 | "displayText": "Oui, c'est %s.", 219 | "cardText": "Bien vu !" 220 | } 221 | ], 222 | "manyTriesAgain": [ 223 | "Et si on recommençait ?" 224 | ], 225 | "fallback": [ 226 | "Vous avez assez joué à Nombre Mystère ?" 227 | ], 228 | "fallback2": [ 229 | "Désolée je rencontre quelques soucis, arrêtons nous là. Rejouons plus tard ensemble." 230 | ], 231 | "deeplink": [ 232 | "%s a %s lettres. Le nombre auquel je pense est plus grand. Un nouvel essai ?", 233 | "%s est un bon essai. Il a %s lettres, mais je pense à un nombre plus grand. Quelle est votre prochain essai ?" 234 | ], 235 | "deeplink2": [ 236 | "%s a %s lettres. Le nombre auquel je pense est plus petit. Un nouvel essai ?", 237 | "%s est un bon essai. Il a %s lettres, mais je pense à un nombre plus petit. Quelle est votre prochain essai ?" 238 | ], 239 | "deeplink3": [ 240 | "Waouh ! Vous êtes un vrai génie des nombres ! %s a %s lettres et le nombre auquel je pensais était %s. Bravo !", 241 | "Génial ! Vous êtes un vrai génie des nombre ! %s a %s lettres et le nombre auquel je pensais au nombre %s. Bon travail !" 242 | ], 243 | "repeat": [ 244 | "Bien sûr. %s.", 245 | "D'accord, %s." 246 | ], 247 | "outOfBoundsDeeplink": [ 248 | "Ouh là là, je ne peux pas vous faire deviner ce nombre-là. Laissez-moi en choisir un autre." 249 | ] 250 | } 251 | } -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guess-a-number", 3 | "description": "Google Actions Guess a number sample for Cloud Functions for Firebase", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache-2.0", 7 | "author": "Google Inc.", 8 | "engines": { 9 | "node": "8" 10 | }, 11 | "scripts": { 12 | "lint": "eslint --fix \"**/*.js\"", 13 | "start": "firebase serve", 14 | "deploy": "firebase deploy", 15 | "test": "npm run lint" 16 | }, 17 | "dependencies": { 18 | "actions-on-google": "^2.6.0", 19 | "firebase-admin": "^7.2.0", 20 | "firebase-functions": "^2.2.1", 21 | "i18n": "^0.8.3", 22 | "sprintf-js": "^1.0.3" 23 | }, 24 | "devDependencies": { 25 | "eslint": "^4.19.1", 26 | "eslint-config-google": "^0.9.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /functions/strings.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Google, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* eslint quote-props: ["error", "always"] */ 16 | /* eslint quotes: ["error", "double"] */ 17 | 18 | "use strict"; 19 | 20 | // eslint-disable-next-line quotes 21 | const i18n = require("i18n"); 22 | const path = require("path"); 23 | 24 | i18n.configure({ 25 | "directory": path.join(__dirname, "/locales"), 26 | "objectNotation": true, 27 | "fallbacks": { 28 | "fr-FR": "fr", 29 | "fr-CA": "fr", 30 | }, 31 | }); 32 | 33 | /** @param {string} locale */ 34 | const setLocale = (locale) => { 35 | i18n.setLocale(locale); 36 | }; 37 | 38 | const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG); 39 | 40 | /** 41 | * (Optional) Change this to the url of your custom hosting site 42 | * By default, it uses the Firebase hosting authDomain as the root url 43 | */ 44 | const hosting = ""; 45 | 46 | const baseUrl = hosting || `https://${firebaseConfig.projectId}.firebaseapp.com`; 47 | 48 | /** 49 | * @param {string} image 50 | * @return {string} 51 | */ 52 | const getImage = (image) => `${baseUrl}/images/${image}`; 53 | 54 | /** 55 | * @param {string} sound 56 | * @return {string} 57 | */ 58 | const getSound = (sound) => `