フロントエンド・Web技術をベースに作れる、スマートスピーカースキル (Google Nest Hub 向け) の導入の話。 https://mild-web-sap.connpass.com/event/147756/
ΏΔ8FCษڧձ!ࡳຈ!UBDDLϑϩϯτΤϯυʹ༏͍͠ εϚʔτεϐʔΧʔεΩϧͷ ࡞Γํ݄
View Slide
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ୭• ໊લtacck / ݪ • ॴଐגࣜձࣾϊʔεσΟςʔϧ• ओ࠵• ΏΔWebษڧձ@ࡳຈ• εϚʔτεϐʔΧʔͰ༡΅͏ձ@ࡳຈ• ͖ͳϑΟΪϡΞεέʔτͷٕεϓϨουɾΠʔάϧ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ݄͠ͷ4B$44ʹࢀՃ͞Εͨํ͕͍ͨΒ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ͍͍ͩͨҰॹͷ͜ͱΛ͠·͢
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔ• Έͳ͞Μ͍࣋ͬͯ·͔͢?
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈσϞλεΫ֬ೝΞϓϦɹɾ5SFMMPͷΧʔυΛݺͼग़͠ɹɾλονͰྃ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈͬ͘͟ΓΈ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒ ʮ"MFYBʯͱݺͼ͔͚ΒΕͨ͜ͱΛೝࣝ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒʮࠓͷ࣌ؒʯԻΛૹ৴
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒԻ͔ΒςΩετʹม
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒʮࠓʯͷʮ࣌ؒʯˠݱࡏ࣌ࠁΛਘͶΒΕ͍ͯΔͱཧղ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒݱࡏ࣌ࠁͷճจΛ࡞
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒճจΛԻʹมͯ͠ εϐʔΧʔฦ٫
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒड৴ͨ͠ԻΛ ࠶ੜ ൃޕޙ࣌Ͱ͢
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεΩϧͷ࡞Γํ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔɹΤϯδϯ Ϋϥυ্௨৴ϚΠΫεϐʔΧʔԻೝࣝจষղੳԻೝࣝର࡞Ի߹"MFYBࠓͷ࣌ؒεΩϧ࡞͜͜ͷ࡞ΓࠐΈ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈجຊతͳߟ͑ํ• ϢʔβʔεϚʔτεϐʔΧʔʮݺͼ͔͚ʯʮ࣭ʯΛߦͳ͏• εϚʔτεϐʔΧʔͦΕʹԠͨ͡ʮ͑ʯΛฦ͢• ݺͼ͔͚࣌ͷݴ༿͔ΒʮҙਤʯΛͲΕ͚ͩर͑Δ͔͕ΧΪ• Intent (ରҙਤ)• ʮͲ͏͍͏ҙਤΛ࣋ͬͨݺͼ͔͚͔ʯͱ͍͏ɺ ରͷछྨΛද͢ͷɻ • Slot (ύϥϝʔλ)• ݺͼ͔͚ͷதͷʮʯʮྔʯͱ͍ͬͨɺ มԽ͢ΔॴΛද͢ͷɻ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈఱؾ༧ใͷ߹• ʮ10݄17ʯ͕࣌Λද͢ Slot• ʮ౦ژʯ͕ॴΛද͢ Slot• ʮఱؾΛڭ͑ͯʯ͕ձͷྲྀΕΛܾΊΔ IntentϢʔβʔ 10݄17ͷ౦ژͷఱؾΛڭ͑ͯɻ໌ͷ౦ژͷఱؾಶΓͷ༧ใͰ͢ɻεϚʔτ εϐʔΧʔ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈఱؾ༧ใͷ߹• ʮ10݄17ʯ͕࣌Λද͢ Slot• ʮ౦ژʯ͕ॴΛද͢ Slot• ʮ࠷ߴؾԹΛڭ͑ͯʯ͕ձͷྲྀΕΛܾΊΔ IntentϢʔβʔ 10݄17ͷ౦ژͷ࠷ߴؾԹΛڭ͑ͯɻ໌ͷ౦ژͷ༧࠷ߴؾԹ20℃Ͱ͢ɻεϚʔτ εϐʔΧʔ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈఱؾ༧ใͷ߹• ʮࠓʯ͕࣌Λද͢ Slot• ʮఱؾΛڭ͑ͯʯ͕ձͷྲྀΕΛܾΊΔ IntentϢʔβʔࠓͷఱؾΛڭ͑ͯɻ (ݱࡏҐஔࡳຈͰొࡁΈ)ࠓͷࡳຈͷఱؾΕͰ͢ɻεϚʔτ εϐʔΧʔ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈͲ͏͍͏ड͚͑ʹ͢Δ͔• ϢʔβʔͷΓ͍ͨ͜ͱʹର͠ɺձͷྲྀΕΛߟ͑Δɻ• ձͷྲྀΕͷத͔Β Intent (ҙਤ) Λݟ͚ͭΔɻ• Intent ͝ͱʹɺ۩ମతͳฦͷϓϩάϥϜΛॻ͍͍ͯ͘ɻࠓͷఱؾ?ࡳຈͷࠓͷఱؾɺ ͘Γ ͷͪ Ε Ͱ͢ɻ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈը໘͖εϚʔτεϐʔΧʔ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈը໘͖σόΠε ը໘දࣔͷΈɹɹɹɹɹɹɹAmazonEcho ShowEcho Show 5Echo SpotAPLɹɹɹɹɹɹɹGoogle Google Nest HubInteractive Canvas (Web)ɹɹɹɹɹɹɹLINE Clova Desk ඇެ։
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈϑϩϯτΤϯυΤϯδχΞͷ օ༷ʹ࿕ใ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ"DUJPOTPO(PPHMF*OUFSBDUJWF$BOWBT
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈͱλονͰૢ࡞Ͱ͖Δ 8FCΞϓϦέʔγϣϯ Ά͍ײ͡ͰεΩϧΛ࡞ΕΔˠීஈ࡞͍ͬͯΔͷʹʮʯΛՃ͢Δ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ֬ೝλεΫ֬ೝΞϓϦɹɾ5SFMMPͷΧʔυΛݺͼग़͠ɹɾλονͰྃ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈදࣔίϯςϯπ 7VFKTIUUQTHJUIVCDPNUBDDLTIPXUPEBZUBTLT
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈmounted: async function () {// Callback Ͱ ΧʔυҠಈͷॲཧΛ࣮window.interactiveCanvas.ready({onUpdate: async data => {let cardId = ''let cardNumber = -1if ('cardId' in data) {cardId = data.cardIdcardNumber = this.list.findIndex(element => {return element.id === cardId})} else if ('cardNumber' in data) {cardNumber = data.cardNumber - 1const card = this.list[cardNumber]cardId = card.id} else {// ߋ৽ෆཁͳͷͰऴྃreturn}await axios.put(`https://api.trello.com/1/cards/${cardId}?idList=${this.finishedListId}&key=${this.apiKey}&token=${this.apiToken}&pos=bottom`)this.list.splice(cardNumber, 1)},onTtsMark: markName => {}})
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈmounted: async function () {// Callback Ͱ ΧʔυҠಈͷॲཧΛ࣮window.interactiveCanvas.ready({onUpdate: async data => {let cardId = ''let cardNumber = -1if ('cardId' in data) {cardId = data.cardIdcardNumber = this.list.findIndex(element => {return element.id === cardId})} else if ('cardNumber' in data) {cardNumber = data.cardNumber - 1const card = this.list[cardNumber]cardId = card.id} else {// ߋ৽ෆཁͳͷͰऴྃreturn}await axios.put(`https://api.trello.com/1/cards/${cardId}?idList=${this.finishedListId}&key=${this.apiKey}&token=${this.apiToken}&pos=bottom`)this.list.splice(cardNumber, 1)},onTtsMark: markName => {}})εϚʔτεϐʔΧΒͷ Πϕϯτड͚औΓλοϓͷ߹ͷ߹λοϓ͞ΕͨΧʔυΛ ྃࡁΈϦετҠಈϦετ͔ΒΞΠςϜআ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈconst response = await axios.get(`https://api.trello.com/1/lists/${this.listId}/cards?fields=all&key=${this.apiKey}&token=${this.apiToken}`)this.list = response.data},methods: {finish: function (id) {window.interactiveCanvas.sendTextQuery(`finishedTask-${id}`)}}
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈconst response = await axios.get(`https://api.trello.com/1/lists/${this.listId}/cards?fields=all&key=${this.apiKey}&token=${this.apiToken}`)this.list = response.data},methods: {finish: function (id) {window.interactiveCanvas.sendTextQuery(`finishedTask-${id}`)}}TrelloͷಛఆͷList͔Β CardҰཡΛऔಘCard ͕λοϓ͞ΕͨΒIntent ૹ৴
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈόοΫΤϯυ "DUJPOTPO(PPHMFIUUQTHJUIVCDPNUBDDLTIPXUPEBZUBTLTMBNCEB
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ'use strict';const axios = require('axios');const { dialogflow, HtmlResponse } = require('actions-on-google');const app = dialogflow({ debug: false });const tasksUrl = 'https://show-today-tasks.netlify.com'const currentListId = process.env.CURRENT_LIST_IDconst finishedListId = process.env.FINISHED_LIST_IDconst apiKey = process.env.API_KEYconst apiToken = process.env.API_TOKEN// ىಈ࣌Intentapp.intent('welcome', (conv) => {conv.ask('ࠓͷλεΫҰཡͰ͢ɻ֬ೝ͠·͠ΐ͏ɻ');conv.ask(new HtmlResponse({url: `${tasksUrl}/?listId=${currentListId}&apiKey=${apiKey}&apiToken=${apiToken}`,suppress: true}));
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ'use strict';const axios = require('axios');const { dialogflow, HtmlResponse } = require('actions-on-google');const app = dialogflow({ debug: false });const tasksUrl = 'https://show-today-tasks.netlify.com'const currentListId = process.env.CURRENT_LIST_IDconst finishedListId = process.env.FINISHED_LIST_IDconst apiKey = process.env.API_KEYconst apiToken = process.env.API_TOKEN// ىಈ࣌Intentapp.intent('welcome', (conv) => {conv.ask('ࠓͷλεΫҰཡͰ͢ɻ֬ೝ͠·͠ΐ͏ɻ');conv.ask(new HtmlResponse({url: `${tasksUrl}/?listId=${currentListId}&apiKey=${apiKey}&apiToken=${apiToken}`,suppress: true}));දࣔίϯςϯπ͕ ը໘ʹग़ΔΑ͏ʹࢦఆ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈapp.intent('finishedTaskByVoice', async (conv, { cardNumber }) => {// Ͱྃʹ͢Δconv.ask('ΧʔυΛྃʹ͠·ͨ͠ɻ')conv.ask(new HtmlResponse({url: `${tasksUrl}/?listId=${currentListId}&finishedListId=${finishedListId}&apiKey=${apiKey}&apiToken=${apiToken}`,suppress: true,data: {cardNumber: cardNumber}}))})
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈapp.intent('finishedTaskByVoice', async (conv, { cardNumber }) => {// Ͱྃʹ͢Δconv.ask('ΧʔυΛྃʹ͠·ͨ͠ɻ')conv.ask(new HtmlResponse({url: `${tasksUrl}/?listId=${currentListId}&finishedListId=${finishedListId}&apiKey=${apiKey}&apiToken=${apiToken}`,suppress: true,data: {cardNumber: cardNumber}}))})Ͱࢦఆ͞ΕͨΧʔυͷදࣔॱΛऔಘίϯςϯπଆʹ ΧʔυͷදࣔॱΛૹ৴
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈapp.intent('finishedTask', async (conv, { cardId }) => {// λοϓͰྃʹ͢Δconv.ask('ΧʔυΛྃʹ͠·ͨ͠ɻ')conv.ask(new HtmlResponse({url: `${tasksUrl}/?listId=${currentListId}&finishedListId=${finishedListId}&apiKey=${apiKey}&apiToken=${apiToken}`,suppress: true,data: {cardId: cardId}}))})
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈapp.intent('finishedTask', async (conv, { cardId }) => {// λοϓͰྃʹ͢Δconv.ask('ΧʔυΛྃʹ͠·ͨ͠ɻ')conv.ask(new HtmlResponse({url: `${tasksUrl}/?listId=${currentListId}&finishedListId=${finishedListId}&apiKey=${apiKey}&apiToken=${apiToken}`,suppress: true,data: {cardId: cardId}}))})λοϓ͞ΕͨΧʔυͷ IDΛऔಘίϯςϯπଆʹ CardId Λૹ৴
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ·ͱΊ
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈ(PPHMF/FTU)VC͍͍Α• εΩϧ։ൃʹɺطଘͷWebΞϓϦ։ൃͷ͕ࣝɺ ͔ͳΓ׆͔ͤΔɻ• ը໘͕ಈ͘ͷָ͍͠ɻ• ࣮ɺInteractive Canvas ɺ Android ͷ Google Assistant Ͱ͑Δ! → AndroidϢʔβʔͳΒɺ͙͢ʹࢼͤΔͶ!
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈͦΕͧΕͷಛ• Echo• γϦʔζల։͕๛ɾຊؾग़͢ͱʮεϚʔτϗʔϜʯԽՄೳ• ը໘͖ͷ “Echo Show 5” ͕͓खࠒ (¥9,980)• Google Home• Android ؚΊͨڧΈ• ը໘͖ͷ “Google Nest Hub” Λ࣠ʹࠓޙల։ͷํ• LINE Clova• LINE LINE Bot ͱͷ࿈ܞ͕ڧ͍(ϝοηʔδ͏ͳΒ͜Ε)• ࠓޙͷల։ɾɾɾΧʔφϏ?
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈεϚʔτεϐʔΧʔ• Έͳ͞Μങ͍·͠ΐ͏!
!UBDDLΏΔ8FCษڧձ!ࡳຈΏΔ8FCࡳຈFOE