- 投稿日:2022-03-23T23:14:58+09:00
【LINE BotをAPI連携させたいだけなのに #1】エラー祭りへようこそ
LINE Botを思い通りに動かしたーい!! LINE Botでプッシュメッセージとリプライメッセージ(おうむ返しと固定テキスト)返すところまではできた。(やっとのことで。息も絶え絶えで。) そして息切れしながら向かった先は、 脳みそに新たなひらめきをくれるAPI連携Botが作りたい 仕事ではプランナーとして企画制作の1000本ノックを日々行い、大学院ではアート作品を作る私。煮詰まった時に脳みそをリフレッシュさせたい!自分の思考の外から新たな風がほしい!! そんな時のお助けアイテムとして、「デザインに関してのインスピレーションを与える名言」 というステキAPIからお言葉を頂戴しようと思った。 まずは単純なオウム返しBot 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const PORT = process.env.PORT || 3000; const config = { channelSecret: 'ひみつ', channelAccessToken: 'ひみつ' }; const client = new line.Client(config); async function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } return client.replyMessage(event.replyToken, { type: 'text', // テキストメッセージ text: event.message.text // }); } const app = express(); app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)')); app.post('/webhook', line.middleware(config), (req, res) => { if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); console.log('検証イベントを受信しました!'); return; } else { console.log('受信しました:', req.body.events); } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); ここまではできた。 この後特定の単語でのリプライもできた。 今度はAPIを連携させる "axios" を利用できるようにするため、コードを書き換える。 npm i axios をターミナルに入力。 そして、下記のコードを元に、APIを引っ張ってくるコードを入れる。 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const config = { channelSecret: 'ひみつ', channelAccessToken: 'ひみつ' }; const client = new line.Client(config); async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } const app = express(); app.post('/webhook', line.middleware(config), (req, res) => { if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); console.log('検証イベントを受信しました!'); return; } else { console.log('受信しました:', req.body.events); } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); ここにひらめき名言をもらうQuotes on DesignとAPI連携できるようコードを完成させたい 入れるコードはこちら。 // axiosライブラリを呼び出す const axios = require('axios'); // 実際にデータを取得する getRequest 関数 async function getRequest() { let response; try { response = await axios.get('https://quotesondesign.com/wp-json/wp/v2/posts/?orderby=rand'); console.log(response.data); } catch (error) { console.error(error); } } // getRequest を呼び出してデータを読み込む getRequest(); しかし、鬼のエラー祭りが私の足を止める。 一歩進むたびに現れる奴ら(=エラー) API連携のため、まず新しいファイルを作った私の前に現れたのは、Webhook設定できない問題。 これは私がMessaging API設定で/Webhookを入力していなかった、というドえらいシンプルなミスをしていたせいでした。 そして次に現れたのが、 ターミナルに文字打てない問題。 こいつが長居するのよ・・・ しかし刻々と時間は過ぎ、メンタルがしおれていくのを感じた私は思った。 「よし、ファイルごと消そう」 しかし私はこの時点で、Control+Cやってエラー出てるファイル消してゼロからやり直す、という作業を、かれこれ20回以上はやっている。 エラーたちがいよいよ盛大に祭りを始める PROBLEMSだけでなく、ターミナルにも直にエラーが表記されているとここら辺で(やっと)気が付いた私。 なるほど!なら検索して直せばいいだけじゃない と思ったら大間違い!初心者は検索そのものがトンチンカンなのでなかなか求める答えドンピシャのページにたどり着かない。 エラーの意味はわかった。 けれど、答えがわからない。 私が出会ったエラーたち(一部) ReferenceError: sampleFunction is not defined at handleEvent これは他の人の記事を読んで、 const sampleFunction = async (event) => { を、コードのどこかに入れる必要がある、というところまではたどり着けた。 でも、ど、どこに・・・・??? コードを最初から一つ一つじー---っと読み込む。 これは何の指示をしているのか、何の設定をするために書いているのか。 コードをじっくり読み返した私が、const sampleFunction = async (event) => {を入れるのはここに違いない!と思ったのは こちらご査収ください。 // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます const sampleFunction = async (event) => { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } そしてターミナルに出てきたのは、 SyntaxError: Identifier 'axios' has already been declared 初めましてのエラー。 意味はわかるよ、わかるの。 構文が違うよ、ってことだよね・・・うん・・・わかった。 ここまでの紆余曲折。 あまりに悔しいので、正しい構文を見つけてやります。 次回、 「#2 ついにLINE BOtとAPIが連携する」 乞うご期待。 おわり!
- 投稿日:2022-03-23T23:11:09+09:00
外部apiの非同期処理を、await/asyncを使って同期的に使えるようにする
この記事で書くこと 外部apiの非同期処理を、どうやったら同期的に扱えるようにできるか模索した結果の記録 この記事で書かないこと await/asyncの説明 外部apiを使用するイメージ .js const externalApi = new ExternalApi(); const response = externalApi.execute(); // 非同期処理になる if (response.status == 'Success') { // 何かresponseを使った処理をする } else { // エラー処理ここで } // さらに呼び出すこともあるかもしれない if (isSpecialCondition) { const response2 = externalApi.execute(); // 非同期処理になる } console.log(response, response2); // どうなる? このような処理を書くときにどうやって書いたらいいか .js async function callExternalApi() { try { const externalApi = new ExternalApi(); const response = await externalApi.execute(); if (response.status == 'Success') { // 何かresponseを使った処理をする } if (isSpecialCondition) { const response2 = await externalApi.execute(); } } catch (err) { // error処理 } finally { console.log(response, response2); } } のように書きたい。 そもそもExternalApi側が用意しているinterfaceを理解することから .js // externalApi.execute(数字の配列の指定, 実行が終わったら結果を返して欲しい関数名の指定); externalApi.execute([1,2], callbackMethodName); externalApiはexecuteを呼ばれると、第一引数の配列を処理し、その結果をAPIを使用するこちら側の指定した関数名の引数に渡すというもの。 試したこと .js // await callAsyncTest1(numArray) される想定 // NG: 何の解決にもなっていない。 function callAsyncTest1() { return new Promise(resolve => { const externalApi = new ExternalApi(); const response = externalApi.execute(numArray, double); if (response) { resolve(); } }); } function double(num) { num * 2 }; // OK: この方法で解決した。 function callAsyncTest2(numArray) { return new Promise(resolve, reject => { const double = (res) => { if (res.status === 'success') { resolve({doubleResult: res * 2, status: 'success'}); // 簡易的な例です。 } else { reject('errorです'); } } const externalApi = new ExternalApi(); externalApi.execute(numArray, double); }); } callback関数をPromiseの中に書くというのが浮かばずに解決するのに時間がかかりました。。
- 投稿日:2022-03-23T22:30:07+09:00
気になった飲食店を探すシンプルなLINE Botを作ってみた
飲食店をさくっと調べて、後から見返したい 気になるお店が見つかった時、どうしますか? いつもと違う道を散歩中にお好みの飲食店を発見したり、グルメな友達が行きつけのお店を紹介してくれたのを聞いて、気になるお店が見つかった時、皆さんどうされていますか? メモアプリは二度手間だし、ググると後から見返すのに不便 私はiPhoneのメモアプリに店名を書き留めておくか、店名をググって、グルメ情報サイトを見るかどっちかだと思います。 特に休日の朝は、カフェで大好きなカプチーノを飲みながらのんびりするのが好きなので、朝早くから営業している居心地の良さそうなカフェを見つけた時は宝物を探し当てたかのような、ほっこりした気持ちになります。更に、カプチーノがメニューにあれば、そりゃーもう言うことないですよ!そのまま入店できない時は、店名を絶対忘れないようにメモっておきます。 メモアプリに入力した場合は、あとから店を探すのは意外にめんどくさくて二度手間です。メモアプリからえぇーと、店名をコピーして、Google開いて貼り付けて、はぃ、検索ってな感じで時間がかかります (お前どこまで遅いねん!)。 一方で、直接検索サイトで探してみると、複数の検索結果に多少目移りしてしまうものの、それなりにさくっとお店の情報にいきつきます。但し、ググって見つけたのは良いけど、後から見返すことができない。整理整頓が苦手な私は、お気に入りに登録したつもりが、分からなくなってしまった、、あーまた検索みたいなことが度々発生します。 ホットペッパーAPI X LINE Botが救世主に さくっとお店を探せて、その情報に戻れるのがベスト 大好きなカフェを見つけた時も、お店の検索と情報の記録にフラストレーションを感じていた私は、①さくっと探せる ②後から容易に情報に戻れる これらの2つのニーズを満たせたらいいなと思い、LINE Botの制作を行いました。 LINE Botの特徴として、友達登録するだけで簡単に使ってもらえて、普段から使い慣れているLINEアプリを開くだけで、多くの人にすぐに使っていただける早さと手軽さは大きなメリットです。 まずはAPI探し 課題解決したいことが決まったら、次は利用可能なAPI探し! APIとは、アプリケーションの開発を容易にするために、あらかじめ備えられたソフトウエア資源。共通して用いられる機能を一定の規約に基づいて利用します。会社によって、無料版があったり、有料版だけの場合もあります。 プログラミングを始めた時にはそもそもAPIという用語も知らなかったけど、こんなに便利なものがあるんだなー!困っていることを解決するエンジニア特有のシェア文化に改めて感心しました。 無料版で使えそうなAPIはホットペッパーだけ 馴染みがある飲食店検索サイトは、4つ。ホットペッパー以外にも、ぐるなび、食べログ、Rettyを検討しましたが、2022年3月現在無料版が使えるのはホットペッパーだけでした。ということで、ホットペッパーAPIを使わせていただくことにしました! 公式ホットペーパーAPI 【参考】飲食店検索アプリのAPI いざLINE Botを制作しよう LINE公式アカウントの作成 LINE Botを作成するには、LINE Developersにアクセスし公式アカウントの作成します。チャネルシークレットとチャネルアクセストークンが取得できたら準備完了です。 LINE公式アカウントの作成手順 ngrokの利用準備 次にngrokを使うためのアカウント登録が必要になります。こちらも事前準備が必要になります。 ngrokの利用準備手順 実行環境の準備 VScodeのインストール node.jsとnpmのインストール VScode内で、jsファイルを作成 ターミナルでフォルダをnpm管理できるように初期化 コード 取得したホットペッパーAPIを入れて実行します。 const sampleFunction = async (event) => { // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。 const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=ここにAPIキーを入れる&format=json&keyword='+encodeURI(event.message.text)); console.log(res.data.results.shop[0].urls.pc); return client.replyMessage(event.replyToken, { type: 'text', text: res.data.results.shop[0].urls.pc }); } 続いてLINEサーバーからのWebhookデータを処理する部分です。 // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } 結果こんな感じになります 余計な部分も多いですが、、イメージをご理解いただくためのデモ動画をツイッターにあげたので、良かったらご覧ください。 コード全文 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用 const config = { channelSecret: 'チャンネルシークレットをコピペ', channelAccessToken: 'アクセストークンをコピペ' }; const sampleFunction = async (event) => { // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。 const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=APIキーをコピペ&format=json&keyword='+encodeURI(event.message.text)); console.log(res.data.results.shop[0].urls.pc); return client.replyMessage(event.replyToken, { type: 'text', text: res.data.results.shop[0].urls.pc }); } const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); 実際使ってみての改善ポイント 検索に引っかからない時、エラーが出てシステムが止まってしまう。でもどうしてよいか分からない。。(涙) ネットで調べてみると、検索にかからない時に、エラー情報を取得してシステムが止まらないようにするには「//try{ } catch (error)」を使用するとあったので試してみたものの、これでもエラーは回避できずに今回は断念。前述のコードから変えたのはここだけなので、//try{ } catch (error)の挟み方が間違っているのだろうか?はたまた、別の対処方法が抜け漏れているのか?Qiita Q&Aでも質問させていただきました。 const sampleFunction = async (event) => { // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。 try { const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=ここにAPIキーをコピペ&format=json&keyword='+encodeURI(event.message.text)); console.log(res.data.results.shop[0].urls.pc); //try{ } catch (error)で検索にかからない時に、エラー情報を取得してシステムが止まらないようにする }catch (error) { console.error(error)}; return client.replyMessage(event.replyToken, { type: 'text', text: res.data.results.shop[0].urls.pc }); } 名前を直接入力じゃなくて、場所から飲食店を探せないか フィードバックをお願いしたら、次は場所から検索できるのも作ってもらえると便利だねという意見を妻にもらいました。店名で探してライン上で見るのもそれなりに使えると思うけど、確かに場所で検索出来たら更にいいよね~。知らない場所を訪れた時も、場所を送るか、GPSから近くの飲食店を5つ位、距離かランキングのパラメータを使って取り出せたら面白そう。 何を隠そう、実はこれ実現したかったんですが、ホットペッパーの個別のパラメータを組み合わせていくのは、私の力不足でどうにもなりませんでした。プログラミング経験者からみればシンプルとお感じになるんでしょうが、今回のBot制作も、周りにサポートしていただいて、その後エラーとも格闘しながら苦しんだ果てに、どうにかここまでたどり着いた状況です、とほほ。。私にとってプログラミングは、暗闇の中のトンネルをとぼとぼ進んでいく感じ、まだまだそんな感覚が続きそうです、、光は見えてくるのだろうか? 至らぬ点が多い記事ですが、最後までご覧いただき、ありがとうございました!!
- 投稿日:2022-03-23T22:21:06+09:00
LINE APIでオーキードーキー博士とポケモンGETしようぜ!
企画意図 初心者のプログラミングスキルで、最低限の面白さを表現するために、人気IPである〇〇モン(デジモンじゃない方)のデータを呼び出せるAPIの力を全力で借りて、オリジナルLINEBotの実装を目指します。 今回使用する、APIに関しては、こちらのシンプルにAPIが体験できるリストをまとめたページから選びました。初心者の方は参考にしてみてください。(俺には全然シンプルじゃなかったけどね!) 環境 Node.js axios Express ngrok <使用API> LINE Messaging API SDK for nodejs PokeAPI 作成するオーキードーキー博士BOTの説明 何か親しみを感じるおじさんの顔が見えますね。紹介します、オーキードーキー博士です。(N社に怒られませんように) 今回実装する「オーキードーキー博士Bot」は、「ポケモン」という言葉を聞くと、初代ポケットモンスターに登場した151のポケモンの中から、あなたにぴったりなポケモンを選び、本日のラッキーモンスターとして提案してくれます。 それ以外の会話には、お前の話はつまらんと言わんばかりに話を遮り、「ポケモン」の話を促してきます。なかなか、頑固な性格のようです。自分の話だけしたがる方、時々いますよね。 (実装力がないばかりに、頑固な性格にしてしまってごめんな、博士…。) 実際のBotの挙動 当初の計画通りには行きませんでしたが、ひとまず動くことをゴールにしました!オーキードーキー博士が、今日のラッキーポケモンをぶっきらぼうに教えてれるBOTです。#API #JS #LINE #protoout #ポケモン pic.twitter.com/Yka4omPDHF— yoshio333 (@yoshio333333) March 23, 2022 オーキードーキー博士のLINEBotと、ともだちになりたい方はこちら ↓ ※博士は気難しいので、時間帯によって既読スルーをしたり、明後日な返答をすることがあります。 ソースコード まずは、全体のソースコードです。 'use strict'; // ######################################## // 初期設定など // ######################################## // パッケージを使用します const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: '**************', channelAccessToken: '******************}; // ########## ▼▼▼ サンプル関数 ▼▼▼ ########## const sampleFunction = async (event) => { // 1〜151の数をランダムに吐き出す let a = Math.floor(Math.random() * 151) let pushData = {}; let replyText = ''; if (event.message.text === 'ポケモン') { // axiosでpokeAPIを叩きます const res = await axios.get('https://pokeapi.co/api/v2/pokemon-form/' + a); console.log(res.data); // 画像を送るメッセージを作る pushData = {type: 'image', originalContentUrl: res.data.sprites.front_default, previewImageUrl: res.data.sprites.front_default}; return client.replyMessage(event.replyToken,pushData) } else { replyText = 'その話題はつまらん。わしはオーキードーキー博士じゃ。正確に「ポケモン」と入力すると、今日のラッキーモンスターを教えてやるぞ!'; return client.replyMessage(event.replyToken, { type: 'text', text: replyText}) } ; }; // ########## ▲▲▲ サンプル関数 ▲▲▲ ########## // ######################################## // LINEサーバーからのWebhookデータを処理する部分 // ######################################## // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } // ######################################## // Expressによるサーバー部分 // ######################################## // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); 各部の解説 ①(乱数の設定) では、細かい要素を説明していきます。 // 1〜151の数をランダムに吐き出す let a = Math.floor(Math.random() * 151) PokeAPIは、IDでポケモンを呼び出せます。そのため、初代のポケモンが割り振られている1〜151の数字を吐き出すように「a」という関数をあらかじめ設定しておきます。(ゲームボーイの初代しかやったことがない。) 各部の解説 ②(関数をAPI内に呼び出す) const res = await axios.get('https://pokeapi.co/api/v2/pokemon-form/' + a); console.log(res.data); そしてaxiosでPokeAPIを呼び出すURLに、先程の乱数を生み出す関数「a」を入れこみます。こうすることで、毎回ランダムに数字が入るようになり、色々なポケモンのデータが呼び出されるようになります。 各部の解説 ③(ポケモンの画像を呼び出す) 先程の、コードでランダムに呼び出されたポケモン一体のいろいろなデータが呼び込まれているので、その中から、さらにポケモンの画だけを指定し(front_default)image形式で吐き出すようにしておきます。 pushData = {type: 'image', originalContentUrl: res.data.sprites.front_default, previewImageUrl: res.data.sprites.front_default}; 各部の解説 ④(LINEプラットフォームに送る) 先程の関数pushDataの中に、ポケモンの画像が入っているので、下記のコードでLINEプラットフォームに戻します。後は、LINEがいい感じに「ポケモン」と送ってきた、ユーザーのところに、ポケモンの画像を送り届けてくれます。 return client.replyMessage(event.replyToken,pushData) つまづいたポイント その① let pushData = {}; letで関数を指定しているときに、その関数はletのある{}の中でしか効かなくなるようです。関数を指定する場所は気をつけましょう。 その② 必要のない箇所を””で囲んでしまっていたので、ただの文字として認識されてしまっていました。 <不正解> originalContentUrl: "res.data.sprites.front_default" <正解> originalContentUrl: res.data.sprites.front_default, 正しいコードを書いていなかったので、結果何度も深夜にポケモンと叫び続けることになりました。みなさま、コードは正確に! 本当はやりたかったこと 「もっと優しい博士にしたかった。」 「オーキードーキー博士はポケモンの画像を返してくれるが、それにあわせてポケモンの名前や、少しフレンドリーなセリフも戻したかった。」 return client.replyMessage(event.replyToken,pushData) return client.replyMessage(event.replyToken, { type: 'text', text: '今日の君のラッキーモンスターは**じゃ。いい日になりそうじゃのう。'}) こんな感じのコードで(これは、動きません)。 ただ、JavaScriptは、returnで一つの値しか返せないらしく、それを解決する方法もあるらしいのですが、時間切れとなり、現状の石頭タイプのオーキードーキー博士に仕上がりました。 やはり今のままだと、やり取りにそっけない感じがあり、まだまだ物足りなさがあります。妻にも、実際にオーキードーキー博士と戯れてもらいましたが、フーンと言う感じでした。「あんなに夜作業してるのにこの程度ですか」と思われていないだろか…。やりたいこと、企画に手が追いつかない悔しさよ…。 そして博士!!プログラミングの勉強をちゃんとして、いつか、やさしくてひょうきんな博士に戻してやるからな!! 次回予告 次回 #protoout 「やさしくなったオーキードーキー博士!?」 みんなも、俺と一緒にポケモンGETリクエストだぜ! それでは、次の課題で会いましょう!
- 投稿日:2022-03-23T22:21:06+09:00
LINE APIでオーキードーキー博士とポケモンGETリクエストしようぜ!
企画意図 初心者のプログラミングスキルで、最低限の面白さを表現するために、人気IPである〇〇モン(デジモンじゃない方)のデータを呼び出せるAPIの力を全力で借りて、オリジナルLINEBotの実装を目指します。 今回使用する、APIに関しては、こちらのシンプルにAPIが体験できるリストをまとめたページから選びました。初心者の方は参考にしてみてください。(僕には全然シンプルじゃなかったけどね!) 環境 Node.js axios Express ngrok <使用API> LINE Messaging API SDK for nodejs PokeAPI 作成するオーキードーキー博士BOTの説明 何か親しみを感じるおじさんの顔が見えますね。紹介します、オーキードーキー博士です。(N社に怒られませんように) 今回実装する「オーキードーキー博士Bot」は、「ポケモン」という言葉を聞くと、初代ポケットモンスターに登場した151のポケモンの中から、あなたにぴったりなポケモンを選び、本日のラッキーモンスターとして提案してくれます。 それ以外の会話には、お前の話はつまらんと言わんばかりに話を遮り、「ポケモン」の話を促してきます。なかなか、頑固な性格のようです。自分の話だけしたがる方、時々いますよね。 (実装力がないばかりに、頑固な性格にしてしまってごめんよ、博士…。) 実際のBotの挙動 当初の計画通りには行きませんでしたが、ひとまず動くことをゴールにしました!オーキードーキー博士が、今日のラッキーポケモンをぶっきらぼうに教えてれるBOTです。#API #JS #LINE #protoout #ポケモン pic.twitter.com/Yka4omPDHF— yoshio333 (@yoshio333333) March 23, 2022 オーキードーキー博士のLINEBotと、ともだちになりたい方はこちら ↓ ※博士は気難しいので、時間帯によって既読スルーをしたり、明後日な返答をすることがあります。 ソースコード まずは、全体のソースコードです。 'use strict'; // ######################################## // 初期設定など // ######################################## // パッケージを使用します const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: '**************', channelAccessToken: '******************}; // ########## ▼▼▼ サンプル関数 ▼▼▼ ########## const sampleFunction = async (event) => { // 1〜151の数をランダムに吐き出す let a = Math.floor(Math.random() * 151) let pushData = {}; let replyText = ''; if (event.message.text === 'ポケモン') { // axiosでpokeAPIを叩きます const res = await axios.get('https://pokeapi.co/api/v2/pokemon-form/' + a); console.log(res.data); // 画像を送るメッセージを作る pushData = {type: 'image', originalContentUrl: res.data.sprites.front_default, previewImageUrl: res.data.sprites.front_default}; return client.replyMessage(event.replyToken,pushData) } else { replyText = 'その話題はつまらん。わしはオーキードーキー博士じゃ。正確に「ポケモン」と入力すると、今日のラッキーモンスターを教えてやるぞ!'; return client.replyMessage(event.replyToken, { type: 'text', text: replyText}) } ; }; // ########## ▲▲▲ サンプル関数 ▲▲▲ ########## // ######################################## // LINEサーバーからのWebhookデータを処理する部分 // ######################################## // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } // ######################################## // Expressによるサーバー部分 // ######################################## // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); 各部の解説 ①(乱数の設定) では、細かい要素を説明していきます。 // 1〜151の数をランダムに吐き出す let a = Math.floor(Math.random() * 151) PokeAPIは、IDでポケモンを呼び出せます。そのため、初代のポケモンが割り振られている1〜151の数字を吐き出すように「a」という関数をあらかじめ設定しておきます。(ゲームボーイの初代しかやったことがない。) 各部の解説 ②(関数をAPI内に呼び出す) const res = await axios.get('https://pokeapi.co/api/v2/pokemon-form/' + a); console.log(res.data); そしてaxiosでPokeAPIを呼び出すURLに、先程の乱数を生み出す関数「a」を入れこみます。こうすることで、毎回ランダムに数字が入るようになり、色々なポケモンのデータが呼び出されるようになります。 各部の解説 ③(ポケモンの画像を呼び出す) 先程の、コードでランダムに呼び出されたポケモン一体のいろいろなデータが呼び込まれているので、その中から、さらにポケモンの画だけを指定し(front_default)image形式で吐き出すようにしておきます。 pushData = {type: 'image', originalContentUrl: res.data.sprites.front_default, previewImageUrl: res.data.sprites.front_default}; 各部の解説 ④(LINEプラットフォームに送る) 先程の関数pushDataの中に、ポケモンの画像が入っているので、下記のコードでLINEプラットフォームに戻します。後は、LINEがいい感じに「ポケモン」と送ってきた、ユーザーのところに、ポケモンの画像を送り届けてくれます。 return client.replyMessage(event.replyToken,pushData) つまずいたポイント その① let pushData = {}; letで関数を指定しているときに、その関数はletのある{}の中でしか効かなくなるようです。関数を指定する場所は気をつけましょう。 その② 必要のない箇所を””で囲んでしまっていたので、ただの文字として認識されてしまっていました。 <不正解> originalContentUrl: "res.data.sprites.front_default" <正解> originalContentUrl: res.data.sprites.front_default, 正しいコードを書いていなかったので、結果何度も深夜にポケモンと叫び続けることになりました。みなさま、コードは正確に! 怪しげなところを直してみるが、LINEAPIがちっとも返事をしてくれないので、ポケモンをGETできない#protoout pic.twitter.com/nZXuena9fq— yoshio333 (@yoshio333333) March 21, 2022 本当はやりたかったこと 「もっと優しい博士にしたかった。」 「オーキードーキー博士はポケモンの画像を返してくれるが、それにあわせてポケモンの名前や、少しフレンドリーなセリフも戻したかった。」 return client.replyMessage(event.replyToken,pushData) return client.replyMessage(event.replyToken, { type: 'text', text: '今日の君のラッキーモンスターは**じゃ。いい日になりそうじゃのう。'}) こんな感じのコードで(これは、動きません) ただ、JavaScriptは、returnで一つの値しか返せないらしく、それを解決する方法もあるらしいのですが、時間切れとなり、現状の石頭タイプのオーキードーキー博士に仕上がりました。 やはり今のままだと、やり取りにそっけない感じがあり、まだまだ物足りなさがあります。妻にも、実際にオーキードーキー博士と戯れてもらいましたが、フーンと言う感じでした。「あんなに夜作業してるのにこの程度ですか」と思われていないだろか…。やりたいことと、企画に手が追いつかない悔しさよ…。 そして博士!!プログラミングの勉強をちゃんとして、いつか、やさしくてひょうきんな博士に戻してやるからな!! 次回予告 次回 #protoout 「やさしくなったオーキードーキー博士!?」 みんなも、俺と一緒にポケモンGETリクエストだぜ! それでは、次の課題で会いましょう!
- 投稿日:2022-03-23T22:02:49+09:00
今何週目?LINEBotとGoogleカレンダーを繋げて1年間の中でいま何週目かを知れるようにした話。
こんにちは。 今回はLINEBotとGoogleカレンダーAPIを使用して、 52週MDのうち今何週目なのかを知れるものを作りました。 ちなみに私は某小売業で働いております。 52週MDとは、【52週マーチャンダイジング】の略で 1年間を週単位にして、「今週(第〇週)は何を売り込むのか」を計画・実施するものです。 パートさんから無茶ぶり。今何週目かすぐわかるもん作って! 小売業あるあるかと思いますが、1年間52週のうち、いま何週目かわからなくなるんです。 商品調達バイヤーから〇週はこれ売って!と言われるのですが、 いま何週目やねん!!とそのパートさんはいつも思うらしく、私を頼ってきました。 パートさん:「なぁ、すぐに今何週目かわかる方法ない?」 私:「店長とかマネージャーに今何週?って聞いたらどうですか?」 パートさん:「そんなん知りたいときにすぐ近くにおらへんやん! 自分ですぐに知りたいねん」 ごもっとも・・・ てことで、パートさんの依頼に応えるべく、頑張ってみることにしました(笑) 一旦、作れたもの こんな感じに「何週目ですか?」と聞くと「3週目です」と 返信が返ってくるものができました! 実行環境 JavaScript Node.js 参考にした記事 LINEBOTからGoogleカレンダーを取得する ↑こちらの記事はJavaScriptを使用していない記事だったので、大体の実装の流れと 実装前の準備内容を把握するために使用。 googleカレンダー情報をLINEで取得&送信 ↑こちらも実装の流れを把握するために使用。 node.jsでGoogleカレンダーを操作する方法。 ↑こちらはGoogleカレンダーAPI取得のために使用。 作成したコード 実際に作成したコードはこちら 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const PORT = process.env.PORT || 3000; // Messaging APIを利用するための鍵を設定します。 const config = { channelSecret: '*******', channelAccessToken: '*******' }; const client = new line.Client(config); async function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // ユーザーにリプライメッセージを送ります。 /************************* sample.js *************************/ // ---------------------- // パッケージのインスタンス生成 // ---------------------- var Calendar = require('node-google-calendar'), config = require('./credentials/calendar-config'), calId = config.calendarId.myCal; var cal = new Calendar(config); // ---------------------- // カレンダーイベントの取得 // ---------------------- // 取得する対象期間を指定 // timeMinからtimeMaxの間のカレンダーイベントが取得される var params = { timeMin: '2022-03-10T06:00:00+09:00', timeMax: '2023-08-31T06:00:00+09:00', } const calEvents = await cal.Events.list(calId, params); console.log(calEvents[0].summary); if (event.message.text == '何週目ですか?') { // 3週目です、を返す return client.replyMessage(event.replyToken, { type: 'text', text: calEvents[0].summary }); } else { // 聞き方を間違えてますよ!など return client.replyMessage(event.replyToken, { type: 'text', text: "聞き方間違えてます!\n「何週目ですか?」と聞いてください。" }); } /* cal.Events.list(calId, params) .then(calEvents => { console.log(calEvents[0].summary); return client.replyMessage(event.replyToken, { type: 'text', text: calEvents[0].summary }); }) .catch(err => { console.log(err.message); }); */ } // ここ以降は理解しなくてOKです const app = express(); app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)')); app.post('/webhook', line.middleware(config), (req, res) => { if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); console.log('検証イベントを受信しました!'); return; } else { console.log('受信しました:', req.body.events); } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); まだまだ修正の余地あり!ここ直したい! 今はまだGoogleカレンダーAPIとLINEBotだけを繋げただけなので・・・ 何週目?と打ち込むと今日のスケジュールを返信されるようにしたい・・・ ちなみに今は、何週目?と聞いても3週目としか返信ができません。 〇週目と返信があるだけでなくToDoリストとかも一緒にわかるようにしたい・・・ 例えば、「今日は週の切り替え日なのでPOPを外しましょう!」と返ってくると 今日すべきことがわかりやすく、より実用的になると思います。 もっと頑張らなければ・・・ パートさんにお披露目!(超辛口FBもらったよ。) てことでパートさんにFBもらいました。 パートさんからのFB 私:「LINEで今何週かわかるものを作ってみましたが、イメージと合ってますか?」 パートさん:「合ってる!正解!でもこれシンプルすぎ! もっと絵文字とか使って可愛くして!あんたと一緒で不愛想あかん! あと、その週に売り込むものとか(特に売らないと怒られるもの)とか 一緒に返信来るようにして!あと、今4週目やからこれ間違ってんで!」 さすが優秀なパートさんだけあって、文句多いし、痛いところつく・・・ 真摯に受け止めて改善しますとしか言いようがない。 なんかもう少し褒めてほしいと思ったので、新たにFBもらいました。 【番外編】お父さんからのFB ただただ甘やかしてほしいがために、お父さんに作ったものを見てもらいました。 私:「お父さん、このLINE使ってみて~」 お父さん:「いやLINEは中国では使えんきん」 ちなみにお父さんは中国在住。中国ではLINEが使えないことをてっきり忘れていました。 てことで、動画を送ってみてもらうと お父さん:「よく頑張った!でもお父さんならごみ捨ての日教えてくれるものがほしい! ごみ捨ての日忘れてお母さんから怒りメールくるけん。」 確かによくお母さんが怒ってた。というよりキレてた!(笑) なるほど、こんな応用方法もあるのか気づかされました。 KakaotalkやWeChatでも同じようなことが出来ないか挑戦してみたいと思います。 まとめ はっきり意見を言ってくれる頼れるパートさんがいてくれたことで 今後の課題がはっきりとわかりました。 もっともっと修正して、パートさんが満足できるものを 作りたいと思います。 最後まで読んでいただき、ありがとうございました。
- 投稿日:2022-03-23T20:23:46+09:00
Types of property 'delimiters' are incompatible. Type 'string[]' is not assignable to type '[string, string]'. の対処法
概要 Django + vue.js などの場合、どちらも変数展開に mustache記法({{ }}) を用いるため、 vue.js 側の delimiters オプションを使用して、以下のように回避することがあると思います。 vue.js delimiters: ['[[', ']]'] Javascript と Typescript が混在しているプロジェクトの場合、 これが原因でビルド時にタイトルのエラーを吐くことがあります。 具体的には、ts 形式のファイルから上記のオプションが設定されている js 形式のモジュールを import して使用する場合などです。 原因 vue.js のランタイムにて、delimiters は以下のように型付けされています。 delimiters?: [string, string]; これは、文字列型が二つ含まれるタプル型です。 ところが、Javascript にはタプル型は存在しないため、文字列が含まれる配列型と誤認してしまうためにエラーとなります。 解決策 解決策はいくつかありますが、 たったこれだけのために副作用のありそうな諸々の無効化オプションを設定したくはないので、最も影響の小さい解決方法を考えました。 ts ファイルで、正しい型定義を行った変数を定義する export const typedDelimiters: [string, string] = ["[[", "]]"]; 上記変数を、js形式のファイルで import し、delimiters オプションの値として使用する import { typedDelimiters } from '../utils'; ...中略... delimiters: typedDelimiters トランスパイル時には、js も ts も全て Typescript として認識されます。 そこで、JS による動的型付けに任せず、明示的に型付けしてしまうというわけです。 以上です。
- 投稿日:2022-03-23T19:28:39+09:00
Qiita始めました!
目次 1.はじめに 2.自己紹介 3.おわりに 1. はじめに 初めまして! 日報アプリgamba!(ガンバ)でエンジニアをしている池田です! この度、日報アプリgamba!のメンバーでブログを始…
- 投稿日:2022-03-23T18:46:30+09:00
【PHP】タグ機能②
下記投稿の続きで、タグ機能に文字かタグの数で改行するように制御を追加します。 タグか文字の数で改行 form.php function inputChange() { : spans = skill.getElementsByTagName("span"), skills = new Array(); for (i = 0; i < spans.length; i++) { skills[i] = spans[i].textContent; } skills = skills.join(''); if (3 <= spans.length || 9 <= skills.length) { // 改行処理 } : タグ数と文字数を取得して、条件に合えば改行するようにします。 今回はタグ数が3つ以上または、文字数が9文字以上で設定します。 それでは、改行処理を実装していきます。 条件が一致したときに、div要素を作成して改行するようにします。 例えば上記のようなタグの場合は、 このようにdiv要素をタグの間に入れて改行させます。 form.php : div_element = document.createElement("div"); span_element.setAttribute("id", "child-span" + i + ""); : if (3 <= spans.length || 9 <= skills.length) { i--; if (document.getElementById('child-span' + i + '') !== null) { parentDiv.appendChild(div_element, document.getElementById('child-span' + i + '')); } i++; } i++; span要素に変数iが入ったIDを追加し、1個前のタグ('child-span' + i + ''からi-1したもの)があれば、div要素を追加します。 しかし、現在のままだと2回目の改行を行うときに、タグ数と文字数が前の状態のため何を入力しても改行してしまいます。 なので、改行があった際の処理を追加します。 form.php <body> : <input type="text" id="sample" /> <div id="skill"> </div> <input type="hidden" name="skill_count" id="skill_count"> //追加 : </body> : skill_count = document.getElementById('skill_count').val; : if (0 < document.getElementById('skill_count').val) { i--; skills = new Array(); // 改行した列で再度文字数取得 for (k = 0; k < skill_count; k++) { skills[k] = spans[i].textContent; i--; } spans = ''; skills = skills.join(''); // skill_countの値で改行後のタグ数を決める switch (skill_count) { case 2: i += 1; spans = '@@'; break; case 3: i += 2; spans = '@@@'; break; case 4: i += 3; spans = '@@@@'; break; case 5: i += 4; spans = '@@@@@'; break; default: } i++; document.getElementById('skill_count').val += 1; } if (3 <= spans.length || 9 <= skills.length) { : document.getElementById('skill_count').val = 1; } 改行を判定する値はブラウザ側(ID属性:skill_count)で持ちます。 skill_countが改行後のタグ数になっているため、こちらで改行後のタグ情報を取得します。 switch文でskill_countの値からspansに@を代入して、 タグ数を決めています。 こちらの処理を追加すると、2回目以降の改行もタグ数と文字数をカウントするようになります。 また、最終行タグのバツ印をクリックした際にdocument.getElementById('skill_count').valの値もデクリメントする必要があるので、最終行をクリックしたかの判定が必要になります。 from.php // タグのバツ印がクリックされた場合 $(document).on('click', '.far.fa-times-circle', function() { var k = 0, skills = new Array(), skill = document.getElementById("skill"), spans = skill.getElementsByTagName("span"), span = $(this).parents(".skill_tag")[0].textContent; switch (document.getElementById('skill_count').val) { case 1: var spans_count = spans.length - 1; break; case 2: var spans_count = spans.length - 2; break; case 3: var spans_count = spans.length - 3; break; default: } for (i = spans_count; i < spans.length; i++) { skills[k] = spans[i].textContent; k++; } $(this).parents(".skill_tag").remove(); skills = skills.join(''); if (skills.indexOf(span) != -1) { document.getElementById('skill_count').val -= 1; } }); spans_countに総タグ数から最終行のタグ数を引いた値を代入して、 その値からskillsに最終行のタグの文字を入れます。 spanにバツ印をクリックしたタグの文字を代入し、 skillsの中にspanの値があれば、document.getElementById('skill_count').valをデクリメントします。 最後にすでに入力されているものについては、タグ追加しないようにします。 function inputChange() { : // 既に入力済みのものはタグ追加しない if (skills.indexOf(fome_x_name) != -1) { return false; } :
- 投稿日:2022-03-23T17:58:25+09:00
ブラウザ上でフリック入力ができる仮想キーボードを作ってみた
はじめに 先日、対戦型ポケモンWordleをリリースしました。 リリース当初、ただテキストボックスにポケモンの名前を入力する形にしており、入力履歴の管理ができておりませんでした。 入力履歴の機能を追加しようと、大元のWordleを参考に入力用のボタンを用意しようと思いました。 ただ、日本語は50音+濁点・半濁点で80ボタン以上必要で、このボタンからポチポチするのは入力が大変だろうなと思いました。 そこで、ブラウザ上でフリック入力できる仮想キーボードを作ってみました。 完成品 See the Pen フリック入力 by yotty (@yotty) on CodePen. パソコンからでは直接フリック機能が使えません。 パソコンからフリック機能を確認したい方は、デベロッパーツールでデバイスを変更することで試すことができます。 ※Chromeの場合 F12でデベロッパーツールを開き、以下のアイコンからデバイスをモバイル端末に変更 実装 以下、実装内容の解説です。 事前準備 事前準備としてさくっと外観を作っておきます。 また、フリック入力の際、長押しで文字選択されたり、入力のタップでズームされたりしないように以下を追加しておきます。 css body{ user-select: none; -webkit-user-select: none; -ms-user-select: none; -moz-user-select: none; -khtml-user-select: none; touch-action: none; } さらに、フリックした際、濁点・半濁点の切り替えを行った際に入力すべき文字を特定するために以下のような配列を用意しておきます。 const l_word = [ ['ア', 'イ', 'ウ', 'エ', 'オ' ] , ['カ', 'キ', 'ク', 'ケ', 'コ' ] , ['サ', 'シ', 'ス', 'セ', 'ソ' ] , ['タ', 'チ', 'ツ', 'テ', 'ト' ] , ['ナ', 'ニ', 'ヌ', 'ネ', 'ノ' ] , ['ハ', 'ヒ', 'フ', 'ヘ', 'ホ' ] , ['マ', 'ミ', 'ム', 'メ', 'モ' ] , ['ヤ', '', 'ユ', '', 'ヨ' ] , ['ラ', 'リ', 'ル', 'レ', 'ロ' ] , ['ワ', 'ヲ', 'ン', 'ー', '' ] ]; const l_word_switch = [ ['ア', 'ァ'] , ['イ', 'ィ'] , ['ウ', 'ゥ'] , ['エ', 'ェ'] , ['オ', 'ォ'] , ['カ', 'ガ'] , ['キ', 'ギ'] , ['ク', 'グ'] , ['ケ', 'ゲ'] , ['コ', 'ゴ'] , ['サ', 'ザ'] , ['シ', 'ジ'] , ['ス', 'ズ'] , ['セ', 'ゼ'] , ['ソ', 'ゾ'] , ['タ', 'ダ'] , ['チ', 'ヂ'] , ['ツ', 'ッ', 'ヅ'] , ['テ', 'デ'] , ['ト', 'ド'] , ['ハ', 'バ', 'パ'] , ['ヒ', 'ビ', 'ピ'] , ['フ', 'ブ', 'プ'] , ['ヘ', 'ベ', 'ペ'] , ['ホ', 'ボ', 'ポ'] , ['ヤ', 'ャ'] , ['ユ', 'ュ'] , ['ヨ', 'ョ'] ]; フリック入力 フリックを検知するために、「touchstart」「touchmove」「touchend」の3つのイベントを利用します。 「touchstart」で、タッチした場所を記憶しておきます。 その他、フリックしたときのオブジェクトの位置や文字などの設定もしています。 touchstart function onTouchStart(event) { $(event.currentTarget).addClass('gray_for_touch'); position = event.originalEvent.touches[0]; direction = 'c'; //フリックのオブジェクトの位置を移動させておく offset = $(event.currentTarget).offset(); $('.kb_key_u').offset({ top: offset.top - 48, left: offset.left }); $('.kb_key_d').offset({ top: offset.top + 48, left: offset.left }); $('.kb_key_l').offset({ top: offset.top, left: offset.left - 48 }); $('.kb_key_r').offset({ top: offset.top, left: offset.left + 48 }); //テキストセット var l = l_word.find(x => x.includes($(event.currentTarget).text())); $('.kb_key_u').text(l[2]); $('.kb_key_d').text(l[4]); $('.kb_key_l').text(l[1]); $('.kb_key_r').text(l[3]); }; 「touchmove」では、移動した現在の位置を取得して上下左右にどれだけ移動したかを計算します。 上下左右それぞれの移動距離のmaxを取って移動方向を検知し、閾値20を超えたらその移動方向にフリックしたと判定させています。 touchmove function onTouchMove(event) { direction = 'c'; $('.kb_key_u').addClass('transparent'); $('.kb_key_d').addClass('transparent'); $('.kb_key_l').addClass('transparent'); $('.kb_key_r').addClass('transparent'); $(event.currentTarget).addClass('gray_for_touch'); $(event.currentTarget).removeClass('transparent'); var new_position = event.originalEvent.touches[0]; var u = position.screenY - new_position.screenY; var d = new_position.screenY - position.screenY; var l = position.screenX - new_position.screenX; var r = new_position.screenX - position.screenX; var max = Math.max(u, d, r, l); if (max < 20) { return; } var input_key = $(event.currentTarget).text(); $(event.currentTarget).removeClass('gray_for_touch'); $(event.currentTarget).addClass('transparent'); if (max == u) { direction = 'u'; $('.kb_key_u').removeClass('transparent'); } else if (max == d) { direction = 'd'; $('.kb_key_d').removeClass('transparent'); } else if (max == l) { direction = 'l'; $('.kb_key_l').removeClass('transparent'); } else if (max == r) { direction = 'r'; $('.kb_key_r').removeClass('transparent'); } }; 「touchend」では、「touchmove」で検知したフリックの方向から入力すべき文字を判断します。 フリックの方向によってindexを変えて、事前に用意した「l_word 」から入力する文字を決定します。 あとは、テキストボックスの値の最後にその文字を付与します。 touchend function onTouchEnd(event) { $('.kb_key_u').addClass('transparent'); $('.kb_key_d').addClass('transparent'); $('.kb_key_l').addClass('transparent'); $('.kb_key_r').addClass('transparent'); $(event.currentTarget).removeClass('gray_for_touch'); $(event.currentTarget).removeClass('transparent'); var input_key = ''; var key = $(event.currentTarget).text(); var l = l_word.find(x => x.includes($(event.currentTarget).text())); var index = 0; if (direction == 'c') { index = 0; } else if (direction == 'u') { index = 2; } else if (direction == 'd') { index = 4; } else if (direction == 'l') { index = 1; } else if (direction == 'r') { index = 3; } var txt = $('#txt').val(); if (l[index] == '') { return; } $('#txt').val(txt + l[index]); }; 濁点・半濁点切り替え テキストボックスに入力されている最後の文字を「l_word_switch」から探し、indexを1ずらして濁点・半濁点を切り替えて文字を取得しています。 濁点・半濁点切り替え function onTouchStart_switch(event) { $(event.currentTarget).addClass('gray_for_touch'); temp_word = $('#txt').val(); targer_word = temp_word.slice(-1); var l_switch = l_word_switch.find(x => x.includes(targer_word)); if (l_switch === undefined) { return; } var index = l_switch.indexOf(targer_word); if (index < 0) { return; } else if (index == l_switch.length - 1) { index = 0; } else { index += 1; } input_key = l_switch[index]; $('#txt').val(temp_word.slice(0, -1) + input_key); }; バックスペース テキストボックスの最後の文字を削除するだけで実装できます。 バックスペース $(document).on('touchstart', '#kb_key_bs', function (event) { $(event.currentTarget).addClass('gray_for_touch'); var temp_txt = $('#txt').val(); $('#txt').val(temp_txt.slice(0, -1)); }); おわりに 実際のアプリでは、今回紹介したフリック入力を組み込み、入力欄を細かく分けることによって入力履歴の管理を実現しています。 アプリが気になった方はこちらから遊ぶことができるので、ぜひ一度触ってみてください! 参考
- 投稿日:2022-03-23T12:30:51+09:00
【Javascript】配列や連想配列をディープコピーして安全な配列を取得する。【JSON.parse, JSON.stringify】
自分用のメモとして残します。 Reactでstateで多次元の連想配列を参照する際は普通の代入ではなくディープコピーしないと、 大元のstateに格納した値(≒要素)がsetStateしてないのに変わってしまいます。 そのため、ディープコピーをしてあげることで安全に作業用の別配列として扱えるようにします。 やり方 JSON.stringify でjson文字列にしてから JSON.parse でJson文字列をjsで使用できる形式に変換する。 //this.state = {hoges : [{hoge1:'hoge2'},{hoge10:'hoge20'}]} //左記の値がhogesに入っているとする。 let orig_hoge = this.state.hoges // orig_hoge をこのまま使うと、this.state.hogesの値も変えてしまうため、下記でディープコピーしてあげる。 let clone_hoge = JSON.parse(JSON.stringify(orig_hoge)) 以上です。
- 投稿日:2022-03-23T12:00:02+09:00
【javascript】type="module"の時にグローバル変数を作る方法
結論 window.globals = {変数名: 値} は、javascript実行環境全体で使いまわすことができる。 そもそもの問題 type="module"でjavascriptを使っていると、「varを省略することでグローバル変数を作る」ことが出来ない。 だから、次のプログラムは動かない。 type="module"の苦悩 <html> <head> <script type="module"> document.body = document.createElement("body"); document.body.innerHTML = ` <div id="カウンタ"></div> <div id="ボタンたち"> <input type="button" value="++" onClick="画面.add(1)"> <input type="button" value="--" onClick="画面.add(-1)"> <input type="button" value="=" onClick="画面.print()"> </div> `; 画面 = { // ★ i : 0, add : function(delta){this.i += delta;}, print: function(){document.getElementById("カウンタ").innerHTML = this.i;} }; 画面.print(); </script> </head> <body></body> </html> これをブラウザから表示すると、RefarenceErrorになる。 なぜならtype="module"の場合、varなどなしに変数を宣言できないからだ。 ★の行をvar 画面 = {に代えると、最初こそ描画されるが、ボタンをクリックしたときに画面 not defined at HTMLInputElement.onclickと怒られる。 headタグ内でvarにて定義したローカル変数を、body以下の要素で利用しようとしたのだから当然だ。 ようするにtype="module"のスクリプト内で定義した変数を、同じファイル内の別の個所で利用しようとすると詰むのだ。 そもそもtype="module"は、グローバルスコープで動かない。 しかし抜け道がある。それこそがwindowオブジェクトの利用だ。 windowオブジェクトは、JavaScriptコードが動作しているWebブラウザのウィンドウまたはフレームを表すオブジェクトだ。 なので、ここにglobalsプロパティを勝手に追加し、window.globalsへグローバル変数とその値を記述した連想配列(=オブジェクト)を代入してしまえば、その内容は同じフレーム内のどこからでも取り出すことが出来るというわけだ。 ちなみに、参考2によれば、javascriptはそれがブラウザで実行されていようがNode.js上で実行されていようが、スクリプト実行前に一つ「Globalオブジェクト」と呼ばれるものを生成し、これは実行環境全体で共有されるそうだ。ブラウザで実行されるjavascriptにおいて、「Globalオブジェクト」とはwindowそのものである。 動作する例は次の通り。 type="module"でもグローバらせてくれるwindowさん(変更点は全5か所) <html> <head> <script type="module"> document.body = document.createElement("body"); document.body.innerHTML = ` <div id="カウンタ"></div> <div id="ボタンたち"> <input type="button" value="++" onClick="window.globals.画面.add(1)"> // 変更点 <input type="button" value="--" onClick="window.globals.画面.add(-1)">// 変更点 <input type="button" value="=" onClick="window.globals.画面.print()">// 変更点 </div> `; var 画面 = { // 変更点 i : 0, add : function(delta){this.i += delta;}, print: function(){document.getElementById("カウンタ").innerHTML = this.i;} }; 画面.print(); window.globals = {画面:画面}; // 変更点 </script> </head> <body></body> </html> See the Pen Untitled by 平田 智剛 (@17ec084) on CodePen. 参考 1. 2.
- 投稿日:2022-03-23T11:52:11+09:00
RailsアプリにChoices.jsを使ったセレクトボックスを作る
概要 Choices.jsを使ってセレクトボックスを作成しました はじめに 現在、フィヨルドブートキャンプでチーム開発をしています。 Choices.jsというライブラリを使って、セレクトボックスを作成しましたが、情報が少なかったのでQiitaに記事として残しておきます。 初学者向けの内容となっています。 Choices.jsについて 軽量な選択ボックス/テキスト入力プラグインです。リッチなセレクトボックスが作れます→DEMO Select2およびSelectizeに似ていますが、jQueryの依存関係はありません。 2年ほどリリースが止まっていたようですが、2021年の12月より再びメンテナンスされているようです。 インストール npm npm install choices.js yarn yarn add choices.js stylesheet 導入したらしっかりstylesheetで読み込みましょう。 自分はこれを忘れて無駄な時間を過ごしてしまいました?(忘れるのは初学者の自分だけだと思います) CDNは以下、その他ディレクトリで読み込むものなど多数あります(README参照) <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/styles/choices.min.css"/> 用語 専門用語というほどではないんですが、ここを読み解かないとうまく使いこなせませんでした。 ほとんどREADMEを翻訳したものだけど載せておきます。 (今回はセレクトボックスを作るので、テキストボックスとしての使い方は割愛) 用語 説明 Choice ユーザーが実際に選択できる値で、<option>要素と同等 Group 選択肢の集まりでその名の通りグループ。<optgroup>要素と同等 Item 選択された要素の<option value = "item">要素と同等 HTMLでのセレクトボックスはというと <select name="spice"> <option value="">スパイスを選択</option> <option value="garammasala">ガラムマサラ</option> <option value="coriander">コリアンダー</option> <option value="cumin">クミン</option> </select> となっているのでChoiceは<option>のこと、Itemは<option>のvalueだとわかります。 ということでChoices.jsを使って、もっとも簡単だと思われるコードを書いてみました index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Spice select</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/styles/choices.min.css" /> <script src="https://cdn.jsdelivr.net/npm/choices.js@9.0.1/public/assets/scripts/choices.min.js"></script> </head> <body> <select name="spice" id="hoge"> <option value="">スパイスを選択</option> <option value="garammasala">ガラムマサラ</option> <option value="coriander">コリアンダー</option> <option value="cumin">クミン</option> </select> <script> document.addEventListener("DOMContentLoaded", () => { new Choices("#hoge") }) </script> </body> </html> セレクトボックスにhogeというidを振って、Choicesオブジェクトを作る際に紐づけるだけです。 これでインクリメンタルサーチが簡単に使えるし、上の通りvalue属性でもヒットします。 選択肢が多くなったときに便利ですね。 開発者ツールを見てみるとclass="choices"やdata-type="select-one"などのタグが使われています。 <select>はselect-one、<select multiple>とselect-multipleとなっていて、 オプションが反映できるか否かは、この入力タイプを確認するとわかりやすいと思います。 Railsでの使い方 再び簡単な復習になるんですが、既にSpiceモデルがあるとして、selectを使うと select(:spice, :name, Spice.pluck(:name, :id), { include_blank: "スパイスを選択" }) collection_selectを使うと collection_select(:spice, :name, Spice.all, :id, :name, prompt: "スパイスを選択") これをHTMLで出力すると、いずれも <select name="spice[name]" id="spice_name"> <option value="">スパイスを選択</option> <option value="1">ガラムマサラ</option> <option value="2">コリアンダー</option> <option value="3">クミン</option> </select> のようになります。 こちらもこのセレクトボックスにidさえ付与すれば、Choices.jsが使用可能です。 ここからは実際に自分がチーム開発で実装したコードに、一部修正を加えたものを記載していきます。 環境とgem ruby 3.1.0 Rails 6.1.4.4 webpacker 5.4.3 Choices.js slim-rails UserモデルとSpiceモデルが存在し、UserはSpice(好みのスパイス)を1つ持つような関係です。 CSSに関してはnode_modulesから読み込むように設定しています。 view = f.label :spice, class: 'a-form-label' = f.collection_select :spice_id, all_spices_with_empty, :id, :name, {}, { id: 'js-spice-select' } フォーム内でのセレクトボックスです。 セレクトボックスにjs-spice-selectというidを付与しています。 all_spices_with_emptyは選択肢なしを含めた配列です。 def all_spices_with_empty Spice.all.to_a.unshift(Spice.new(name: '好みのスパイスなし')) end javascript app/javascript/packs/application.js import '../spice-select.js' JavaScriptファイルをWebpacker経由で読み込んでいます。 app/javascript/spice-select.js import Choices from 'choices.js' document.addEventListener('DOMContentLoaded', () => { return new Choices('#js-spice-select', { removeItemButton: true, searchResultLimit: 10, searchPlaceholderValue: '検索ワード', noResultsText: '一致する情報は見つかりません', itemSelectText: '選択' }) }) 以上のように、少しだけオプションをつけてみますと Choices.jsを使ったセレクトボックスが完成しました? これがRailsでChoices.jsを使う初歩的な方法だと思います。 他にも選択肢を他からfetchして作成したり、複数の選択肢を選んだり、タグをつけたり、、、 などなどカスタマイズが可能なので、少しリッチなセレクトボックスを作成するのにいかがでしょうか? 最後に 最後までご覧いただきありがとうございました。本記事がどなたかのご参考になれば幸いです。 何かご意見やご感想などありましたら、お気軽にコメントや編集リクエストをお待ちしております? 参考文献 執筆にあたり、以下の記事を参考とさせていただきました。ありがとうございました?♂️
- 投稿日:2022-03-23T09:03:53+09:00
VSCode上でNode.js(ES6で実装)のdebugをするための設定をしてみた
以下の記事を参照して頂ければと思います ※Qiitaの記事は全て個人的な記載であり、所属する組織団体とは無関係です 補足 ソースコード全体は以下。
- 投稿日:2022-03-23T04:47:13+09:00
【jQuery】動的にHTML要素を追加する処理
概要 jQueryをつかって要素を動的に追加する処理を実装したので残す HTML要素 index.blade.php <label class="col-md-2 col-form-label text-md-right"> URL </label> <div class="col-md-12 mb-4 domain_area"> <input type="url" name="url[]" class="form-control" value="" placeholder="https://example.com/"> </div> <div class="col-md-2"> <input type="button" value="+" class="add btn btn-secondary"> <input type="button" value="-" class="del btn btn-secondary"> </div> index.js $(function () { /** * @summary 項目追加 * @param target */ const addForm = (target) => { $(target).parent().prev('.domain_area').clone(true).insertBefore($(event).parent()); } /** * @summary 項目削除 * @param target */ const delForm = (target) => { const nowRowCount = $('.domain_area').length - 1; if (nowRowCount >= 1) { $(target).parent().prev('.domain_area').remove(); } } $(document).on("click", ".add", (event) => addForm(event.currentTarget)); $(document).on("click", ".del", (event) => delForm(event.currentTarget)); });
- 投稿日:2022-03-23T01:57:16+09:00
Material UI(MUI)テーマをKotlin/JSで作成する
こんにちは。最近Kotlin/JSのMaterial UI(以下MUI)で遊んだときにフォントを変えたくて少し詰まったので共有します。 MUI のスタイルについて MUIには色などの設定を管理するための手段としてThemeという概念があります。 これをKotlin/JSで実装します。 Kotlin/JS でMUI のTheme を作成する 今回はフォントにGoogle Fonts から提供されているものを使用します。 そのため、使用するHTMLファイルにlinkタグなどで使用できるようにしておいてください。 SampleTheme.kt import kotlinx.js.jso import mui.material.styles.createTheme // 使用するフォントをListで定義する var fonts = listOf("Noto Sans JP", "sans-serif").toString().drop(1).dropLast(1) // テーマの作成 val SampleTheme = createTheme( jso{ typography = jso{ fontFamily = fonts } } ) まず使用するフォントをリストで定義します。このリストをfontFamilyに指定すればいいのですがそのままだとリストの両端にある[ ]が邪魔になります。そこで、.drop(1).dropLast(1)とすることで両端の不要な文字を削除しています。 なんだか汚い感じがするので、これの他に方法を知っている方はぜひ教えてください。 コンポーネントにテーマを適用する 今回は、コンポーネント全体に適用させたかったため全ての親要素でテーマを適用しています。 テーマを使用するにはThemeProviderのthemeプロパティに作成したテーマを指定するだけです。 App.kt fun main(){ render( element = App.create(), container = document.getElementById("root")!! ) } private val App = FC<Props>{ // ThemeProvider を使用する ThemeProvider{ this.theme=SampleTheme // 作成したテーマの適用 CssBaseline() Box{ Appbar() } } } 注意点 同名のクラスが複数存在するためimportするクラスを間違える可能性がとても高いです。 うまく実行できないときはimportしているクラスを見直して、以下のクラスになっているか確認してください。 import mui.material.Box import mui.material.CssBaseline import mui.system.ThemeProvider おわりに Kotlin/JS とMUI を使ったテーマの適用方法を共有しました。 間違っている点や、改善点などあれば優しく指摘してくれると助かります。
- 投稿日:2022-03-23T01:06:55+09:00
【4日目】メモ帳アプリの作成 [1]
1.本日の課題 今回はメモアプリを作る。最初は単純な仕組みから始めていき、途中からいろいろな要素を足していこうと思います。 まずはテキストボックス内に入力した文字を、ボタンを押したら表示できるようにするところから始めていきます。 <html> <head> <title>メモ</title> </head> <body> <input type="text" id="writeMemo" value="" /> <input type="button" value="save" onclick="saveMemo()" /> <hr> <div> メモ記録領域 </div> <p id="outputMemo"></p> </body> <script> function saveMemo() { let writeMemo = document.getElementById('writeMemo'); let data = writeMemo.value; let outputMemo = document.getElementById('outputMemo'); outputMemo.innerHTML += `<p>${data}</p>`; } </script> </html> このプログラムの流れを説明すると、 テキスト入力枠のidwriteMemoを取ってくる dataにwriteMemoのvalueを取得させる テキスト出力枠のidoutputMemoを取る 出力枠のinnerHTMLにdataを表示する枠を追加させる 今度はlocalStrageを用いて、ページをリロードしてもデータが削除されないようなメモ帳を作ります。まずは1つだけを保存できるようにプログラムを組みました。 <html> <head> <title>メモ</title> </head> <body> <input type="text" id="writeMemo" value="" /> <input type="button" value="save" onclick="saveMemo()" /> <input type="button" value="load" onclick="loadMemo()" /> <hr> <div> メモ記録領域 </div> <p id="outputMemo"></p> </body> <script> function saveMemo() { let writeMemo = document.getElementById('writeMemo'); localStorage.setItem('writeMemo', writeMemo.value); } function loadMemo() { let outputMemo = document.getElementById('outputMemo'); outputMemo.innerHTML += `<p>${localStorage.getItem('writeMemo')}</p>`; } </script> </html> ここではちゃんとlocalStrageにデータが保存されているのかを確認するためにsaveボタンとloadボタン(それぞれ保存と読み込み)を作りました。saveで入力欄に書いてある文字列を保存し、loadでその文字を出力することができます。ただし、saveは新しいものを保存しようとするとデータが上書きされてしまいます。 2.本日学んだこと 本日新たに学んだものはlocalStrage関数です。今回使った関数を簡単に説明します。 localStorage.setItem('キーの名前', 保存するデータ); localStorage.getItem('キーの名前'); 使い方としては、データを保存したいときにはsetItemを使用してキーの名前を設定し保存するデータを入力します。そして取り出すときはgetItemを使用し、取り出したいキーの名前を記入します。これが今回使ったlocalStrageの簡単な動作です。 今回はデータを一つまでしか保存できなかったので、次回は複数個のデータをlocalStrageに格納する方法とプログラムを投稿します。
- 投稿日:2022-03-23T00:22:18+09:00
【初心者向け】ポケモンでオブジェクト指向をふわっと学ぶ
はじめに オブジェクト指向とは何か。 プログラミングを学んでいく上で欠かせないものらしいとわかっていながら、ずっと後回しにしていた方や何回学んでも忘れてしまう方(自分)に向けてアウトプットを兼ねて、馴染みのあるポケモンを例に自分なりにまとめてみました! できるだけわかりやすい言葉でまとめているのでかる〜い気持ちで読んでくれたら嬉しいです! もくじ ・オブジェクト指向の理解が困難なワケ… ・オブジェクト指向誕生のきっかけ ・オブジェクト指向とは ・オブジェクト指向で出てくる用語 ・ポケモンでオブジェクト指向を考えてみる ・さいごに オブジェクト指向の理解が困難なワケ… これにはプログラミング初学者ならではの理由があると思いました。 ・オブジェクト指向自体が考え方や概念であるため。 ・書籍や記事での解説が実用的な例を使った解説ではないため。 オブジェクト指向はルールがあって、それに従って書けば動く何かだ!と言うシンプルなものではなく、「概念」「考え方」であるために難しく感じてしまいます。「愛とは何か。」それを考えるのも難しいですよね。。 そして、未経験の人にとってはオブジェクト指向を知らなくてもポートフォリオなどは難なく開発できてしまうために、概念を知ったとしても使うことのメリットは感じづらいことも挙げられるかと思います。 前提として、オブジェクト指向はさまざまな機能を盛り込み、後々修正箇所が多岐に渡るような大規模な開発において力を発揮します。 また、書籍や記事で見る多くの説明は、そのページ内では理解できても、実際の開発現場の使い方が全くイメージできないなようなものが多いなと感じました。 オブジェクト指向誕生のきっかけ 新たな発明には必ず背景があり、より良いものを生み出そうとする理由があります。オブジェクト指向の理解もまた、背景を知ることで深まると感じました。簡単に誕生のきっかけをまとめてみました。 時代と共にコンピューターの性能が向上し、開発規模が大きくなる。するとソースコードが膨大になり、複雑化することによってバグが多くなるという問題が生まれました。 ・修正したい箇所は一つなのに、他のコードも修正する必要があった ・一つのプログラムの追加や修正が他のプログラムに影響を与えてしまい、バグが発生。 ・少しの機能を変更したいだけなのに修正しなければならない箇所が多い。 ・冗長なコードで読みづらく、新規アサインメンバーが解読できない。 以上のように修正が面倒なことや影響範囲が大きいと言った理由から大規模システムを作るのは難しかったみたいです。 このままでは、ソフトウェア需要に対して供給が追いつかない!! 時間が足りない、、、人手も足りない、、、なんとか解消しなければ、、、 そこで!!! システム開発の複雑化を解消するために作られたのがオブジェクト指向 なわけです! オブジェクト指向とは それではオブジェクト指向とは何か、どのような概念・考え方なのか。 いかに効率よく、わかりやすく、開発ができるか そのために機能を共通化し、コードの重複を減らし、再利用性を高める これを突き詰めた考え方がオブジェクト指向です。 しかし、これでは良くわかりません。概念を知っただけじゃその考え方が実際にどのように使われているのか想像もつきません。それがどれほど重要なのかもさっぱりです。。 そこで、以下の用語を使ってポケモンを例にオブジェクト指向をわかりやすく説明しようと思います!! オブジェクト指向で出てくる用語 ・?クラス……設計図 ・?インスタンス……実体/オブジェクト(モノ) ・?プロパティ……属性/性質 ・?メソッド……機能/処理 ・?継承……クラスからプロパティやメソッドを引き継ぐ ・オーバーライド……上書き ポケモンでオブジェクト指向を考えてみる ポケモンが持つプロパティ(属性)は 「体の色」「大きさ」「形」と考えます。 メソッド(処理)は 「鳴き声を発する」「技を繰り出す」だと考えます。 どれもポケモン一匹ずつがそれぞれ持つものであり、異なるものです。 ポケモン図鑑に登録されているポケモンは全部で何種類でしょうか? wikiによると、全部で908種類いるみたいです。。ポケモンすご。。 それではあなたがポケモン開発部隊だったとします。 ーーーーオブジェクト指向じゃない場合ーーーー このポケモンたちそれぞれ"1匹"に対して、 プロパティとして色、形、大きさをセットして、、、 さらに鳴き声を発する機能をつけて、、、 さらにこんな鳴き声にセットして、、、 技を繰り出す機能つけて、、、 こんな技を使えるようにセットして、、、完了っと!!! さっ!この工程であと907種類作るぞぉーー!!!(狂気 想像しただけで涙が出ます。ここは地獄でしょうか。あぁ、アニメがみたい。ゲームがしたい。実家の味噌汁を啜りたい。。。 それではオブジェクト指向の場合どのようになるのか。 ーーーーオブジェクト指向の場合ーーーー 全ポケモンが共通しているプロパティ(色、形、大きさ)・メソッド(鳴き声を発する、技を出す)を変更を仮定して予め記述したポケモンクラス(ポケモンの設計図)を最初に作成する。 そのポケモンクラスを継承し、クラスにセットされた共通のプロパティとメソッドをその1匹独自のものに上書きしていく(オーバーライドする)だけ。 例えば、「ピカチュウ」というインスタンスを作ろうとした時、 元々ポケモンクラスから与えられた「色」プロパティに対して「黄色」を、 「形」プロパティに対して「ねずみ」を、 「大きさ」プロパティに対して「40cm」を、 「鳴き声を発する」メソッドに対して「ピッピカチュ〜♪」を、 「技を繰り出す」メソッドに対して「10まんボルト」をはめ込みます。 このようにしてクラスから与えられた情報に対して独自の見た目や技をはめ込むようにして唯一のポケモン(インスタンス/実体)を作れることができ、全てを0から書く必要がなくなります。 つまり、予めクラスに書いておいた、「技を繰り出すメソッドの定義や鳴き声をはめ込むための定義」等は改めて書く必要はありません。どんな技かを繰り出すのかをセットするだけです。 修正箇所が見つかった場合も同様の考え方ができます。 一匹のポケモン(インスタンス)に修正箇所が見つかった場合、独立したそのポケモンのプロパティやメソッドを修正すれば良いだけで、その他のポケモン(別のインスタンス)に修正を加える必要はありません。 つまり、修正ポイントを見つけやすく、修正による他のコードへの影響も少なく、修正箇所も自然と少なくなります。 さいごに 以上で、「オブジェクト指向とは何か、どういう概念なのか。」で述べた いかに効率よく、わかりやすく、開発ができるか 機能を共通化することによって、コードの重複を減らし、再利用性を高めている ということを突き詰めた考え方である。 と説明したのが少しは腑に落ちるのではないかと思います。 私自身まだまだ理解が乏しい駆け出しの身なので、間違えている箇所等ありましたらご指摘いただけると幸いです! 最後まで読んでいただきありがとうございました!
- 投稿日:2022-03-23T00:14:28+09:00
M5Stackモジュールにせっかく赤外線がついているんだから、互いに送受信し合おう
M5Stackには、WiFiやBluetoothがついているので、無線でやりとりすることができます。 ですが、通信するためのHTTPの実装やら、BLEの実装やらで、面倒です。 慣れてしまえばよいのですが、メモリもたくさん食うし、ちょっとしたデータの交換だけの場合は大げさに見えてきます。 一方で、たくさんあるM5Stackモジュールには、意外と赤外線送信機がついているものです。 ちょっと挙げただけでも、M5StickC、M5StickC Plus、M5Atom Matrix、M5Atom Lite、M5Echoなど。 一方で、赤外受信機は、あまりついていないですが、M5Unitとして赤外線送受信ユニットがあります。 ということで、今回は、お手軽に赤外線で送受信してデータを交換してみます。 ちなみに、最近技術書を書きまして、それの拡張として実装しましたので、それも説明します。こちらもぜひ見てみてください!実装が非常に楽になります! M5StackとJavascriptではじめるIoTデバイス制御 利用するライブラリ 利用するのは、「IRremoteESP8266」です。PlatformIOのLibraryで検索に引っ掛かります。 platformio.iniに以下を追加するのでもよいです。 platformio.ini lib_deps = crankyoldgit/IRremoteESP8266@^2.8.2 送信側 宣言 #include <IRsend.h> static IRsend *g_irsend = NULL; 初期化 pinに赤外線送信端子があるPIN番号を指定します。 g_irsend = new IRsend(pin); g_irsend->begin(); 送信 g_irsend->sendNEC(data, 32, repeat); 上記は、NEC方式の送信で、送信データは任意の32ビット長の値です。ほんとは64ビットも大丈夫なのですが、簡略化のため32ビットで。 repeatには、再送回数を指定します。まあ、間に障害物がなければ、0回でも大丈夫でしょう。 受信側 宣言 #include <IRrecv.h> static IRrecv *g_irrecv = NULL; static decode_results results; 初期化 pinに赤外線受信端子があるPIN番号を指定します。 g_irrecv = new IRrecv(pin, kRawBuf, kTimeoutMs, true); 受信待ち開始 g_irrecv->enableIRIn(); g_irrecv->resume(); 受信チェック uint32_t checkRecv(void){ if( g_irrecv->decode(&results) ){ g_irrecv->resume(); if( results.decode_type != NEC_LIKE ) return 0; return results.value; } return 0; } 上記関数は、0以外が返ってきたら、それが受信データという仕様です。簡略化のため、NEC方式しか受け付けません。 これだけで、32ビット(4バイト長)のデータを送り合いっこができます。 Javascriptで実装 以下の技術書で紹介している、ESP32内部で動作するJavascript環境で動作させる場合のJavascriptコード例です。 M5StackとJavascriptではじめるIoTデバイス制御 以下のコードを、サンプルWebページから、それぞれのM5Stackモジュールにダウンロードするだけです。 まずは、送信側。PIN番号9に、赤外線送信端子がついている想定です。 import * as ir from "Ir"; import * as input from "Input"; ir.sendBegin(9); console.log("setup finished"); setInterval( () =>{ esp32.update(); if( input.wasPressed(input.BUTTON_A) ){ console.log('ir.send'); ir.send(12345678); } }, 1); 次は受信側。PIN番号32に、赤外線受信端子がついている想定です。 import * as ir from "Ir"; ir.recvBegin(32); ir.recvStart(); setInterval(() =>{ var data = ir.checkRecv(); if( data ){ console.log('ir.received: ' + data ); } }, 1); 以上