Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8920 articles
Browse latest View live

RESTサーバのMockを簡単に作る方法

$
0
0

node.jsのjson-serverを利用する

json-serverというライブラリが素敵な感じ。

導入

ローカルに導入するとして。。。
1. 展開場所確保
npm init
2. インストール
npm install json-server
3. キック用にpackage.jason書き換え

package.json
"scripts":{"test":"echo \"Error: no test specified\"&& exit 1","json-server":"json-server"},

4.mockデータファイル用意。例えばこんな感じのjsonだとするとtop下の階層がエンドポイントになる感じ。

mockdata.json
{"movies":[{"title":"マトリックス","rating":9.1},{"title":"タイタニック","rating":8.8}],"user":[{"name":"fukuhara","age":35,"sex":"male"},{"name":"sugiyama","age":42,"sex":"male"}]}

5.mockサーバ起動
npm run json-server -- --watch mockdata.json

npm WARN lifecycle The node binary used for scripts is C:\Program Files (x86)\Nodist\bin\node.exe but npm is using C:\Program Files (x86)\Nodist\v-x64\10.15.3\node.exe itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.

> restmock@1.0.0 json-server H:\work\restmock
> json-server "--watch" "mockdata.json"


  \{^_^}/ hi!

  Loading mockdata.json
  Done

  Resources
  http://localhost:3000/movies
  http://localhost:3000/user

  Home
  http://localhost:3000

  Type s + enter at any time to create a snapshot of the database
  Watching...

6.Curlでちょっと叩いてみる

curl -X GET "http://localhost:3000/movies"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   126  100   126    0     0    126      0  0:00:01 --:--:--  0:00:01   473[
  {
    "title": "マトリックス",
    "rating": 9.1
  },
  {
    "title": "タイタニック",
    "rating": 8.8
  }
]

できること

登録/更新/削除操作ができる

登録

curl -X POST -H "Content-Type: application/json" -d '{
  "title": "test1",
  "id":1
}' "http://localhost:3000/movies"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    65  100    33  100    32     33     32  0:00:01 --:--:--  0:00:01   277{
  "title": "test1",
  "id": 1
}

curl -X GET "http://localhost:3000/movies/"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   169  100   169    0     0    169      0  0:00:01 --:--:--  0:00:01   722[
  {
    "title": "マトリックス",
    "rating": 9.1
  },
  {
    "title": "タイタニック",
    "rating": 8.8
  },
  {
    "title": "test1",
    "id": 1
  }
]

curl -X GET "http://localhost:3000/movies/1"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    33  100    33    0     0     33      0  0:00:01 --:--:--  0:00:01   150{
  "title": "test1",
  "id": 1
}

  • 更新系作業時は「id」ってフィールド無いとダメっぽい。
  • 逆に「id」フィールドさえあれば他はチェックしてないくさい

更新されたmockデータのjsonをスナップショットとして保存できる

ctrl+s

  Saved snapshot to db-1582737815807.json

NuxtとNode.jsサーバーでSlack通知実装

$
0
0

設計

  • NuxtベースアプリケーションからSlackに通知する機能を実装したい。
  • 定期的な自動再送機能もほしい。
  • トークンをNuxt側に置くのはちょっと...

そんな要件を満たすためには、Nuxtからサーバーを経由するのもありかなという感じで作成。

必要なもの

  1. Nuxtアプリケーション
  2. Node.js (当方Version 13.2.0)
  3. 設定済みSlack API (トークンとBot Token Scopesにてchat:write権限が必要。#generalに参加済み)

Nuxtでの実装

求められるものは
1. メッセージ入力
2. 宛先入力
3. 再送間隔 or 再送しないの入力
4. 設定内容のPOST

とりあえず上記の4つを実装すれば最低限機能は満たせそうなので書いていく。
見た目はこの際捨て置く。

またquerystringを使っているので未インストールなら

npm install --save querystring

以下、コード。

<template><divclass="create-post"><inputtype="text"v-model="message"id="message"><inputtype="text"v-model="channel"id="channel"><inputtype="text"v-model="schedule"id="schedule"><buttonv-on:click="send">送信
    </button></div></template><script>importquerystringfrom"querystring"exportdefault{data(){return{message:'',channel:'',schedule:'none'}},methods:{asyncsend(){letjson_data={message:this.$data.message,channel:this.$data.channel,schedule:this.$data.schedule};constresponse=awaitthis.$axios.$post('/test/rest',querystring.stringify(json_data));}}}</script><stylescoped>.create-post{width:30%;height:200px;padding-left:200px;}#message{margin-top:10%;}#channel{margin-top:10%;}#schedule{margin-top:10%;}</style>

そうするとこのようなページが出来上がる

スクリーンショット 2020-02-25 17.23.36.png

今回、NuxtからサーバーにPOSTするために@nuxtjs/axiosを使用している。
詳しくはご自分で調べてほしいのだが、使用する際、nuxt.confing.jsにて以下の文を編集・追加している。

modules:['@nuxtjs/axios',],axios:{proxy:true},proxy:{'/test/':{target:'http://0.0.0.0:3000/',pathRewrite:{'^/test/':''}}},

このように設定することでCORSを簡単に解決することができる。
クロスオリジンリソースを共有するには?

サーバーの用意

サーバーでは次のような処理を行いたい。
1. HTTP Requestを受けとる
2. 中身のデータからメッセージとあて先、定期再送情報を取り出す
3. それに合わせてSlackに通知する

それではコードを書いていく。
これにあたって、以下の記事やサイトを参考にさせていただいた。
Nodejs HTTP/HTTPS サーバーとクライアントのコーディングパターン
【Node.js】Slack APIを使用してメッセージを送信する
Node.jsで定期実行メモ
node-cron npm
node-fetch npm

consthttp=require('http');constfetch=require('node-fetch');constcron=require('node-cron');constqueryString=require("querystring");constStringDecoder=require("string_decoder").StringDecoder;constDecoder=newStringDecoder('utf8');letserver=http.createServer();asyncfunctionpostToSlack(token,msg,channel){const{WebClient}=require('@slack/web-api');constclient=newWebClient(token);constparams={channel:channel,text:msg};awaitclient.chat.postMessage(params);}lettasks=[];server.on('request',function(req,res){req.on('data',asyncfunction(chunk){letjson=JSON.parse(JSON.stringify(queryString.parse(Decoder.write(chunk))));if(json.schedule==='none'){//スケジュール指定がない場合awaitpostToSlack('your token here',json.message,json.channel);}else{//ある場合lettask=cron.schedule(json.schedule,async()=>{awaitpostToSlack('your token here',json.message,json.channel);});tasks.push(['start',task]);}letreturn_json={state:true,};res.writeHead(200,{'Content-Type':'application/json'});letreplyData=queryString.stringify(return_json);res.write(replyData);res.end();});});server.listen(3000);

実際に試す

Nuxtのページで文章とあて先を入力して送信。

スクリーンショット 2020-02-25 18.23.34.png

そしてSlackに通知が来た。

スクリーンショット 2020-02-25 18.26.59.png

定期実行も確認

スクリーンショット 2020-02-25 18.30.11.png
スクリーンショット 2020-02-25 18.31.28.png

以上、実装完了とす。

感想

エラー処理を全く実装していないので怖い。
APIが用意されていると楽。
あとはSlackに限らず通知できるように拡張できそう。

おわりに

お読みいただきありがとうございました。なにか間違いがございましたらコメントへよろしくお願いします。

Windowsでnpm startしたときに「内部コマンドまたは外部コマンド~」と出てくる現象と戦う

$
0
0

1. 問題発生

Webpackをglobalにインストールすることが非推奨であることを知り、

> npm remove -g webpack webpack-cli

としたところ、既存のプロジェクトにて

> npm run build
'webpack' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

と出てしまった。
package.json の scripts はこんな感じ

package.json
"scripts":{"build":"webpack --mode development",}

ひとまずこれは再度グローバルにインストールすることで回避していたが、
ある日、Reactの勉強を始めようとしたところ、

> npx create-react-app pwa-sample
> cd pwa-sample
> yarn start
'react-scripts' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

と、こちらもNG。
どういうことなの?

2. 試したこと

  1. node_modules と packege-lock.json を削除して npm install・・・ NG
  2. コマンドプロンプトやPowerShellを管理者で起動して ・・・ NG
  3. node_modules/.bin の中に webpack.cmd(react-scripts.cmd) が存在するか確認 ・・・ 存在する
  4. npx コマンドでstart ・・・ OK
  5. package.json 内の webpack や react-scripts コマンドのパス書き換え ・・・ OK

つまり、 node_modules/.bin のパスを理解していないということか?
この時点での解決策は以下になるが、これではMacやLinuxと共通化ができずに面倒くさいしイマイチ美しくない。

package.json
"scripts":{"build":"node_modules\\.bin\\webpack.cmd --mode development"//webpackの場合"start":"node_modules\\.bin\\react-scripts.cmd start"//react-scriptの場合}

通常、私はプロジェクト用のディレクトリは D:\projectsというパスに設置しているが、
ちょっと気分を変えて C:\Users\hogehoge\Documents(マイドキュメント)にて作業してみた。

>yarn start
yarn run v1.22.0
$ react-scripts start
Starting the development server...
Compiled successfully!

あれ?通った?

3. 原因判明

CドライブとDドライブで何が違うんだろう・・・とパーミッションやnpmグローバルディレクトリを覗いていたがよくわからない。
C:\Users\hogehoge\AppData\Roaming\npm-cache\_logsも見るがパスは通っているように見える。
(PATHの行に プロジェクトディレクトリ/node_modules/.bin が含まれている)

後は何が問題なのだろうか、ドライブ間で何か特別な操作をしただろうか?

1つだけあった
それは以前に 大文字小文字を区別する設定をしていたことだった。

# 管理者で実行
> fsutil.exe file SetCaseSensitiveInfo D:\projects
ディレクトリ D:\projects の大文字と小文字を区別する属性が有効になっています。

試しにこれを解除して再チャレンジ

# 管理者で実行
> fsutil.exe file SetCaseSensitiveInfo D:\project disable
ディレクトリ D:\project の大文字と小文字を区別する属性が無効になっています。

# 通常ユーザーで実行
> yarn start
yarn run v1.22.0
$ react-scripts start
Starting the development server...
Compiled successfully!

問題なく通りました。
もちろんwebpackもOK。

どうやら、webpack や react-scripts コマンドは webpack.cmd や react-scripts.cmdではなく、webpack.CMD や react-scripts.CMDを探していた、ということらしい。

安易にSetCaseSensitiveInfoを有効にしないように注意しましょう。
ただ、これが有効になっていないと困るケースもあるので悩ましい・・・
状況により中段に書いた node_modules\\.bin\\webpack.cmdと併用することになりそう。

AIに歌を聞かせた。そして彼は笑顔😃になった。(COTOHA-感情分析API)

$
0
0

概要

歌を聞かせた。
彼はその歌を聴き、
Positiveな感情になると笑い😃、
Negativeな感情になると怒り😠、
Neutralな感情になると。。。😐

(COTOHA感情分析APIを使って、歌の歌詞からどのような感情をくみ取るのかを試した。COTOHA API for Developersに登録してすぐに、感情分析APIを試せるようWEBアプリも作った。)

結果

とりあえず歴代CDシングル売り上げ枚数ランキングを聞かせた。
「1位から順に!カウントアップ!👆」

1.(457.7万枚)およげ!たいやきくん

😠
"sentiment": "Negative"
"score": 0.5

Response
{"result":{"sentiment":"Negative","score":0.5,"emotional_phrase":[{"form":"こころがはずむ","emotion":"喜ぶ"},{"form":"たのしい","emotion":"P"},{"form":"おもい","emotion":"N"},{"form":"ひろいぜ","emotion":"PN"},{"form":"いじめられる","emotion":"N"},{"form":"もがいて","emotion":"N"},{"form":"こげある","emotion":"N"},{"form":"うまそう","emotion":"P"}]},"status":0,"message":"OK"}

2.(325.6万枚)女のみち

😠
"sentiment": "Negative"
"score": 0.2011977885862898

Response
{"result":{"sentiment":"Negative","score":0.2011977885862898,"emotional_phrase":[{"form":"みちならば暗い","emotion":"N"},{"form":"恋","emotion":"好ましい,切ない"},{"form":"幸せ","emotion":"P"},{"form":"つらい","emotion":"N"},{"form":"泣","emotion":"喜ぶ,悲しい,切ない"},{"form":"すがって泣いたうぶな","emotion":"PN"},{"form":"いけない","emotion":"N"},{"form":"みちならば","emotion":"PN"},{"form":"捨てた","emotion":"N"},{"form":"いじめる","emotion":"N"}]},"status":0,"message":"OK"}

3.(313.2万枚)世界に一つだけの花

😃
"sentiment": "Positive"
"score": 0.08995079776388203

Response
{"result":{"sentiment":"Positive","score":0.08995079776388203,"emotional_phrase":[{"form":"うれしそうな","emotion":"P"},{"form":"笑顔","emotion":"安心"},{"form":"好み","emotion":"好ましい"},{"form":"困った","emotion":"N"},{"form":"迷","emotion":"不安"},{"form":"笑い","emotion":"喜ぶ"},{"form":"もともと特別な","emotion":"PN"},{"form":"ひとそれぞれ","emotion":"PN"},{"form":"きれいだ","emotion":"P"},{"form":"争う","emotion":"N"},{"form":"誇らしげ","emotion":"P"},{"form":"咲かせる","emotion":"P"},{"form":"一生懸命","emotion":"PN"},{"form":"頑張って","emotion":"P"},{"form":"仕方ないね","emotion":"N"},{"form":"色とりどり","emotion":"P"},{"form":"気づかない","emotion":"N"},{"form":"小さい","emotion":"PN"},{"form":"同じものはない","emotion":"PN"}]},"status":0,"message":"OK"}

4.(293.6万枚)TSUNAMI

😠
"sentiment": "Negative"
"score": 0.10121480950551423

Response
{"result":{"sentiment":"Negative","score":0.10121480950551423,"emotional_phrase":[{"form":"愛","emotion":"安心,好ましい"},{"form":"愛しい","emotion":"P"},{"form":"怯えてる","emotion":"不安"},{"form":"悲しみ","emotion":"悲しい"},{"form":"恋","emotion":"好ましい,切ない"},{"form":"好きな","emotion":"P"},{"form":"戸惑う","emotion":"N"},{"form":"泣いた","emotion":"N"},{"form":"涙","emotion":"喜ぶ,不安,悲しい,切ない"},{"form":"微笑","emotion":"安心"},{"form":"弱気な","emotion":"N"},{"form":"すがる","emotion":"N"},{"form":"涙もろい","emotion":"PN"},{"form":"清か","emotion":"P"},{"form":"魔性","emotion":"N"},{"form":"彷徨う","emotion":"N"},{"form":"枯れる","emotion":"N"},{"form":"素直に","emotion":"P"},{"form":"侘しさ","emotion":"PN"},{"form":"鏡のような","emotion":"P"},{"form":"深い","emotion":"PN"},{"form":"気付いてる","emotion":"P"},{"form":"張り裂けそうな","emotion":"N"}]},"status":0,"message":"OK"}

5.(291.8万枚)だんご3兄弟

😠
"sentiment": "Negative"
"score": 0.06456371454543736

Response
{"result":{"sentiment":"Negative","score":0.06456371454543736,"emotional_phrase":[{"form":"かたくな","emotion":"不安"},{"form":"ささって","emotion":"P"},{"form":"まれ","emotion":"PN"},{"form":"たくさん","emotion":"PN"},{"form":"こげ","emotion":"N"},{"form":"かたくなりました","emotion":"PN"}]},"status":0,"message":"OK"}

6.(289.5万枚)君がいるだけで

😃
"sentiment": "Positive"
"score": 0.009547567550434202

Response
{"result":{"sentiment":"Positive","score":0.009547567550434202,"emotional_phrase":[{"form":"憧れ","emotion":"P"},{"form":"笑顔","emotion":"安心"},{"form":"くやしい","emotion":"N"},{"form":"何より大切な","emotion":"P"},{"form":"涙","emotion":"喜ぶ,切ない,不安,悲しい"},{"form":"儚い","emotion":"PN"},{"form":"強く","emotion":"PN"},{"form":"気付かせてくれたね","emotion":"P"},{"form":"ありがちな","emotion":"PN"},{"form":"つい引き込まれ","emotion":"P"},{"form":"弱さ","emotion":"N"},{"form":"忘れてた","emotion":"PN"},{"form":"もっと素直になれなかった","emotion":"N"},{"form":"わかって","emotion":"P"}]},"status":0,"message":"OK"}

7.(282.2万枚)SAY YES

😠
"sentiment": "Negative"
"score": 0.2490204168421342

Response
{"result":{"sentiment":"Negative","score":0.2490204168421342,"emotional_phrase":[{"form":"愛","emotion":"好ましい,安心"},{"form":"愛してる","emotion":"P"},{"form":"恋","emotion":"好ましい,切ない"},{"form":"寂しい","emotion":"N"},{"form":"切なさ","emotion":"PN"},{"form":"迷","emotion":"不安"},{"form":"ワガママ","emotion":"N"},{"form":"余計な","emotion":"N"},{"form":"何げなく暮らさない","emotion":"PN"},{"form":"あふれてる","emotion":"N"},{"form":"勝てない","emotion":"N"},{"form":"消えない","emotion":"P"}]},"status":0,"message":"OK"}

8.(276.6万枚)Tomorrow never knows

😃
"sentiment": "Positive"
"score": 0.007165278534051281

Response
{"result":{"sentiment":"Positive","score":0.007165278534051281,"emotional_phrase":[{"form":"愛した","emotion":"P"},{"form":"悲しい","emotion":"N"},{"form":"孤独な","emotion":"PN"},{"form":"寂しい","emotion":"N"},{"form":"愛される喜び","emotion":"P"},{"form":"消えた帰らぬ","emotion":"P"},{"form":"すれ違う","emotion":"N"},{"form":"無邪気に","emotion":"P"},{"form":"裏切れる","emotion":"N"},{"form":"欲しがっていた","emotion":"P"},{"form":"分かり合えた","emotion":"P"},{"form":"夢中で駆け抜ける","emotion":"P"},{"form":"勝利","emotion":"P"},{"form":"敗北もない","emotion":"P"},{"form":"忘れてゆく","emotion":"PN"},{"form":"避けて通れない","emotion":"P"},{"form":"果てしない","emotion":"P"},{"form":"優しさ","emotion":"P"},{"form":"長い","emotion":"PN"},{"form":"癒える","emotion":"P"},{"form":"少し","emotion":"PN"}]},"status":0,"message":"OK"}

9.(258.8万枚)ラブ・ストーリーは突然に

😃
"sentiment": "Positive"
"score": 0.060429544970368884

Response
{"result":{"sentiment":"Positive","score":0.060429544970368884,"emotional_phrase":[{"form":"もっと好き","emotion":"P"},{"form":"もう心揺れたりしないで切ない","emotion":"PN"},{"form":"揺れたり","emotion":"興奮"},{"form":"いい","emotion":"P"},{"form":"分からない","emotion":"N"},{"form":"消えてゆく","emotion":"N"},{"form":"あんまりすてきだ","emotion":"P"},{"form":"甘く","emotion":"PN"},{"form":"やわらかく","emotion":"PN"},{"form":"心が動いた","emotion":"P"},{"form":"忘れない","emotion":"PN"},{"form":"心揺れたりしないで","emotion":"PN"}]},"status":0,"message":"OK"}

10.(248.9万枚)LOVE LOVE LOVE

😃
"sentiment": "Positive"
"score": 0.24542462771149284

Response
{"result":{"sentiment":"Positive","score":0.24542462771149284,"emotional_phrase":[{"form":"愛","emotion":"安心,好ましい"},{"form":"愛してる","emotion":"P"},{"form":"すごく好きな","emotion":"P"},{"form":"涙","emotion":"喜ぶ,切ない,不安,悲しい"},{"form":"すっごく","emotion":"PN"},{"form":"うまく","emotion":"P"},{"form":"願う","emotion":"P"},{"form":"少しずつ思い出になって","emotion":"P"}]},"status":0,"message":"OK"}

番外編(何となく気になった曲)

大きなのっぽの古時計

😃
"sentiment": "Positive"
"score": 0.30601098537139876

Response
{"result":{"sentiment":"Positive","score":0.30601098537139876,"emotional_phrase":[{"form":"うれしい","emotion":"P"},{"form":"かなしい","emotion":"N"},{"form":"のっぽの","emotion":"PN"},{"form":"ごじまんの","emotion":"PN"},{"form":"きれいな","emotion":"P"}]},"status":0,"message":"OK"}

千の風になって

😃
"sentiment": "Positive"
"score": 0.14780720679775194,

Response
{"result":{"sentiment":"Positive""score":0.14780720679775194,"emotional_phrase":[{"form":"泣かないでください","emotion":"P"},{"form":"きらめく","emotion":"P"},{"form":"見守る","emotion":"P"}]},"status":0,"message":"OK"}

パプリカ

😃
"sentiment": "Positive"
"score": 0.01385186275421083

Response
{"result":{"sentiment":"Positive","score":0.01385186275421083,"emotional_phrase":[{"form":"泣いてた","emotion":"N"},{"form":"喜び","emotion":"P"},{"form":"晴れた","emotion":"P"},{"form":"燻り","emotion":"N"},{"form":"慰める","emotion":"P"},{"form":"まれ","emotion":"PN"}]},"status":0,"message":"OK"}

WEBアプリ化

COTOHA API for Developersに登録した人限定ではあるが、同じ遊びが出来るようにした。(簡単に登録可)
以下URLで遊ぶには、登録後に取得できる、Client IDClient secretが必要。
https://cotoha-demo.now.sh/

2020-02-26_17h32_04.png

Client IDClient secretは、画面リフレッシュのたびに入力しなおすのは面倒。なので、ブラウザのlocalstrageにて情報を保持している。

技術

アプリ化に伴い、以下の技術を使用。

  • Nuxt.js【JavaScript framework】
  • ant-design-vue【Css framework】
  • Now【Deploy】
  • Express【API】

server.js
server.js
constexpress=require('express')constrequest=require('request')constapp=express()// serverconstport=process.env.PORT||5000app.listen(port,err=>{if(err)throwerrconsole.log(`> Ready On Server http://localhost:${port}`)})// APIapp.get('/getToken',function(req,res,next){(()=>{returnnewPromise(r=>{if(req.query.ClientID&&req.query.Clientsecret&&req.query.AccessTokenPublishURL){constAccessTokenPublishURL=req.query.AccessTokenPublishURLconstClientID=req.query.ClientIDconstClientsecret=req.query.Clientsecretconstheaders={'Content-Type':'application/json'}constdata={'grantType':'client_credentials','clientId':ClientID,'clientSecret':Clientsecret}constoptions={url:AccessTokenPublishURL,method:'POST',headers:headers,json:data}request(options,(error,response,body)=>{returnr(body)})}else{r('Parameter is incorrect')}})})().then(data=>{res.send(data)}).catch(error=>{res.send(error)})})app.get('/cotohaApiSimilarity',function(req,res,next){(()=>{returnnewPromise(r=>{if(req.query.sentence&&req.query.token){constsentence=req.query.sentenceconsttoken=req.query.tokenconstheaders={'Content-Type':'application/json;charset=UTF-8','Authorization':`Bearer ${token}`}constdata={'sentence':sentence,}constoptions={url:'https://api.ce-cotoha.com/api/dev/nlp/v1/sentiment',method:'POST',headers:headers,json:data}request(options,(error,response,body)=>{returnr(body)})}else{r('Parameter is incorrect')}})})().then(data=>{res.send(data)}).catch(error=>{res.send(error)})})

あとがき

他のAPIと組み合わせれば、話す内容によって表情を変えるbotが簡単に出来そう。

あと要約(β)API・キーワード抽出API・言い淀み除去(β)・音声認識誤り検知(β)・音声認識API ここら辺のを使って、音声データから議事録を作るWEBアプリ(WEBサービス)作りたい。。。時間が欲しい。。。

おまけ

Q. さて、これは誰が歌っているのさくらの結果でしょうか???↓

さくら

😃
"sentiment": "Positive",
"score": 0.0010436678748683892,

Response
{"result":{"sentiment":"Positive","score":0.0010436678748683892,"emotional_phrase":[{"form":"また気になる","emotion":"PN"},{"form":"忘れた","emotion":"PN"},{"form":"淡い","emotion":"PN"},{"form":"意味なく","emotion":"N"},{"form":"変わらない","emotion":"PN"},{"form":"甦る","emotion":"PN"},{"form":"長い","emotion":"PN"},{"form":"たわいない","emotion":"PN"},{"form":"気付けば","emotion":"P"},{"form":"誘われ","emotion":"P"},{"form":"すり抜けた","emotion":"PN"},{"form":"分からなかった","emotion":"N"},{"form":"若かった","emotion":"P"},{"form":"香る","emotion":"P"},{"form":"暖かい","emotion":"P"},{"form":"こぼれる","emotion":"N"},{"form":"消えてしまうよ","emotion":"N"}]},"status":0,"message":"OK"}

AWS Lambda入門(Node編)

$
0
0

概要

  • ServerlessFrameworkを使ってLambda関数を作り、ローカルで動作確認したあとにAWSにデプロイしてアクセスするところまでやってみます

Lambdaとは

特徴

  • 通常のアプリケーションはサーバにデプロイし稼働させることでアクセスすることができますが、当然サーバが止まっていたら利用することはできません
  • Lambdaはサーバーレスに分類されるサービスで、アクセスがあるとそのつど起動し処理が実行され終了すると停止します
  • つまりサーバーレスはサーバの死活監視のようなことをする必要がなく、また課金単位も実行時間単位なので金銭面でもお得といった特徴があります

関数の作成

ServerlessFrameworkのインストール

  • グローバルにインストール
npm i -g serverless
  • 動作確認
sls -v

ServerlessFrameworkのコマンドがslsserverlessのどちらでも動きます

  • 以下のような内容が表示されればOKです
Framework Core: 1.64.1
Plugin: 3.4.1
SDK: 2.3.0
Components Core: 1.1.2
Components CLI: 1.4.0

雛形の生成

  • ServerlessFrameworkの機能でLambda用の関数や設定ファイルの雛形を生成します
    • 今回はaws-nodejsというtemplateを指定します
    • 他にどんなtemplateがあるかはserverless create --helpを実行すると見ることができます
mkdir sls-sample
cd sls-sample
serverless create --template aws-nodejs
  • 以下のようなファイルが生成されているはずです
% tree -a
.
├── .gitignore
├── handler.js
└── serverless.yml
  • handler.jsは今回のメインのファイルでLambdaで実行する処理を書くファイルです
  • serverless.ymlはServerlessFrameworkを使う上での設定ファイルです
  • .gitignoreはgit管理する際にServerlessFrameworkが生成する一時ファイルを管理対象外にするための記載が追加されています

関数をローカルで実行してみる

  • AWSにデプロイする前にまずはローカルで動作確認します

関数の内容を確認

  • 実行する前にhandler.jsの中身を確認しましょう
'use strict';module.exports.hello=asyncevent=>{return{statusCode:200,body:JSON.stringify({message:'Go Serverless v1.0! Your function executed successfully!',input:event,},null,2),};// Use this code if you don't use the http event with the LAMBDA-PROXY integration// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };};
  • メインの処理である関数をhelloという名前でmodule.exportsによって外部からアクセス可能にしています
  • 関数の中を見てみるとreturn文しかありません
  • statusCodebodyの2つのプロパティを持ったobjectを返却しています
    • statusCodeは実行結果を表現していて200は成功を意味しています(詳しくはググって)
    • bodyは実行結果のメイン部分でメッセージを定義したmessageと入力値をそのまま返却するinputを返しています

ローカルで実行する

  • それでは実行してみましょう
  • ServerlessFrameworkを使うとAWSにデプロイせずともLocalマシン上で動作確認ができます
sls invoke local--function hello
  • sls invokeが関数を呼び出すためのコマンドです
    • そのあとのlocalはAWSにアクセスするのではなく手元のファイルにアクセスすることを意味しています
    • 最後の--function helloは実行したい関数を指定していています
      • 先程helloという名前でexportしていることを確認しましたね
  • 実行結果はこんな感じです
{"statusCode": 200,
    "body": "{\n\"message\": \"Go Serverless v1.0! Your function executed successfully!\",\n\"input\": \"\"\n}"}
  • inputが空っぽなので適当な値を渡してみます
    • --dataで指定することで値を渡すことができます
sls invoke local--function hello --data Hello
{"statusCode": 200,
    "body": "{\n\"message\": \"Go Serverless v1.0! Your function executed successfully!\",\n\"input\": \"Hello\"\n}"}
  • Helloも取得できることが確認できました

関数をAWSにデプロイして実行してみる

  • AWSにデプロイしてアクセスしてみます

AWSにアクセスするための設定

  • AWSにデプロイするためにはキー情報の設定が必要になります
  • アクセスキーの発行についてはIAM ユーザーのアクセスキーの管理を参考に実施してください
  • 以下のコマンドのaws_access_key_idにAccess key IDを、aws_secret_access_keyにSecret access keyを入れて実行してください
    • ~/.aws/credentialsがすでに作成されている場合は上書きされるか尋ねられるので問題ないかファイルの内容を確認し対応してください
serverless config credentials --provider aws --key aws_access_key_id --secret aws_secret_access_key
  • キー情報が漏洩し悪用されると多額の請求につながる危険性があるので取り扱いには十分気をつけてください

デプロイする

  • デプロイもServerlessFrameworkの機能で簡単に実行できます
serverless deploy --region ap-northeast-1
  • --region ap-northeast-1はAWSの東京リージョンにデプロイすることを指定しています
    • serverless.ymlに記載しておけば毎回引数で設定する必要はなくなります
serverless.yml
# 抜粋provider:name:awsruntime:nodejs12.xregion:ap-northeast-1# これ
  • デプロイが成功すると以下のような出力がされます
Service Information
service: sls-sample
stage: dev
region: ap-northeast-1
stack: sls-sample-dev
resources: 6
api keys:
  None
endpoints:
  None
functions:
  hello: sls-sample-dev-hello
layers:
  None
Serverless: Run the "serverless"command to setup monitoring, troubleshooting and testing.
  • エラーが出た場合は出力されたログをよく確認して対処しましょう

デプロイした関数を実行する

  • AWSにデプロイしたLambda関数もコマンドラインから実行することができます
sls invoke --function hello --data Hello --region ap-northeast-1
  • ローカルで実行したときとの違いはlocalを指定していないだけですね
  • ローカルのときと同じように以下のレスポンスを受け取れていれば成功です
{
    "statusCode": 200,
    "body": "{\n  \"message\": \"Go Serverless v1.0! Your function executed successfully!\",\n  \"input\": \"Hello\"\n}"
}

まとめ

  • Lambda関数の基本的な扱い方について紹介しました
  • ServerlessFrameworkを使うと設定やデプロイ周りがとても簡単ですね!

花粉症対策デジタル医療相談Botの開発 GASでユーザー毎に飛散予測を定時プッシュ

$
0
0

概要

現在スギ花粉症患者さん向けに医療機関を受診しなくてもLINEで市販の医療用医薬品を使い分けることが出来るサービスを開発中です。

3月4日までCAMPFIREでテスト版ユーザー募集中です。花粉症の方は是非ご参加ください!
CAMPFIREのプロジェクトページはこちら
LINEで花粉症の重症度や最適な市販薬がわかるデジタル医療相談【アレルナビ】

以前のQiita記事はこちら
花粉症対策デジタル医療相談Botの開発 ユーザーIDと位置情報をFirestoreで管理

ユーザーが特定した地点のピンポイント花粉飛散予測を定時にプッシュする機能を開発中なのでその辺りをまとめました。

作成方法

1. 気象予測API
今回のアプリではこちらの気象APIを利用しています。
Lifesocket

2. プログラムを作成
気象データを取得する関数 getweather()

asyncfunctiongetweather(userlat,userlong){constBASE_URL="***************************************";letPATH=`/location/${userlat}/${userlong}`;//緯度経度leturl=BASE_URL+PATH+"?days=7";//7日分取得の場合letres=awaitaxios.get(url,{headers:{"Content-Type":"application/json",Accept:"application/json","x-access-key":"***********************************"}});returnres.data;}

飛散予測をプッシュする関数pushpollen()

asyncfunctionpushpollen(){//Firebaseから位置情報データを受ける    letlocationsRef=db.collection('locations');letalllocations=locationsRef.get().then(snapshot=>{snapshot.forEach(asyncdoc=>{console.log(doc.id,'=>',doc.data());letlineId=doc.data().line_user_id;letuserlat=doc.data().latitude;letuserlong=doc.data().longitude;if(lineId==null){console.log("IDなし")}else{letitem2=awaitgetweather(userlat,userlong);console.log(item2);leturl2;let[date,time]=item2.Daily[1].DateTime.split("T");if(item2.Daily[1].Index===0){url2="**********";}elseif(item2.Daily[1].Index===1){url2="**********";}elseif(item2.Daily[1].Index===2){url2="**********";}elseif(item2.Daily[1].Index===3){url2="**********";}elseif(item2.Daily[1].Index===4){url2="**********";}client.pushMessage(lineId,{type:"template",altText:"This is a buttons template",template:{type:"buttons",thumbnailImageUrl:url2,imageAspectRatio:"rectangle",imageSize:"cover",imageBackgroundColor:"#FFFFFF",title:`${date}${item2.PinpointName}の花粉飛散情報`,text:item2.Daily[1].IndexCommentary,defaultAction:{type:"uri",label:"View detail",uri:"**********"},actions:[{type:"message",label:"花粉症の重症度を判定",text:"判定"}]}})}});}).catch(err=>{console.log('Error getting documents',err);});}

3. GASで定時実行
1.GASファイルを作成
Googleにアクセスしてログイン
Google App Script
新しいプロジェクトを選択
image.png

2.コード.jsに以下のように書く

functionmyFunction(){UrlFetchApp.fetch("実行させたいURL/cron");}

3.トリガーを追加
コード編集画面のメニューにある時計マークをクリック
image.png

pushしたい時間帯に設定
(何分おきや何時間おき、毎週何曜日のこの時間なども設定できます)
image.png

4. プログラムの書き換え
以下を追記して定時に飛散予測をプッシュする関数pushpollen()が実行されるようにする。

app.get("/cron",(req,res)=>{pushpollen();});

完成図

定時に花粉予測がプッシュされました。
IMG-1039.jpg

考察

非同期処理の理解が浅かったためユーザーID毎に異なる飛散情報をプッシュさせるまでにかなり苦労しました。定時実行に関してはGASを使うと無料で簡単にCronのように時間指定で任意のURLをリクエストできちゃうのでとても便利ですね。

Auth0のログをリアルタイムでエクスポートできるようになったぞー

$
0
0

Auth0のログをBigQueryを使って永続化したかった - Qiita

こちらの記事でも書かせていただいたとおり、 Auth0 のログ保有期間 Enterprise でも30日と、比較的保有期間が短いことがネックになっていました。私が今担当しているプロジェクトでは Firebase を利用しているため、
ログを定期的に API を使って取得し、 Storage にエクスポートしています。

ふとダッシュボードを見ると、Logs の中に Streams という機能が追加されており、見ると AWS の EventBridge と Webhook が使えそうなので、早速使ってみました。

Log Streams

Log Streams - Auth0
HTTP Event Log Streams - Auth0

このドキュメントを読むとおり、イベントが起きたタイミングで指定の Webhook をコールしてくれるので、以前書いた記事のような onRun()ではなく、使い慣れた onRequest()が利用できるようになります。ログの内容は body に入ってくる仕様です。

以前の記事にも書きましたが、 Stackdriver 経由で Storage にエクスポートしているので、今回は Stackdriver にぶち込む処理をこんな感じで書いてみました。

utils/logToStackDriverLogging.ts
import{Logging}from'@google-cloud/logging';interfacefirebaseEnvConfig{databaseURL:string;storageBucket:string;projectId:string;}constfirebaseConfigRaw=process.env.FIREBASE_CONFIGasstring;constfirebaseConfig:firebaseEnvConfig=JSON.parse(firebaseConfigRaw);constlogToStackDriverLogging=async(payload:any)=>{constlogging=newLogging({projectId:firebaseConfig.gcloudProject,});// NOTE: この辺はお好みでconstlog=logging.log('auth0');constmetadata={resource:{type:'global',},};constentry=log.entry(metadata,payload);awaitlog.write(entry);};exportdefaultlogToStackDriverLogging;
src/logStream.ts
import*asfunctionsfrom'firebase-functions';import*asexpressfrom'express';import*asbodyParserfrom'body-parser';importlogToStackdriverLoggingfrom'../utils/logToStackDriverLogging';constapp=express();app.use(bodyParser.json());app.post('/auth0',async(req,res)=>{res.setHeader('Content-Type','text/plain');constauthorization=req.get('Authorization');if(!authorization){console.error('No authorization header');res.status(400).json({success:false});return;}// NOTE: ここはよしなに。if(authorization!==functions.config().auth0.token){console.error('Authorization Token missmatch');res.status(401).json({success:false});}const{body}=req;awaitlogToStackdriverLogging(JSON.stringify(body));res.status(201).json({success:true});});exportconstlogStream=functions.https.onRequest(app);

Cloud Functions にデプロイしたら、あとは Auth0 のダッシュボードから、 Logs → Streams とたどり、 Custom Webhook の設定をします。
スクリーンショット 2020-02-28 10.51.37.png

こんな感じにて Save 。
あとはログインしたりログアウトしたりします。

IMG_AA188D94A3F7-1.jpeg

いけましたね:tada:あとは Stackdriver → Storage のエクスポートは既に設定済みなので今回は説明省略しますが、ちゃんとエクスポートされていればOKです。

注意点

受け取る側の Webhook がエラーになった場合、3回は Auth0 はイベントを送ってきます。こちら側でrerunする処理を考えなくていいのでとても親切ですね!逆に言うと、エラーを吐いちゃうエンドポイントを何度も叩くことになりかねないので注意してください。

If your server is unable to receive the event, we will retry up to three times to deliver the event

それと、「リアルタイムエクスポート」って書きましたが、嘘です。たまに遅延します。多分これはどのサービスでも発生する可能性がありますが、ベストエフォートです。

Auth0 does not provide real-time logs for your tenant. While we do our best to index events as they arrive, you may see some delays.

それでは!

docker コマンドだけで起動する proxy サーバ

$
0
0

proxyという npm パッケージを node コンテナで実行するだけでお手軽に proxy サーバが起動できる。

コマンド

$docker run --rm-p 8888:3128 node npx proxy 
  • proxy コマンドはデフォルトで 3128ポートをListenするのでそれをホスト側の 8888にポートマッピングしている

ブラウザの設定(Firefoxの場合)

Image from Gyazo

  • Manual Proxy setting でホスト名を localhost、ポート番号を先のコマンドでポートマッピングしたportに設定

参考


amazon-qldb-driver-nodejsからQLDBを使う①(接続編)

$
0
0

QLDBとは

https://aws.amazon.com/jp/qldb/
Amazonが提供するフルマネージド型の台帳データベースです。
ブロックチェーンとは異なり中央集権で管理されます。

データに対する変更は全て記録され、後から確認可能なようです。
また、変更履歴が正確であることを暗号的に検証する機能を提供しています。
https://docs.aws.amazon.com/qldb/latest/developerguide/verification.html

中央集権からトランザクションの実行時にネットワーク参加者の合意を経る必要がないため、一般的なブロックチェーンベースのフレームワークより高いスループットが出るようです。

以上から、「データの信頼性やトレーサビリティを担保したいけど、分散型である必要はない」などの場合にとても魅力的な選択肢になりそうです。

amazon-qldb-driver-nodejsについて

プログラムからアクセスする場合は現状はJavaのdriverを使うのが主流なようです。
nodejs用のdriverも用意されており、今回はこちらを使ってQLDBにプログラムから接続してみます!
現在はまだpreviewで本番環境用途に使用するのは推奨していないようですので、ご注意下さい。

前提

今回のサンプルはAWSのコンソールで台帳およびいくつかテーブルを作成してある状態を前提とします。

筆者の環境では以下のチュートリアルで登録したデータ使って動作確認しました。
https://docs.aws.amazon.com/ja_jp/qldb/latest/developerguide/getting-started.html

チュートリアルは以下の記事が参考になりました。
https://qiita.com/yanyansk/items/586b7f1c86eca4352b44

IAMユーザのアクセスキーの作成

実装する前にQLDBにアクセスするための認証情報を作成する必要があります。
以下の手順でアクセスキーを発行して下さい。

サービス > IAM > ユーザ からユーザの追加を選択して下さい。

iam_1.png

任意のユーザ名を入力して下さい。アクセスの種類は「プログラムによるアクセス」にチェックを入れて下さい。

iam2.png

一旦はテストで使用するだけなので、ユーザグループなどは作成せず、ポリシーを直接アタッチします。

iam3.png

タグなどは特に必要がないので、ユーザーの作成まで完了して下さい。
作成完了の画面で表示される「アクセスキーID」と「シークレットアクセスキー」を控えて下さい。
driverからQLDBにアクセスする際にこの情報を使って認証します。

iam4.png

実装

driverや必要なモジュールのinstall

npm i amazon-qldb-driver-nodejs aws-sdk ion-js typescript

credential情報の編集

「credentials.json」などの名前で以下のファイルを作成してください。

{"accessKeyId":"${作成したアクセスキーID}","secretAccessKey":"${作成したシークレットアクセスキー}"}

認証部分の実装

認証情報の設定

作成したcredentialのjsonを使って認証します。

constAWS=require("aws-sdk");AWS.config.loadFromPath("./credentials.json");

Credentialの確認

デバッグしやすいように、Credentialが正しく設定されているか確認するfunctionを追加します。

functioncheckCredential(){returnnewPromise((resolve,reject)=>{AWS.config.getCredentials(function(err:Error){if(err){returnreject(err);}console.log("Access key:",AWS.config.credentials.accessKeyId);console.log("Secret access key:",AWS.config.credentials.secretAccessKey);resolve();});})}

メインフローの実装

メインの部分を実装していきます。

セッションの作成

regionはQLDBを作成したリージョンを設定してください。
PooledQldbDriverの第一引数には作成した台帳の名前を指定して下さい。

consttestServiceConfigOptions={region:"{QLDBを作成したリージョン}"};constqldbDriver:PooledQldbDriver=newPooledQldbDriver("{作成した台帳の名前}",testServiceConfigOptions);constqldbSession:QldbSession=awaitqldbDriver.getSession();

台帳上のテーブルの確認

for(consttableofawaitqldbSession.getTableNames()){console.log(table);}

動作確認

実装したコードの全文は以下になります。
こちらを動作確認してみます。

import{PooledQldbDriver,QldbSession}from"amazon-qldb-driver-nodejs";constAWS=require("aws-sdk");AWS.config.loadFromPath("./credentials.json");(async()=>{awaitcheckCredential();consttestServiceConfigOptions={region:"{QLDBを作成したリージョン}"};constqldbDriver:PooledQldbDriver=newPooledQldbDriver("{作成した台帳の名前}",testServiceConfigOptions);constqldbSession:QldbSession=awaitqldbDriver.getSession();for(consttableofawaitqldbSession.getTableNames()){console.log(table);}})().catch(err=>{console.error(err);});functioncheckCredential(){returnnewPromise((resolve,reject)=>{AWS.config.getCredentials(function(err:Error){if(err){returnreject(err);}console.log("Access key:",AWS.config.credentials.accessKeyId);console.log("Secret access key:",AWS.config.credentials.secretAccessKey);resolve();});})}
$ npx tsc main.ts
$ node main.js

Access key: xxxxxxxxxxxxxxxxxx
Secret access key: xxxxxxxxxxxxxxxxxx
VehicleRegistration
DriversLicense
Vehicle
Person

無事台帳上のテーブル名が表示されました!
次回は検索やデータの登録について書きたいと思います!

さいごに

ZEROBILLBANKでは一緒に働く仲間を募集中です。
ZEROBILLBANK JAPAN Inc.

nuxtでDOMException: Failed to execute 'appendChild' on 'Node'エラー

$
0
0

Failed to execute 'appendChild' on 'Node'

nuxtでこんな感じのエラーが出る
因みにdevでは出ない。

DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.
    at Object.appendChild

原因

<template>タグ内に <body>が入ってると起こる
色々なエラーにつながるので原因究明がしづらい。注意。

node.jsからWebsockets経由でOBSを操作する

$
0
0

動画配信ソフトのOBSをコードから操作して自動化したいなと思ったので調べたメモです。

obs-websocket

WebSocketsでOBSを操作出来るようにするプラグインです。
https://github.com/Palakis/obs-websocket

インストーラーが用意されているのでGUIからインストールするだけでOBSメニューの「ツール」にWebSockets Server Settingsという項目が追加されます。

obs-websocket-js

上記のWebsockets APIのJSラッパーです。
https://github.com/haganbmj/obs-websocket-js

READMEに書いてある通りに書けば動きます。
exampleそのままですがコメント付きのサンプルコードも乗せておきます。

// ライブラリのimportconstOBSWebSocket=require('obs-websocket-js');// インスタンス初期化constobs=newOBSWebSocket();// OBSに接続してPromiseを受け取るobs.connect({address:'localhost:4444',password:'$up3rSecretP@ssw0rd'})// 接続成功.then(()=>{console.log(`Success! We're connected & authenticated.`);// シーンの一覧を取得するリクエストreturnobs.send('GetSceneList');}).then(data=>{console.log(`${data.scenes.length} Available Scenes!`);// シーンの一覧から現在のシーンを探すdata.scenes.forEach(scene=>{if(scene.name!==data.currentScene){console.log(`Found a different scene! Switching to Scene: ${scene.name}`);// 現在のシーンを切り替えるリクエストobs.send('SetCurrentScene',{'scene-name':scene.name});}});}).catch(err=>{// Promise convention dicates you have a catch on every chain.console.log(err);});// シーンが切り替わったイベントをObserveするobs.on('SwitchScenes',data=>{console.log(`New Active Scene: ${data.sceneName}`);});// エラーをObserveするobs.on('error',err=>{console.error('socket error:',err);});

API リファレンス

ここです。ここに書かれているものは基本的にobs-websocket-jsからでも使えるようです。
https://github.com/Palakis/obs-websocket/blob/4.x-current/docs/generated/protocol.md

使用例

https://note.com/kirimin_chan/n/n0d44d734d3c4

TypeScriptのクラスをドラクエ風のステータスを実装しながら学んでみた

$
0
0

はじめに

  • npm: 6.13.7
  • node: 13.8.0
  • TypeScript: 3.8.2

TypeScript 学習のため、ドラクエ風のステータス出力をクラスで実装してみました。

  • ステータス
HP:  体力
ATK: 攻撃力
DEF: 防御力

今回はステータスの出力までですが、次の機会はダメージ計算なども追加して実際にバトルをさせてみたいと思います。

TypeScript のクラスについて

  • 完成
dorakue_style.ts
classBrave{name:string;hp:number;atk:number;def:number;constructor(name:string,hp:number,atk:number,def:number){this.name=name;this.hp=hp;this.atk=atk;this.def=def;}info(){return`${this.name}のHPは${this.hp}、攻撃力は${this.atk}、防御力は${this.def}です。`}}classMonsterextendsBrave{constructor(name:string,hp:number,atk:number,def:number){super(name,hp,atk,def);}}letbrave=newBrave("勇者",100,20,10);letslime=newMonster("スライム",10,2,1);console.log(brave.info());console.log(slime.info());
  • 実行結果
$ tsc dorake_style.ts
$ node dorake_style.js
>勇者のHPは100、攻撃力は20、防御力は10です。
>スライムのHPは10、攻撃力は2、防御力は1です。

TypeScript のクラスを使って「勇者」と「モンスター(スライム)」のステータスを表示しています。


勇者を作る(クラス宣言)

// クラスを宣言classBrave{// ステータスに必要なプロパティを用意name:string;hp:number;atk:number;def:number;//...}

まずはクラスを定義します。「勇者」を作成するためBraveクラスとし、ステータスに必要なプロパティを用意します。


コンストラクタ

コンストラクタは、Braveクラスがnewされたときに実行されるメソッド。

classBrave{// ...// クラスが new されたときに、実行されるconstructor(name:string,hp:number,atk:number,def:number){// new のときに渡された値がそれぞれ代入this.name=name;this.hp=hp;this.atk=atk;this.def=def;}}letbrave=newBrave("勇者",100,20,10);console.log(brave.name);// 勇者

new Brave(名前, HP, ATK, DEF)の際に値を渡すことでコンストラクタが実行され、渡された値がそれぞれ代入されます。


モンスターを作るためにBraveクラスを継承する

ドラクエの定番であるモンスターのスライムを作っていきます。
スライムも勇者と同様のステータスを持つため、Braveクラスをベースに継承を使ってMonsterクラスを作っていきます。

// Brave クラスをベースに Monster クラスを作成classMonsterextendsBrave{// 独自にプロパティを定義しなくていいconstructor(name:string,hp:number,atk:number,def:number){// super(); は継承元のコンストラクタを実行するsuper(name,hp,atk,def);}}


class Monster extends Braveとすることで、BraveクラスをベースにMonsterクラスが作成され、MonsterクラスはBraveクラスを継承することで、独自にプロパティを定義する必要がなくなります。


super()Monsterクラスの継承元であるBraveクラスのコンストラクタを実行します。
ts
super(name, hp, atk, def);


ステータスを出力

classBrave{// ...info(){return`${this.name}のHPは${this.hp}、攻撃力は${this.atk}、防御力は${this.def}です。`}}letbrave=newBrave("勇者",100,20,10);letslime=newMonster("スライム",10,2,1,15);console.log(brave.info());// 勇者のHPは100、攻撃力は20、防御力は10です。console.log(slime.info());// スライムのHPは10、攻撃力は2、防御力は1です。

Braveクラスにステータスを出力する処理を記述して、インスタンス化したそれぞれからinfo()を呼び出します。
MonsterクラスはBraveクラスを継承しているため、info()を呼び出すことができます。


  • 完成
classBrave{name:string;hp:number;atk:number;def:number;constructor(name:string,hp:number,atk:number,def:number){this.name=name;this.hp=hp;this.atk=atk;this.def=def;}info(){return`${this.name}のHPは${this.hp}、攻撃力は${this.atk}、防御力は${this.def}です。`}}classMonsterextendsBrave{constructor(name:string,hp:number,atk:number,def:number){super(name,hp,atk,def);}}letbrave=newBrave("勇者",100,20,10);letslime=newMonster("スライム",10,2,1);console.log(brave.info());console.log(slime.info());// 勇者のHPは100、攻撃力は20、防御力は10です。// スライムのHPは10、攻撃力は2、防御力は1です。


参考

TypeScript初心者が知っておくと嬉しいこと | Qiita
TypeScript ハンドブック - クラス | js STUDIO

localStrageメモ

$
0
0

localstrageとは

クライアント側のみで動く、データを保管するもの。
「情報をブラウザ上に保存しておく場所」

クライアント側のみで動く為、メッセージを送信する際はIDも一緒に送る必要がある。
(inputのvalueに値を指定などで)

身近な例

ユーザーがあるサービスにログインした場合、ログイン情報をローカルストレージ上に保存しておくことで、次回ユーザーが訪れた時に自動ログインが完了しているパターンなどが考えられます。

参考

https://toukei-lab.com/localstrage

コメント欄はいらない!Webmentionを使ってブログにツイッターでの反応を表示する (Part 1)

$
0
0

2020年。Gatsbyなどを使ってブログをゼロから一晩で作れる時代になった。特に投稿を、レポジトリの一部として、Github/Gitlabに保管すると、staticなプロジェクトとしてデプロイもすごく楽。だが、コメント機能を実装したければ、もともと不要だったデータベースが必要になって、一気にめんどくさくなる。自分でDBを管理したくない人は、Disqusのようなthird-partyサービスを使う選択肢もあるけど、有料。そこにWebmentionが登場する!

Webmentionとは

厳密にいえばWebmentionというのはプロトコルの名前である。そして現在は、W3Cの勧告である。

ただ、SNS・チャットによくあるみんなお馴染みの「メンション」のような概念として理解しても良い。Twitter/FB/Instagramと違って、自分のブログを誰が読んで、どこでリンクをシェアしているかは、Googleの分析を見ないとわからないし、ほかの人にも見えないが、Webmentionを導入すれば、その情報を取得できるようになるし、サーバーを所有していればその情報の所有権も自分にある。そして、自分のブログで、SNSと同期して反応した人の返信や「いいね」まで表示できるようになる!

以下が僕のブログにあるツイッターのメンションの例:

Capture.JPG

「いいね」も返信も表示されている。

Webmentionの導入

Webmentionを導入するには、最低限、二つの条件が必要:

  • メンションしたい側がWebmention用のエンドポイントに、メンションが存在するリンク (source) と、メンションされているリンク (target) を含めたPOSTリクエストを送る。ここの例を参照
curl -si https://webmention.io/aaronpk/webmention \-dsource=https://aaronpk.com/reply.html \-dtarget=https://aaronparecki.com/2018/06/30/11/your-first-webmention
  • メンション受けたい側が、Webmention用のエンドポイントを用意する。

ただ、TwitterやFBはWebmentionを自動的に送らないし、シェアしている人にいちいち手動でウェブメンションしてもらうこともない。

そこで、Bridgyのような無料サービスが、登録したSNSを定期的にクロールして、いいねとかリツイートとかのメンションを見つけたら、代わりにWebmention用のエンドポイントを叩く。NetlifyやNowのようなstaticなホスティングを使いたい場合、エンドポイントを用意するのはなかなか難しいと思うけど、代わりにエンドポイントになってくれるサービスもある。Webmention.ioはそのひとつで、僕が現在使っているサービス。ただ、もちろんこの場合メンションの情報はWebmention.ioに保管されることになるので、Webmention.ioが指定しているGETリクエストでそれをまず取得する必要がある。(以下もWebmention.ioを使う前提で書いてある)

このように、OSSでもあるこの二つのサービスを使うことで、Webmentionフローを簡単に導入できる。

最後に、Webmention.ioが正しく機能するように以下のステップが必要:

  • たとえばツイッターのメンションを受けたい場合は、ツイッターのプロフィールに自分のブログ・ウエブサイトへのリンクを貼る (インスタの場合は、インスタのプロフィールにetc)

  • 自分のウェブサイトのページに、自分のツイッターのプロフィールへのリンクを貼る。これはやり方が二つあって、ひとつは<head>rel="me"<link>を追加する。

<linkhref="https://twitter.com/maaiiya8"rel="me">

または、すでにページ上にツイッターのプロフィールへのリンク(<a>)があれば、それにrel="me"を追加するだけでもいい

  • 自分のウェブサイトのページの<head>に以下を追加する (phantasiai.devの代わりに自分のドメインを書く):
<linkrel="webmention"href="https://webmention.io/phantasiai.dev/webmention"/><linkrel="pingback"href="https://webmention.io/phantasiai.dev/xmlrpc"/>

これでWebmentionの導入は大体完了!

自分のブログのすべてメンションを以下のように取得できる (tokenはWebmention.ioにもらうAPIキー):

GET https://webmention.io/api/mentions.jf2?domain=phantasiai.dev&token=xxxxx

このような情報が返ってくる:

Capture21e.JPG

wm-propertyは表示する際かなり役に立つプロパティである。それを見て、たとえばlike-ofだったらいいねだということがわかる。返信だったらin-reply-toになる。

Webmentionを取得・表示する

上記の情報をどうするかは、あなたの自由でこの投稿の範囲外だが、自分のページに表示したい場合は、主に二つのアプローチがある:

  • ビルド時に取得する
  • クライアント側で取得する

Gatsbyで作った僕のブログでは、ビルド時に取得している。ただ、新しいメンションがあるのに、まだビルドしていない状況ちょっと嫌だなと思ったときに、Webmention.ioでは通知のたびに実行するWebhooksがあることを発見。僕の場合は、Netlifyを使っているで、ビルドHookへのリンクをWebmention.ioに登録すると、メンションがあるたびにブログが再ビルドされる。これで、完全にstaticでありながらフレッシュな情報を表示できる!

具体的なやりかたはPart 2で書きます。

注意

現在は、BridgyはBrotliに対応していない。つまりあなたのページはBrotliで提供されているなら、Bridgyを使うことができない。それはBridgyが使っているGoogle App Engineのバグのせいであって、将来は修正されるはず。

TypeScriptのExpressアプリケーションを作る

$
0
0

動作環境

  • macOS Mojave
  • node v12.14.1
  • npm 6.13.4
  • express 4.16.4
  • typescript 3.8.2

最終的なコード

最終的なコードは以下に上げてあるので、先に見たい方は御覧ください。
https://github.com/jumperson/quick-start-express-typescript

作成手順

JavaScriptのExpressアプリケーションを作成する

Express のアプリケーション生成プログラムに従って作成していきます。

グローバルにexpressをインストールします。

$ npm install express-generator -g

インストールしたコマンドで作業ディレクトリに quick-start-express-typescriptという名のExpressアプリケーションを作成します。

$ express quick-start-express-typescript --git --no-view

npm installを実行し、Expressアプリケーションを起動します。

$ cd quick-start-express-typescript
$ npm install
$ DEBUG=myapp:* npm start

http://localhost:3000/にアクセスし、動作していることを確認します。

不要なViewファイルを削除する

今回はTypeScriptにするファイルを減らすためにViewファイルは削除します。

$ rm -r ./public/
$ rm ./routes/index.js 

上記を参照しているコードも修正します。

diff --git a/app.js b/app.js
index d187f73..3aecdc0 100644
--- a/app.js
+++ b/app.js
@@ -3,7 +3,6 @@ var path = require('path');
 var cookieParser = require('cookie-parser');
 var logger = require('morgan');

-var indexRouter = require('./routes/index');
 var usersRouter = require('./routes/users');

 var app = express();
@@ -14,7 +13,6 @@ app.use(express.urlencoded({ extended: false }));
 app.use(cookieParser());
 app.use(express.static(path.join(__dirname, 'public')));

-app.use('/', indexRouter);
 app.use('/users', usersRouter);

 module.exports = app;

TypeScriptに書き換える

初めに、以下のコマンドを実行し、TypeScriptに必要なモジュールを追加します。

$ npm install --save-dev typescript @types/node @types/express @types/debug

次に、以下のコマンドで、 tsconfig.jsonを作成します。

$ ./node_modules/.bin/tsc --init

次に、JavaScriptファイルを以下の通りTypeScriptに書き換えます。

./routes/users.js => ./routes/users.ts

import{Request,Response}from"express";/* GET users listing. */exportconstindex=(req:Request,res:Response)=>{res.send('respond with a resource');};

./app.js => ./app.ts

importexpress=require('express')import*asusersRouterfrom"./routes/users";constapp=express();app.use('/users',usersRouter.index);exportdefaultapp;

./bin/www => ./bin/www.ts

#!/usr/bin/env node
/**
 * Module dependencies.
 */importappfrom'../app';import*ashttpfrom'http';import*asdebugModulefrom'debug';vardebug=debugModule.debug('quick-start-express-typescript:server');/**
 * Get port from environment and store in Express.
 */constport=normalizePort(process.env.PORT||'3000');app.set('port',port);/**
 * Create HTTP server.
 */constserver=http.createServer(app);/**
 * Listen on provided port, on all network interfaces.
 */server.listen(port);server.on('error',onError);server.on('listening',onListening);/**
 * Normalize a port into a number, string, or false.
 */functionnormalizePort(val:string):number|string|boolean{constnport=parseInt(val,10);if(isNaN(nport)){// named pipereturnval;}if(nport>=0){// port numberreturnnport;}returnfalse;}/**
 * Event listener for HTTP server "error" event.
 */functiononError(error:any):void{if(error.syscall!=='listen'){throwerror;}constbind=typeofport==='string'?'Pipe '+port:'Port '+port;// handle specific listen errors with friendly messagesswitch(error.code){case'EACCES':console.error(bind+' requires elevated privileges');process.exit(1);case'EADDRINUSE':console.error(bind+' is already in use');process.exit(1);default:throwerror;}}/**
 * Event listener for HTTP server "listening" event.
 */functiononListening():void{functionbind(){constaddr=server.address();if(addr===null){return'';}if(typeofaddr==='string'){return'pipe '+addr;}if('port'inaddr){return'port '+addr.port;}}debug('Listening on '+bind());}

TypeScriptをJavaScriptにトランスパイルする

まずは srcディレクトリを用意し、上記の .tsファイルをすべて移動します。

On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    renamed:    app.ts -> src/app.ts
    renamed:    bin/www.ts -> src/bin/www.ts
    renamed:    routes/users.ts -> src/routes/users.ts

次に distディレクトリを用意し、 tsconfig.jsonを以下のように変更します。

diff --git a/tsconfig.json b/tsconfig.json
index 54d53fd..b88125a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,7 @@
     // "outFile": "./",                       /* Concatenate and emit output to single file. */
-    // "outDir": "./",                        /* Redirect output structure to the directory. */
+    "outDir": "./dist",                        /* Redirect output structure to the directory. */
     // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */

次に、トランスパイルのコマンドを追加します。

diff --git a/package.json b/package.json
index 29eaa22..48a62e0 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,8 @@
   "version": "0.0.0",
   "private": true,
   "scripts": {
-    "start": "node ./bin/www"
+    "start": "node ./bin/www",
+    "build": "tsc"
   },
   "dependencies": {
     "cookie-parser": "~1.4.4",

追加したコマンドを実行し、 distディレクトリにJavaScriptのファイルが作成されることを確認します。

$ npm run build

また、 distディレクトリはgit管理不要なので、以下の修正を行います。

diff --git a/.gitignore b/.gitignore
index d1bed12..a031e35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,3 +59,5 @@ typings/

 # next.js build output
 .next
+
+/dist

トランスパイルされたファイルでExpressを起動する

Express起動をコマンドを修正してExpressを起動できるようにします。

diff --git a/package.json b/package.json
index 48a62e0..ee55eb6 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "version": "0.0.0",
   "private": true,
   "scripts": {
-    "start": "node ./bin/www",
+    "start": "npm run build && node ./dist/bin/www.js",
     "build": "tsc"
   },
   "dependencies": {

ホットリロードの設定

最後に、開発時のコード修正時にホットリロードされるようにします。

以下のモジュールを追加します。

$ npm install --save-dev ts-node nodemon

次にルートに以下のファイルを追加します。

{"watch":["src"],"ext":"ts","exec":"ts-node ./src/bin/www.ts"}

次にコマンドを追加します。

diff --git a/package.json b/package.json
index f602cdd..c56097d 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
   "private": true,
   "scripts": {
     "start": "npm run build && node ./dist/bin/www.js",
+    "start:dev": "nodemon",
     "build": "tsc"
   },
   "dependencies": {

これで以下のコマンドでExpressが起動し、またコードを修正した際に自動で再起動されるようになります。

npm run start:dev

最終的なコード(再掲)

冒頭でも記載しましたが、最終的なコードは以下に上げてあります。
https://github.com/jumperson/quick-start-express-typescript

参考にしたページ

以下はこの記事の参考にさせて頂いたページです。


azure app service で kintone アプリデータ公開

$
0
0

kintone のアプリデータをazure app service で公開してみます。

概要

プラグインを購入いただいたお客様のサポートやプラグインのバージョンアップなどで、kintone アプリを使ってどうにか出来ないかと調べています。
kintone アプリデータを外部に公開する外部サービスがありますが、簡単にできる反面、いろいろ制約があったりします。
azure app service で、kintone アプリデータを外部に公開する試作をしてみます。

環境

いつも使っている PC 環境で開発・テストして、azure へデプロイ。

  • 開発環境

  • azure環境

    • east asia (東京)

開発準備

vscode, node, npm あたりがそろっていれば OK。

Express ジェネレーター

アプリ名を指定すれば、基本的な一式をそろえてくれる優れもの。

アプリ名 kintone-view-test1 で作ってみます。

$ mkdir webapp
$ cd webapp
$ npx express-generator kintone-view-test1 --view pug --git
$ cd kintone-view-test1
$ npm install
...

アプリケーションの実行

npm start すると、http://localhost:3000で参照できます。
CTL+C で、終了。

$ npm start

表示されるページ
2020-02-29_22h31_28.png

アプリケーションのデバッグ

vscode で、デバッグが出来るのがうれしい。
下記で、 wwww ファイルを起点とした設定が出来ます。

  • vscode で、kintone-view-test1\bin\www を開きます。
  • vscode の「デバッグと実行」をクリック
  • 「lunch.json ファイルを作成します。」をクリック
  • 「Node.js の実行とデバッグ(F5)」をクリック

2020-02-29_22h41_31.png

  • ブレークポイント
    • index.js にブレークポイントを設定
  • ブラウザから、http://localhost:3000で参照
    2020-02-29_22h47_46.png

  • ブレークポイントで停止中は、変数にカーソルを当てると内容が表示されます

  • デバッグコンソール上で、変数を入力すると内容が確認できます。
    2020-02-29_22h50_59.png

アプリケーションのデプロイ

azure 環境へ、デプロイしてみます。

  • azure のアカウントを持っていて、以前からvscode で開発していれば、「Deploy to web app」をクリックするとデプロイが始まります。
    2020-02-29_23h03_05.png

  • kintone-view-test1 のフォルダーを指定
    2020-02-29_23h03_25.png
    2020-02-29_23h04_59.png

  • あとは、順番に node.js のバージョン等を指定します。

  • デプロイされた app をazure 上で確認
    2020-02-29_23h21_37.png

  • アプリの URL をクリックすると、実行結果が確認できます。
    2020-02-29_23h25_23.png

kintone アプリのレコード取得を組み込み

ここまでの作業で、azure 上でアプリを動作することが出来ました。
いよいよ kintone アプリのレコードを取得して、公開してみます。

kintone JS SDK のインストール

$ cd kintone-view-test1
$ npm install --save @kintone/kintone-js-sdk@v0.6.1

app.js の変更

とりあえず、kintone アプリレコードの照会画面用に、app.js 等を変更していきます。
「/customer」の URL でアクセスされたら、kintone の顧客アプリを表示するイメージです。
既存の「/users」と同じように処理を追加します。

app.js
varcreateError=require('http-errors');varexpress=require('express');varpath=require('path');varcookieParser=require('cookie-parser');varlogger=require('morgan');varindexRouter=require('./routes/index');varusersRouter=require('./routes/users');varcustomerRouter=require('./routes/customer');varapp=express();// view engine setupapp.set('views',path.join(__dirname,'views'));app.set('view engine','pug');app.use(logger('dev'));app.use(express.json());app.use(express.urlencoded({extended:false}));app.use(cookieParser());app.use(express.static(path.join(__dirname,'public')));app.use('/',indexRouter);app.use('/users',usersRouter);app.use('/customer',usersRouter);// catch 404 and forward to error handlerapp.use(function(req,res,next){next(createError(404));});// error handlerapp.use(function(err,req,res,next){// set locals, only providing error in developmentres.locals.message=err.message;res.locals.error=req.app.get('env')==='development'?err:{};// render the error pageres.status(err.status||500);res.render('error');});module.exports=app;

index.pug

index から、顧客一覧へのリンクを追加しておきます。

index.pub
extends layout

block content
  h1= title
  p Welcome to #{title}
  br
  a(href="./customer", title="顧客一覧") 顧客一覧

customer.js

アプリIDやURLなどは、開発用・本番用で切り替えが必要のので
kintone 接続用の情報は、モジュール化してまとめたほうがよさそうです。

customer.js
varexpress=require('express');varrouter=express.Router();constkintone=require('@kintone/kintone-js-sdk');/* GET customers listing. */router.get('/',function(req,res,next){constkintoneAuth=newkintone.Auth();constusername='xxxxxxxxx';constpassword='xxxxxxxxx';kintoneAuth.setPasswordAuth({username:username,password:password});constdomainName='xxxxxxxxx.cybozu.com';constkintoneConnection=newkintone.Connection({domain:domainName,auth:kintoneAuth});constkintoneRecord=newkintone.Record({connection:kintoneConnection});constfinfos=[{label:'会社名',fcode:'会社名'},{label:'担当者名',fcode:'担当者名'},{label:'メールアドレス',fcode:'メールアドレス'}];constfields=finfos.map((finfo)=>{returnfinfo.fcode;});constappId=549;// target appIDconstparm={app:appId,query:'order by $id',fields:fields};kintoneRecord.getRecords(parm).then((rsp)=>{console.log('rsp',rsp);res.render('customer',{title:'顧客一覧',finfos:finfos,records:rsp.records});}).catch((err)=>{// The promise function always reject with KintoneAPIExeptionconsole.log(err.get());// res.render('forum', { title: 'rex0220 forum', user: 'error' });});});module.exports=router;

customer.pug

顧客一覧用のテンプレートです。
単純なテーブル構造だと簡単ですが、データ量が多くページ制御等があると少し面倒なので、グリッドライブラリを使ったほうがいいかもしれません。

customer.pug
extends layout

block content
  h1= title

  table(class="xp-customer-list")
    thead
      each finfo in finfos
        th #{finfo.label}
    tbody
      each rec in records
        tr
          each finfo in finfos
            td #{rec[finfo.fcode].value}

style sheet

顧客一覧用に少し手直し
見映えをよくする場合は、地道に手直し。

style.css
body{padding:50px;font:14px"Lucida Grande",Helvetica,Arial,sans-serif;}a{color:#00B7FF;}.xp-customer-list{background-color:aqua;width:700px;font-size:14px;}.xp-customer-listthead{background-color:azure;}

取得結果

素のテーブルだけだと、いまいち。

2020-03-01_02h31_48.png

azure へデプロイ

初回と同様に「Deploy to web app」をクリックすると、デプロイされます。
2回目以降は、入力なしにそのまま続行します。

参考サイト

あとがき

vscode でデバッグ出来るのが、一番うれしい。
azure へのデプロイも vscode から簡単に実行できる。
Express の構成に慣れれば、開発が楽かもしれません。

node.jsでswitchbotの温湿度計データ取得

$
0
0

switchbotの温湿度計を購入したので、node.jsでデータを取得し、s3にアップロードしてみた。

データの取得

データの取得にはnobleを使用した。
nodeのv9以降では動かないようなので、v8系をインストールすること。
nobleの使い方は割愛。

microbotの開発元?のOpenWonderLabsがAPI仕様を公開しているので、それを参照しながらデータを読み取る。
https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API
ビットの操作はとっても苦手なので、苦労した。
例えば、4バイト目の後ろ7ビットには温度のデータが入っているようなので、このようにして取得。

consttemperature=data.readUInt8(4)&0x7f;

&(ビット演算子)で0x7f(1111111)をビットANDして、後ろ7桁を切り出すらしい。

前3桁が欲しかったら、こんな感じで0xE0(11100000)をビットANDして、
さらにシフト演算子 >>>(0埋めバージョン)で後ろ5桁を溢れさせる。

vara=data.readUInt8(4)&0xE0>>>5

web開発ばっかりやってた人間としては、このあたりはとっても苦手な領域。

データが取れたら fsでファイルに書き出す。
後で扱いやすいように moment.jsを使って日時分秒をファイル名にした。

↓ソース全体(scan.js)

'use strict';constnoble=require('noble');constfs=require('fs');constmoment=require('moment');// living swithbotconstMAC="xxxxxxx";// switchbotのmacアドレスconsttmpDir="/var/tmp/envdata/";// データを保存したいディレクトリnoble.on('stateChange',(state)=>{if(noble.state==='poweredOn'){noble.startScanning();}else{noble.on('stateChange',scanStart);}});noble.on('scanStart',()=>{console.log('start scanning');});noble.on('scanStop',()=>{console.log('stop scanning');process.exit(0);});noble.on('discover',(peripheral)=>{if(peripheral.uuid===MAC){noble.stopScanning();constdata=peripheral.advertisement.serviceData[0].data;constdeviceType=data.slice(0,1).toString('ascii');constbattery=data.readUInt8(2)&0x7f;consttemperature=data.readUInt8(4)&0x7f;consthumidity=data.readUInt8(5)&0x7f;consttime=moment().format('YYYY-MM-DD_HH:mm:ss');constjsonData={"datetime":time,"battery":battery,"temperature":temperature,"humidity":humidity};// write to filefs.writeFileSync(tmpDir+time+'.log',JSON.stringify(jsonData));noble.stopScanning();}});

s3へのアップロード

s3へのアップロードはいたって簡単です。

下準備

aws-sdkのライブラリを npm installする。
AWSのIAMでアップロード用のユーザ作ってAmazonS3FullAccessのポリシー(uploadだけだったら権限絞ったポリシーで良い気もする)付けて、そのcredential情報をjsonファイルに書き出しておく。

{"accessKeyId":"xxxxxxxxxxxxx","secretAccessKey":"xxxxxxxxxx"}

こんな具合。

S3への接続

こんな感じでs3オブジェクトをセットアップ。

AWS.config.loadFromPath('/home/pi/.aws/credential.json');// 保存したcredentialファイルのパスを指定AWS.config.update({region:'ap-northeast-1'});consts3=newAWS.S3();

あとはアップロードしたいファイルの情報を整えて s3.putObject()でアップロード。

↓ソース(upload.js)

constAWS=require('aws-sdk');constfs=require('fs');AWS.config.loadFromPath('/home/pi/.aws/credential.json');AWS.config.update({region:'ap-northeast-1'});consts3=newAWS.S3();consttmpdir='/var/tmp/envdata/';fs.readdir(tmpdir,(err,files)=>{if(err)throwerr;files.forEach((file)=>{varparams={Bucket:'switchbot-room',Key:file};params.Body=fs.readFileSync(tmpdir+file);s3.putObject(params,(err,data)=>{if(err)throwerr;fs.unlinkSync(tmpdir+file,(err)=>{if(err)throwerr;});});});});

自動起動設定

cron設定するだけ。

*/5 **** /usr/local/bin/node /home/pi/swithbot/scan.js
 */5 **** /usr/local/bin/node /home/pi/swithbot/upload.js

5分間隔でデータを取得して、アップロード。

今のところ問題なくデータが上がってます。

NextAction

次は、上がったデータを可視化したい。
QuickSight, Mackerel, ES + Kibana どれでやろうか検討中。chart.js使って自分で作ってもいいけど、
一番費用が掛からない方法がいいな。

Node.js Expressフレームワークを使用する為の準備作業

$
0
0

はじめに

本格的な開発をするにはフレームワークを活用します。
フレームワークを活用することで効率よく、開発することが出来るようになります。

今回は「Express」というフレームワークを採用しました。

環境

OS:Windows 10 Pro 64bit
node.js:v12.16.1
npm:v6.13.4

Express Generatorのインストール

Expressを使用するには、Express Generatorを使用するのが便利です。
まず、Express Generatorをインストールします。
コマンドプロンプトを立ち上げで、以下のコマンドでインストールします。

npm install express-generator - g

上記コマンドはグローバルインストールと呼ばれるインストール方法になります。
Express Generatorはプログラム中で使用するものではありませんので、グルーバルな領域にインストールします。
ですので、コマンドを打つ場所(カレントフォルダ)はどこでも良いです。

確認

以下のコマンドでインストールされたか確認出来ます。

npm view express-generator version

今回は4.16.1がインストールされました。
ex_var.jpg

node-canvasで絵文字を扱う

$
0
0

ユーザ生成コンテンツは、OGPを画像化して見せるのがデファクトスタンダードのようになってきました。
node.jsから、画像処理をcanvasのように扱える node-canvasを使う人も多いのではないでしょうか。

その方法は色々紹介されています。例えばこちらの記事など。
node.jsではてなブログ風アイキャッチ画像を動的に生成する

このnode-canvasですが、デフォルトでは絵文字は描画ができません。

今回は、node-canvasで、絵文字を Twemojiの見た目で描画するモジュールの紹介です。

node-canvasで絵文字を扱う

node module を npmで公開しておきました。

$ npm install--save node-canvas-with-emoji 
const{createCanvas}=require('canvas');const{fillTextWithTwemoji}=require('node-canvas-with-twemoji');asyncfunctionmain(){constcanvas=createCanvas(500,300);constcontext=canvas.getContext('2d');context.font='30px Arial';awaitfillTextWithTwemoji(context,'絵文字も描画😉',140,160);}main();

localhost.png

提供しているのは fillTextWithTwemoji(context, text, x, y, options?)という関数です。
contextを渡す以外は、本家の CanvasRenderingContext2D.fillTextのように扱えるようにしています。

ただし、
fillTextWithTwemojiは非同期(Promise)で扱われるため、Promise や async/await も利用する必要があります。
(関数内で絵文字画像をダウンロードしているため)

また、現時点(2020.03.01)ではイタリック書体や、maxWidth引数などに対応していません。
こちらで開発しているので何かあれば。 GitHub
スターがつくだけでもやる気が出て実装するかもしれません。

雑記

Twemojiではなく、Apple Color Emojiなど、絵文字フォントを使う方法もあるようです。
- Any emoji support?
- Colored Emoji

ただし、こちらは著作権周りで、実際に使う場合は注意が必要です。
- 「絵文字」の利用で気をつけた方が良いこと、安全な使い方について

雑記2

同じようなこと考えてる人もいました。
- node-canvas でカラー絵文字対応について考えてたこと

nodejsのorマッパー sequelize のaccociaton

$
0
0

associationのまとめ

belongsTo

1つの関連をもつ。

  • 例) Players.belongsTo(Teams) Playersに外部キーteamIdが自動追加される。

hasOne

1つの関連をもつ。

  • 例) Players.hasOne(Profiles)

Profilesに外部キーplayerIdが自動追加される。include時、最初の1つのみ出力される。

hasMany

複数の関連をもつ。

  • 例) Players.hasMany(Profiles)

Profilesに外部キーplayerIdが自動追加される。include時、複数件出力される。

belongsToMany

多対多の中間テーブルを持つ。

  • 例1)Players.belongsToMany(Teams, { through: TeamPlayers })
    中間テーブル'TeamPlayers'が作成され、そこに外部キーteamIdplayerIdが自動追加される

  • 例2)Teams.belongsToMany(Players, { through: TeamPlayers })

    中間テーブル'TeamPlayers'が作成され、そこに外部キーplayerIdteamIdが自動追加される

belongToとhasOneの違い

挙動はほぼ同じ。指定の順が変わっただけ。

belongsTo のサンプル

// model(テーブル)を定義// 主キー`id`や`createdAt`、`updatedAt`は勝手に定義されるので記述不要constPlayers=sequelize.define('players',{name:Sequelize.STRING,});constTeams=sequelize.define('teams',{name:Sequelize.STRING,});Players.belongsTo(Teams,{foreignKey:'team_id'})// playersに外部キー'team_id'が作成される。foreignKeyを省略した場合、キャメルケースでid名'teamId'が自動生成される// modelを元にDBテーブルの作成({force:true}オプションは、テーブルdrop&create)awaitPlayers.sync({force:true});awaitTeams.sync({force:true});// レコードのcreateconstteam1=awaitTeams.create({name:'team1'});constplayer1=awaitPlayers.create({name:'player1',team_id:team1.id});// レコード取得constplayer=awaitPlayers.findById(player1.id,{include:Teams})console.log(player.get({plain:true}))

出力結果

{ id: 1,
    name: 'player1',
    createdat: 2019-08-29t02:36:21.464z,
    updatedat: 2019-08-29t02:36:21.464z,
    team_id: 1,
    team:
    { id: 1,
    name: 'team1',
    createdat: 2019-08-29t02:36:21.448z,
    updatedat: 2019-08-29t02:36:21.448z } }

以下でも、teams : players = 1 : n で取得できそうだが、できない

constteam=awaitTeams.findById(team1.id,{include:Players})

出力結果

failed: sequelizeeagerloadingerror: players is not associated to teams!

hasOne のサンプル

// model(テーブル)を定義constPlayers=sequelize.define('players',{name:Sequelize.STRING,});constProfiles=sequelize.define('profiles',{name:Sequelize.STRING,});Players.hasOne(Profiles,{foreignKey:'player_id'})// modelを元にDBテーブルの作成awaitPlayers.sync({force:true});awaitProfiles.sync({force:true});// レコードのcreateconstplayer1=awaitPlayers.create({name:'player1'});constprofile1=awaitProfiles.create({name:'profile1',player_id:player1.id});constprofile2=awaitProfiles.create({name:'profile2',player_id:player1.id});// レコードの取得constplayer=awaitPlayers.findById(player1.id,{include:Profiles})console.log(player.get({plain:true}))

出力結果

{ id: 1,
    name: 'player1',
    createdAt: 2019-08-29T03:59:30.432Z,
    updatedAt: 2019-08-29T03:59:30.432Z,
    profile:
    { id: 1,
    name: 'profile1',
    createdAt: 2019-08-29T03:59:30.448Z,
    updatedAt: 2019-08-29T03:59:30.448Z,
    player_id: 1 } }

hasMany のサンプル

// model(テーブル)を定義constPlayers=sequelize.define('players',{name:Sequelize.STRING,});constProfiles=sequelize.define('profiles',{name:Sequelize.STRING,});Players.hasMany(Profiles,{foreignKey:'player_id'})// modelを元にDBテーブルの作成awaitPlayers.sync({force:true});awaitProfiles.sync({force:true});// レコードのcreateconstplayer1=awaitPlayers.create({name:'player1'});constprofile1=awaitProfiles.create({name:'profile1',player_id:player1.id});constprofile2=awaitProfiles.create({name:'profile2',player_id:player1.id});// レコードの取得constplayer=awaitPlayers.findById(player1.id,{include:Profiles})// hasOneだと最初の1件しか取得できないがhasManyだと全て取得できるconsole.log(player.get({plain:true}))

出力結果

{ id: 1,
    name: 'player1',
    createdAt: 2019-08-29T04:13:05.396Z,
    updatedAt: 2019-08-29T04:13:05.396Z,
    profiles:
    [ { id: 1,
        name: 'profile1',
        createdAt: 2019-08-29T04:13:05.412Z,
        updatedAt: 2019-08-29T04:13:05.412Z,
        player_id: 1 },
    { id: 2,
        name: 'profile2',
        createdAt: 2019-08-29T04:13:05.428Z,
        updatedAt: 2019-08-29T04:13:05.428Z,
        player_id: 1 } ] }

belongsToMany のサンプル

// model(テーブル)を定義// 主キー'id'や'createdAt'、'updatedAt'は勝手に定義されるので記述不要constPlayers=sequelize.define('players',{name:Sequelize.STRING,});constTeams=sequelize.define('teams',{name:Sequelize.STRING,});constTeamPlayers=sequelize.define('team_players',{name:Sequelize.STRING,});// 中間テーブル'TeamPlayers'を作成し、そこに'team_id'を追加するPlayers.belongsToMany(Teams,{through:TeamPlayers,foreignKey:'team_id'})// 中間テーブル'TeamPlayers'を作成し、そこに'team_id'を追加するTeams.belongsToMany(Players,{through:TeamPlayers,foreignKey:'player_id'})awaitTeamPlayers.sync({force:true});awaitPlayers.sync({force:true});awaitTeams.sync({force:true});// レコードのcreateconstteam1=awaitTeams.create({name:'team1'});constteam2=awaitTeams.create({name:'team2'});constplayer1=awaitPlayers.create({name:'player1'});player1.addTeam(team1,{status:'started'})player1.addTeam(team2,{status:'started'})constplayer2=awaitPlayers.create({name:'player2'});player2.addTeam(team1,{status:'started'})player2.addTeam(team2,{status:'started'})// レコード取得constplayer=awaitPlayers.findById(player1.id,{include:Teams})console.log(player.get({plain:true}))constteam=awaitTeams.findById(team1.id,{include:Players})console.log(team.get({plain:true}))

出力結果

{ id: 1,
  name: 'player1',
  createdAt: 2019-08-29T05:56:11.200Z,
  updatedAt: 2019-08-29T05:56:11.200Z,
  teams:
   [ { id: 1,
       name: 'team1',
       createdAt: 2019-08-29T05:56:11.168Z,
       updatedAt: 2019-08-29T05:56:11.168Z,
       team_players: [Object] },
     { id: 2,
       name: 'team2',
       createdAt: 2019-08-29T05:56:11.184Z,
       updatedAt: 2019-08-29T05:56:11.184Z,
       team_players: [Object] } ] }

{ id: 1,
  name: 'team1',
  createdAt: 2019-08-29T05:56:11.168Z,
  updatedAt: 2019-08-29T05:56:11.168Z,
  players:
   [ { id: 1,
       name: 'player1',
       createdAt: 2019-08-29T05:56:11.200Z,
       updatedAt: 2019-08-29T05:56:11.200Z,
       team_players: [Object] },
     { id: 2,
       name: 'player2',
       createdAt: 2019-08-29T05:56:11.217Z,
       updatedAt: 2019-08-29T05:56:11.217Z,
       team_players: [Object] } ] }
Viewing all 8920 articles
Browse latest View live