Pythonでurllib.requestでmethodを渡すとunexpected keyword argument 'method'でエラーになる。
ChatBotで位置情報をとって、それをGoogle Map上に表示。
活動でデモをする必要があり、チャットボットいろいろと作ろうと思っている。
今日作ったのは、送信された位置情報を受け取って、DBに保存し、Webサイト上でそれを見れるようにするというもの。簡単なChatBotを作成している想定で話をします。
手順としては、
1.ユーザーから受け取ったメッセージから位置情報を抜き出す
2.DBにその情報を保存
3.Google Map上に表示
1.ユーザーから受け取ったメッセージから位置情報を抜き出す
受け取ったメッセージから位置情報をとるのは下記のようにします。
lat = event.message.attachments[0].payload.coordinates.lat
lng = event.message.attachments[0].payload.coordinates.long
2.DBにその情報を保存
このChatBotではMongoDBをMongoose経由で利用しているのですが、位置情報の入れ方がよく分からず時間がかかってしまいました。
var location;
var locationSchema = mongoose.Schema({
title: String,
coordinates: [Number]
});
new location(
{"title": title
, "coordinates":[lat,lng]}).save();
こんな感じで定義して、保存したら問題なく行けました。
3.Google Map上に表示
地図を表示するには、、、
あとから見つけましたがGoogleのサイトが丁寧で分かりやすかったです。
Adding a Google Map with a Marker to Your Website | Google Maps JavaScript API | Google Developers
吹き出しを追加したい場合は以下のサイトを参考に。
http://www.tam-tam.co.jp/tipsnote/javascript/post7755.html
JSONデータを取得する処理久しぶりに書いて、はてさてとなったので今どきの書き方のリンクも貼りつけます。
色々とあさってたらこういうGitHubのレポジトリが。ChatBotのコードを丁寧に説明してくれている。また読んでみよう。
FirebaseでトランプのドボンのWebアプリを作る。(レイアウト)
色々と手を出し過ぎなんだけど、今FirebaseでWebアプリを作ろうとしている。トランプのドボンが出来るアプリ。
ステップはこんな感じと思う。
1.FirebaseのWebアプリのサンプルの確認
2.Firebaseから手札のデータを取得して表示する
3.選んだ手札をFirebaseDBに登録する
4.新しいゲームのボタンを押したら、カードをランダムに配る
5.順番に手札を台札に乗せる
6.手札が出せる札かチェックする
7.得点計算をする
ちょっとずつ更新する予定
1.FirebaseのWebアプリのサンプルの確認
Firebaseのチャットアプリのサンプルの動作や作りを一通り眺める。
2.Firebaseから手札のデータを取得して表示する
これはコードを眺めて対応するところをコピペした。
Firebaseの方で手札データを登録して、それを表示させる。
3.選んだ手札をFirebaseに登録する
ここで、bindしないとつらそうな気がしたので、ちょっとググる。
これを参考にVueを導入してみた。まずは、クライアントでrequireを使うので、browserifyが必要らしい。
>npm install -g browserify
>npm install --save promise-polyfill
>browserify ./public/scripts/main.js -o ./public/scripts/bundle.js
>firebase serve
で、これ以降はmain.jsではなく、browserifyで生成したbundle.jsをindex.htmlで読み込んで使う。
動かしてみたがエラー。メッセージからすると、エレメントが見つからないからエラー。2行目は分かりにくいけどたぶん関係ない。
[Vue warn]: Cannot find element: #cards
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
(found in <Root>)
確認して、bundle.jsを作り直して、もう一度表示。
やっぱりまだエラー。
これVueのバージョンアップに絡んでいそうなので、ちょっとここでいったんストップ。同じ感じで表示だけ行う処理を組み込んだ。
2017/04/23 更新
Vueはちょっとお預け。
データはまずは以下の形で作っている自分のカードを表示して、こんな感じにした。
root
┣messages
┣current┃┣mark
┃┗num
┗cards
┗id
┃┣mark
┃┗num
┣id
手札を出す=cardsから1つ削除&currentを更新。
main.js →browserify → bundle.js
this.cardsRef.child(key).remove().then(function() {
// Clear message text field and SEND button state.
Dobon.resetMaterialTextfield(this.messageInput);
this.toggleButton();
}.bind(this)).catch(function(error) {
console.error('Error writing new message to Firebase Database', error);
});
このコードでカードデータを削除。
Dobon.prototype.loadCards = function() {
// // Reference to the /messages/ database path.
this.cardsRef = this.database.ref('cards');// Make sure we remove all previous listeners.
this.cardsRef.off();var remCard = function(data) {
var val = data.val();
var elm = document.getElementById(data.key);
elm.parentNode.removeChild(elm);
}.bind(this);
// Loads the last 12 messages and listen for new ones.
var setCard = function(data) {
var val = data.val();
this.displayCard(data.key, val.mark, val.num);
}.bind(this);
this.cardsRef.limitToLast(12).on('child_added', setCard);
this.cardsRef.limitToLast(12).on('child_changed', setCard);
this.cardsRef.limitToLast(12).on('child_removed', remCard);
};
上記はDB変更時のイベントの設定。元々のサンプルはchild_added, child_changedだけだけど、child_removedを追加。
次に、FirebaseのFunctionを追加する。
index.js
exports.makeUppercase = functions.database.ref('/cards/{pushId}')
.onWrite(event => {// Only edit data when it is deleted
if *1
|| event.data.exists()) {
console.log('end trigger');
return;
}
// Grab the current value of what was written to the Realtime Database.
var mark = event.data.previous.val().mark;
var num = event.data.previous.val().num;
event.data.ref.parent.parent.child('current').child('mark').set(mark);
return event.data.ref.parent.parent.child('current').child('num').set(num);
});
previousにあって、今のデータにない=削除されたデータ。
で、上記の処理でノードを更新。
Realtime Database Triggers | Firebase
このサイトがとても詳しく書かれていてありがたい。
Read and Write Data on the Web | Firebase
2017/04/24 追記
4.新しいゲームのボタンを押したら、カードをランダムに配る
カードデータを作成して、ランダムにシャッフルする処理は以下のような感じ。
ランダムに並べ替えるアルゴリズムがあるそうで助かりました。
このサイトが見ててきれい。
function getCards(){
var marks = ["spade","clover","heart","diamond"];
var cards = [];
for(var i = 0; i < marks.length; i++){
for(var j = 1; j <= 13; j++){
var node = {"mark":marks[i], "num": j};
cards.push(node);
}
}
return cards;
}
function shuffle(array) {
var n = array.length, t, i;while (n) {
i = Math.floor(Math.random() * n--);
t = array[n];
array[n] = array[i];
array[i] = t;
}return array;
}var test_array = getCards();
var cards =shuffle(test_array);
console.log(cards.length);
console.log(cards);
2017/04/25 追記
4.新しいゲームのボタンを押したら、カードをランダムに配る
の続き。
Firebaseで手札を配る際に、RESTでGETしたら手札を配るようにしたいため、FirebaseのFunctionsという機能を利用する。これはREST APIっぽいことが出来るみたい。index.jsでの書き方はこんな感じ。
exports.date = functions.https.onRequest((req, res) => {
// ...
});
ここで、dataがHTTPのTriggerの名前。reqからJSONデータを取ることもできるらしい。詳しくは公式をご覧ください。
HTTP Triggersの書き方はこんな感じ。
Databaseへの登録の書き方はこんな感じ。
Read and Write Data on the Web | Firebase
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.newgame = functions.https.onRequest((req, res) => {
var getCards = function(){
var marks = ["spade","clover","heart","diamond"];
var cards = [];
for(var i = 0; i < marks.length; i++){
for(var j = 1; j <= 13; j++){
var node = {"mark":marks[i], "num": j};
cards.push(node);
}
}
return cards;
};
var shuffle = function(array) {
var n = array.length, t, i;while (n) {
i = Math.floor(Math.random() * n--);
t = array[n];
array[n] = array[i];
array[i] = t;
}
return array;
};
var newGame = function(cards){
for(var i = 0; i < cards.length; i++){
// A card entry.
var cardData = {
mark: cards[i].mark,
num: cards[i].num
};var newCardKey = admin.database().ref().child('cards').push().key;
var updates = {};
updates['/cards/' + newCardKey] = cardData;
admin.database().ref().update(updates);
}
};
newGame(shuffle(getCards()));
res.send(200);
});
JSっぽくない書き方になってるのでどう直したらいいか教えてください!
これをdeployして、GETリクエストを投げたらこんな感じでデータが出来ました。
うれしい、、、。
2017/04/27 追記
カードを配る前に今のカードを全部削除する処理を追加した。
上の処理を1行追加してこんな感じ。
admin.database().ref().child('cards').remove();
newGame(shuffle(getCards()));
res.send(200);
removeで配下のものを全部削除してくれるらしい。
2017/04/28 追記
ユーザーごとにカードを配る処理を実装したが、アクティブユーザー一覧が取れなさそうなので、ゲームの流れとしては、開始を呼びかける→他のユーザーが参加表明→ゲーム開始という流れにして、表明のタイミングでUIDを登録するようにする。
参加表明はクライアントからIDを送る。ちょっと微妙な気もするが、HTTP Requestのトリガーではuidが取れなさそう。
Dobon.prototype.enrollGame = function() {
this.playersRef = this.database.ref('players');
// Make sure we remove all previous listeners.
var user = firebase.auth().currentUser;
var data = {
uid: user.uid
,name: user.displayName
};
this.playersRef.push(data);
};
ゲーム開始時の処理も修正。カードをユーザーごとに配る。
ユーザーIDのJSONが配列じゃなかったから結構はまってしまったし、探し方が悪かった。やりたいことをきっちり言語化すべきだった。ここだと、JSONのキーを取得して、そこからそれぞれの要素を取得する、だった。
でできちゃうんだ、、、。
2017/05/05 追記
ちょっと飽きたのでレイアウトをいじることにした。
HTML見てると、classにmdl~とかいう記述があったけど、どうもそれは、Material Design LiteというGoogle謹製のライブラリらしい。これを使うと、WebでもAndroid同様に面とインクのメタファーに倣ったデザインができるらしい。ちょっと触ってみたけど、影を出すのが精いっぱいでリップルは出せなかった。とりあえず、台とカードは出た。
クラスを複数指定することによりデザインをきれいに出来る。でもそもそもCSSが全然わかってないし、MDLもよく分かってないのでなんでこんな書き方になるのか説明できない。1行めはグリッド表示用、2行目が各カードの親DIVのクラス。
<div class="mdl-grid">
<div class="mdl-play-card mdl_supporting-text mdl-color--white mdl-shadow--2dp">
概要や使い方は上記で。
色の指定はこのサイトがいろいろと見本を載せてくれていて良い。
2017/05/27 追記
カードを引く処理を以下の流れで想定していたがうまく行かなかった。
JavascriptでuidをPOSTして、Functionsでuidを受け取って、カードを1件取得して、uidをキーにカードをpushし直す。
問題は2つあった。1.クロスドメインNG。2.1件データが取得できない。
まず1.についてはHostingとFunctionsのドメインが異なるため。ただGETでは問題ないため、メソッドをGETに変更し、クエリにuidを含めるよう変更。
2.については何か書き方間違えているかもだけど分からないからとりあえず力技でしのいだ。
exports.drawcard = functions.https.onRequest((req, res) => {
admin.database().ref().child('cards').once('value').then(function(snapshot) {
var allData = snapshot.val();
var keys = Object.keys(allData);
var val = allData[keys[0]];
var uid = req.query.uid;
var data = {"mark": val.mark, "num": val.num};
admin.database().ref('hands').child(uid).child('cards').push(data);
admin.database().ref('cards').child( keys[0]).remove();
});res.status(200).send();
});
2017/06/18 追記
8を出したときにマークを選ぶダイアログを表示させるようにした。
Dialogはまだ一般的じゃないみたいだけどChrome限定だから良しとする。
マークを選ぶのはラジオボタンから。
最近はブラウザでdialogがサポートされるようになってきている。
HTML DOM Dialog showModal() Method
ドキュメントとかでPromiseというのがでてきたけど、まだちょっと内容が分かってない。
まだ機能足りてないけどとりあえずGitHubに上げました。
*1:!event.data.previous.exists(
こんなアプリがあるのね。
今まで全く知らなかったけどこんなアプリがあるんだと思った話。
マチコミというAndroid、iPhoneアプリがあって、幼稚園とかと保護者の情報共有をするアプリ。
多分、元々Webサービスがあって、それをionicとかのフレームワークで移植してアプリとして公開してるんだと思う。
ユーザーは施設運営者と保護者の2パターン。施設運営者は事前にマチコミに登録が必要と思われる。で、学年ごととかでグループを作成する。保護者はユーザー登録をした後で、施設運営者から学年ごとに振られるIDを教えてもらい、こどもの教室を登録する。
SNS、ニュースなども見ることが出来て、広告が入っている。
収入は広告らしい。利用者はアンケートに答える必要があるらしく、これもデータを売ってたりするのかな。意外とダウンロードされててびっくり。
クローズなコミュニティ向けのサービスって意外と需要があるのね。
Androidでテストを導入したい。Contextの受け渡し。
Androidでちっちゃいアプリを作っている。機能を追加する前にユニットテストを組み込みたいと思ってお勉強中。
Googleのサンプルに従って導入しているところだけどデータアクセスのところでつまづいた。辞書機能を作るのにSQLiteを使っているのだが、DBアクセスにContextが必要。ただDBアクセス部はコードを分離したい、、、。ちょっと探してみると2つくらい選択肢があるっぽい。1、Dagger2というDIライブラリを使う。2、Contextをアプリ内で参照できるようにする。
1、を選ぶと収集がつかなくなりそうだったので、2、のContextをアプリ内で参照できるように修正。テスト用のコードとリリースとで実装を分けて、テストしやすくなりそうな気がする。
ありがとうございました。