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

Hubotを使ってみた

$
0
0

はじめに

この記事はなんとなくJSとかを勉強している学生がメモ代わりに書いているものです。内容は期待しないでください。

1.仮想環境を開く

今回はUbuntuで行うのでiTerm2で仮想環境を起動する。

起動したところで
1.Virtual Box(バーチャルボックス)
2.Vagrant(ベイグラント)
という2つのソフトウェアを使った仮想環境でUbuntuを使用します。
cd ~/vagrant/ubuntu
vagrant up
vagrant ssh

Ubuntuがインストールされたディレクトリに移動。vagrant upは仮想的なPCにインストールされたUbuntuを起動するコマンドで,vagrant sshはVagrantの仮想マシンがセットされている状態でSSHに接続します。

2.botを作る上で必要なモジュールをインストールする

npm install -g hubot yo generator-hubot coffee-script

yoとはYeomanという雛形作成ツールです。
generator-hubotはHubot のためのYeomanジェネレーター(generator-hubot)です。
coffeescriptはhubotをJSで扱うために必要なためインストール。

3.botを作る

yo hubot --adapter=slack

上記した内容はyo hubotというコマンドでボットを作成し,それ以降はslackをアダプターとする内容です。
複数の質問に答えることができたらボットのプロジェクトが作成されます。
Done in 秒数.

と出てきたら成功です。

3.Hubotを動かすプログラムを書く

'use strict';module.exports=(rbot)=>{rbot.hear(/hello>/i,msg=>{msg.send(`Hi`);});

1行目はJSをstrictモードで利用するための記述です。

2行目はmodule.exportsを使ってrbotという変数に値を直接格納できしています。module.exportsを使うと、指定した値を他のjsファイルから読み込んで再利用することができるようになります。少し似ているexportsとの違いはプロパティを設定せずに値を直接格納できます。記述方法は以下参照。
module.exports.プロパティ名 = 値

3行目以降はモジュールの関数で,helloというワードに反応してhiと返す記述です。特定の内容に反応させるには,Robotクラスのメソッドhearやrespond(用途はほぼ同じ)を使用します。記述方法は以下参照。
hear: (マッチさせたい正規表現, 正規表現にマッチしたときに呼び出されるコールバック関数)

また,チャットに発言を投稿するにはsendとreply(用途はほぼ同じ)を使います。記述方法は以下参照。
send(`文字列`);

引用元(https://gihyo.jp/dev/serial/01/hubot/0004)

4.hubotを動かしてみる

以下コマンドでボットを動かしてみて,先ほどに指定した文字列に正しく反応できたら成功です。お疲れ様でした!!!!
bin/hubot

以下引用(https://gihyo.jp/dev/serial/01/hubot/0001)

botとは

チャットツールに常駐してチャット経由でコマンドを待ち受けて実行したり,決められた条件に従ってチャットに発言してチャットの参加者に通知したりするようなプログラムのことをbotと呼びます。

hubotとは

世の中には,開発言語やチャットツールごとにbotを作成するための様々なフレームワークが存在します。たとえば,PerlでIRCのbotを作るならikachan,WebサービスでTwitterのbotを作るならtwittbotが有名なところでしょうか。Hubotは,GitHub社が開発しMITライセンスで公開しているNode.jsでbotを作り動かすためのフレームワークです。
Hubotの大きな特徴に,様々なチャットツールに対応している点があります。たとえば,先ほど取り上げたikachanは,IRC用のbotを作るためのフレームワークであり,Twitterのbotを作る目的には使えません。一方でtwittbotはTwitter専用であり,IRC用のbotを作ることはできません。しかし,Hubotは,Hubotとチャットツールをつなぐモジュールである「Adapter」を切り替えることで様々なチャットツールに接続することができます。

アダプターとは

Adapterは,チャットツールからRobotに誰かが入力した発言などチャットの状況を入力し,スクリプトなどによって作られた出力をチャットツールに伝える役割をします。初期状態では,シェル上でHubotと対話できるshell adapterと,Campfireというチャットツールに接続するCampfire AdapterいうAdapterが付属しています。
様々なAdapterがサードパーティーから提供されているため,大抵の場合は自分が使っているチャットツールに対応したAdapterが見つかると思います。


【Node.js】Dockerで作成したMySQLをDUMPする

$
0
0

経緯

Docker-composeで作成したMySQLのデータをAmazonRDSに移行する必要があった。

結論

Dockerコンテナで作成したMySQLデータをRDSなどの外部サーバーに移行する

エクスポート時

$ docker exec コンテナ名 mysqldump --default-character-set=文字コード(utfmb4など) -u ユーザ名 -p パスワード データベース名 > backup.sql

インポート時

mysql --default-character-set=文字コード -h エンドポイント(AmazonRDSの場合) -P 3306 -u admin -p データベース名 < backup.sql

Sequelize(Node.jsのORM)を使ってスキーマを作成していた場合

エクスポート時

$ docker exec コンテナ名 mysqldump  --skip-lock-tables --default-character-set=文字コード(utfmb4など) --ignore-table=データベース名.SequelizeMeta  -u ユーザ名 -p パスワード データベース名 > backup.sql

インポート時

mysql --default-character-set=文字コード -h エンドポイント(AmazonRDSの場合) -P 3306 -u admin -p データベース名 < backup.sql

Sequelizeを使ってスキーマを作成していた場合は SequelizeMetaというテーブルが作成されているので、dumpを作成しようとすると、以下のようなエラーになります。(アッパーキャメルだから?) 

doesn't exist when using LOCK TABLES
Couldn't read status information for table SequelizeMeta

なので、dump作成時に --skip-lock-tables--ignore-tableオプションを使います。

Sequelizeを用いたアプリケーションにおける、dump作成からRDSなどの外部サーバーで同じ環境を構築する流れは以下のようになります。

コンテナ内でdump作成
↓
RDSなどの移行先サーバーでマイグレーションを実行する
(ここでSequelizeMetaテーブルは正常に作成される)
↓
dumpをインポートする

インポートしたデータが文字化けする場合

dumpしたデータを読み込んだ際に日本語のデータが文字化けする場合があります。

原因は
--default-character-setオプションの設定が間違っている
DBサーバの文字コード設定が間違っている
といったものが予想されます。

RDSの場合は以下の記事が参考になりました。
[AWS][RDS][MySQL] 文字コードをutf8mb4にする

【Node.js】SequelizeのDB接続情報を環境変数で管理する

$
0
0

経緯

Sequelizeのデフォルトは config.jsonというファイル内でDBへの接続情報を管理しているので、 .envなどの環境変数で管理したかった。

結論

config/config.jsonの中身を以下のようにモジュール化して、拡張子を .jsに変更する。

dotenvで環境変数を読み込む。

config.js
require('dotenv').config();module.exports={'development':{'username':process.env.DB_USERNAME,'password':process.env.DB_PASS,'database':process.env.DB_DATABASE,'host':process.env.DB_HOST,'port':'3306','dialect':'mysql','operatorsAliases':false,},};

model/index.jsの以下を編集を編集する。

index.js
// 編集前constconfig=require(__dirname+'/../config/config.json')[env];// 編集後constconfig=require(__dirname+'/../config/config.js')[env];

herokuでnodeアプリをデプロイする

$
0
0

個人的な作業メモ。チュートリアルをなぞっただけです。

https://cli-assets.heroku.com/heroku.pkgからCLIのインストーラーをダウンロードして実行。

$ heroku --version
 ›   Warning: heroku update available from 7.35.0 to 7.39.5.
heroku/7.35.0 darwin-x64 node-v12.13.0

アップデートできるようなのでしおく。

$ heroku update
heroku: Updating CLI from 7.35.0 to 7.39.5... done
heroku: Updating CLI... done

herokuにログインする。

$ heroku login
heroku: Press any key to open up the browser to login or q to exit: 

何らかのキーを押すとブラウザが開く。

スクリーンショット 2020-04-29 9.18.38.png

Log Inを押下。CLIに戻るとログインされていた。すげぇ。

Logging in... done
Logged in as my@mail.address

サンプルのリポジトリをクローン。

git clone https://github.com/heroku/node-js-getting-started.git
cd node-js-getting-started

リモートと紐づける。

$ heroku create
Creating app... done, ⬢ morning-fortress-****
https://morning-fortress-****.herokuapp.com/ | https://git.heroku.com/morning-fortress-****.git
$ view ./.git/config 

[remote "heroku"]
        url = https://git.heroku.com/morning-fortress-****.git
        fetch = +refs/heads/*:refs/remotes/heroku/*

リモートにherokuが追加されている。

git push heroku master

herokuにプッシュする。

$ heroku open

を叩くとブラウザが起動した。

スクリーンショット 2020-04-29 9.47.00.png

めでたしめでたし

Commit Message / Pull Request Titleを美しく保つ方法

$
0
0

初投稿なので変な点があったら気軽にご指摘ください。

TL;DR

commitlintCommitizenSemantic Pull Requestを用いてConventialなメッセージを書こう。

はじめに

皆さんはコミットメッセージを普段どのように書いているでしょうか?人には人それぞれのコミットメッセージの書き方があると思います。しかしコミットメッセージの書き方が人によってバラバラだと非常に見にくいものとなってしまいます。

そのための規約がConventional Commitsです。

Conventional Commitsの仕様は、コミットメッセージのための軽量の規約です。 明示的なコミット履歴を作成するための簡単なルールを提供します、この規則に従うことで自動化ツールの導入を簡単にします。 この規約はSemVerと組み合わせることで、コミットメッセージへ機能、修正、破壊的変更を入れることで、さらに詳細な説明を可能にします。

このようにConventional Commitsではコミットメッセージにいくつかの規約を設けます。これによってコミットメッセージが統一されるだけでなくいくつかの自動化ツールによってChangelogの記述やSemantic Versioningの運用を楽にすることができます。
Conventional Commitsの規約については今回は触れませんがそこまで難しく無い規約なのでぜひ公式サイトを読んでください(日本語化されています)。

今回はそんなConventionalなコミットメッセージやプルリクエストにするためのいくつかのツールを紹介します。

commitlint

commitlintはコミットメッセージがConventional Commitsの規約に沿っているかを判断してくれるコミットメッセージLinterです。これを用いることによって規約に沿ったコミットメッセージを書き続けることができます。

導入方法

npmプロジェクトなら比較的簡単に導入することができます。
はじめにnpmプロジェクトの初期化とcommitlintパッケージのインストールを行います。

commitlintのインストール
# npmを用いますがyarnでも構いません# また、すでにpackage.jsonがある場合はこの操作は不要です
npm init

# commitlintのCLIツールと設定ファイルをインストールします
npm install--save-dev @commitlint/cli @commitlint/config-conventional

次にcommitlintの設定ファイルであるcommitlint.config.jsを作成します。

commitlint.config.js
module.exports={extends:["@commitlint/config-conventional"],};

これでCommitlint自体のインストールと設定は完了です。しかし、これだけではコミット時に自動的にLinterが動作しません。そのためGit hooksを簡単に設定できるhuskyをインストールします。

huskyのインストール
# huskyをインストールします# 先にgit initをしておかないとhooksが作られないので注意が必要です
npm install--save-dev husky

package.jsonにhuskyの設定を記述します。

package.json
{"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS"}}}

これでcommitlintの導入は完了です。コミットしようとすると自動的にcommitlintが走り、規約に反している場合はコミットが失敗します。

GitHub ActionsによるCI

huskyによるcommitlintはgit commit --no-verifyで実は回避することができます。またlintがうまく動作しないこともありますのでGithub Actionsを用いてCI上でもcommitlintをしましょう。

まずはリポジトリルート下に.github/workflowsをというディレクトリを作ります

mkdir-p .github/workflows

次にlint.yml.github/workflowsに作成し、設定を記述します。

lint.yml
name:linton:[push,pull_request]jobs:commitlint:runs-on:ubuntu-lateststeps:# リポジトリをチェックアウトします-name:Checkoutuses:actions/checkout@v2# Node.jsのセットアップを行います-name:Setup Node.jsuses:actions/setup-node@v1# CI高速化のためにキャッシュを使用します-name:Fetch cacheuses:actions/cache@v1with:path:~/.cache/yarnkey:${{ runner.os }}-${{ github.ref }}-${{ hashFiles('**/yarn.lock') }}restore-keys:|${{ runner.os }}-${{ github.ref }}-${{ runner.os }}-# 依存関係をインストールします-name:Install dependenciesrun:npm ci# commitlintを行います-name:Commitlintrun:npm run commitlint

package.jsonにnpm scriptを記述します。

package.json
{"scripts":{"commitlint":"commitlint -f HEAD"}}

これでGitHub Actionsの設定は完了です。pushすると自動的にcommitlintが走ります。

Commitizen

Commitizenはコミット時に専用のプロンプトが出現しそれによって規約に沿ったコミットメッセージを簡単に作成することができます。

導入方法

# Commitizenをインストールします
npm install--save-dev commitizen

# cz-conventional-changelogをインストールします
npm install--save-dev cz-conventional-changelog

package.jsonにcommitizenの設定とnpm scriptsの設定を記述します。

package.json
{"scripts":{"cz":"git-cz"},"config":{"commitizen":{"path":"cz-conventional-changelog"}}}

次にhuskyにCommitizenの設定を記述します。
注意: これらの設定はMac/Linuxのみの設定です。Windowsでは機能しませんのでご注意ください。

package.json
{"husky":{"hooks":{"prepare-commit-msg":"exec < /dev/tty && git cz --hook || true"}}}

これでCommitizenの導入は完了です。npm run czで専用のプロンプトが立ち上がり、コミットすることができます。また、Mac/Linuxの場合はgit commitでも専用のプロンプトが立ち上がります。

Semantic Pull Requests

コミットメッセージだけでなくPull RequestのタイトルもConventionalにしましょう。
Semantic Pull Requestsを用いるとPull RequestのタイトルにLintを書けることができます。

導入方法

こちらからGitHub Appをインストールできます。

また、.github/semantic.ymlで設定をすることができます。
詳しい設定は公式READMEをご覧ください。

Amplify CLIがamplify: command not foundで進まない件

$
0
0

結論:PATHが通ってませんでした

この記事はamplify -vとかしても

amplify: command not found

と出てしまう人向けです。

まずは

npm config get prefix

でnpmモジュールのインストール先を確認します。
ここでは /Users/hogehoge/.global とします。
次に

echo $PATH

として、その中に先程のパスが含まれているか確認。多分含まれていないはず。

あとは

export PATH=$HOME/.global:$PATH

としてパスを追加。
これでnpmでインストールしたモジュールまでパスが通ったので
amplifyのコマンドが通るようになります。

Expressでのエラーハンドリング ベストプラクティス

$
0
0

Express公式サイトのベストプラクティスには、パフォーマンスと信頼性についてのベストプラクティスが解説されています。

その中で、適切なエラーハンドリングのベストプラクティスについて解説されています。

Express(Node.js)では発生したエラーがキャッチされないと、プロセスが異常終了したりハングしてしまいます。そうなると、Epxressアプリケーションの信頼性(可用性)が地に落ちてしまいます。このようにエラーハンドリングの適切さは信頼性に大きく影響するため、エラーハンドリングはとっても重要なのです。

今回の投稿では、Express公式サイト(と、そこからリンクされる参考ページ)で紹介されているエラーハンドリングのベストプラクティスを、できるだけ分かりやすく説明させていだこうと思います。

まずはアンチパターンいついて説明します。

Expressにおけるエラーハンドリング【アンチパターン】

その①:コールバック関数で、手動でnextを呼び出す。

非同期関数(以下の例ではqueryDb(), makeCsv())を呼び出した後のエラーハンドリングでは、コールバック関数でnext関数にエラーを渡して実行する必要があります。

app.get('/',function(req,res,next){queryDb(function(err,data){if(err)returnnext(err)// handle datamakeCsv(data,function(err,csv){if(err)returnnext(err)// handle csv})})})app.use(function(err,req,res,next){// handle error})

しかし、それは以下の点で問題があります。

  • 非同期関数でエラーが発生した場合、nextの呼び出しをうっかり忘れてしまう恐れがある。
  • 非同期関数が正常に処理されたとしても、コールバック中にエラーが起きることはある。その場合に、nextの呼び出しをうっかり忘れてしまう恐れがある。

その②:Promiseを利用する。

そこで、非同期関数を呼び出してPromiseを受け取り、Promiseのcatch()にnext関数を登録すれば良い、ということになります。

app.get('/',function(req,res,next){// do some sync stuffqueryDb().then(function(data){// handle datareturnmakeCsv(data)}).then(function(csv){// handle csv}).catch(next)})app.use(function(err,req,res,next){// handle error})

ただ、この書き方は冗長で読みづらいです。そこで、async awaitを使って読みやすくしてみましょう。

その③:async awaitを利用する。

app.get('/',(req,res,next)=>{(async()=>{letdata=awaitqueryDb()// handle dataletcsv=awaitmakeCsv(data)// handle csv})().catch(next)}))

async関数は、暗黙的にPromiseを返します。このため、

  • queryDb()makeCsv()でエラーがthrowされる
  • async関数内の他の箇所でエラーがthrowされる

といった場合は、throwされたエラーでPromiseがrejectされます。すると、catchに登録された関数nextにそのエラーが渡されてnextが実行されます。(これは直前のパターンでも同じことです。)

これで、Promiseのチェーンで書かれたコードよりは、少し読みやすくなりました。

ただ、このように「async即時関数でcatch」というコードが散在するのはNGでしょう。ということで、冒頭のパターンがベストプラクティスということなのです。

ベストプラクティスに進む前に、念のため論外なパターンも紹介しておきます。

その④:【論外!ダメ絶対!】 ハンドラ関数をasyncにするだけ。

なお、以下のように、ハンドラ関数をasyncにしただけではNGです。

app.get('/',async(req,res)=>{letdata=awaitqueryDb()// handle dataletcsv=awaitmakeCsv(data)// handle csv}))

この場合、queryDb()makeCsv()でエラーがthrowされると、async関数が返すPromiseがrejectされます。しかし、ExpressはrejectされたPromiseをハンドリングしません。

すると、サーバー側の処理は終了となります(UnhandledPromiseRejectionWarningが発生するはずです)。クライアント側(GETメソッドの呼び出し元)には何も返されません。

その結果、多くの場合、呼び出し元はタイムアウトとなります。

Epxressのエラーハンドリング用ミドルウェアでハンドリングするためには、先ほどの例のように、nextが呼び出されるようにする必要があるのです。

Expressにおけるエラーハンドリング【ベストプラクティス】

ベストプラクティスは以下の通りです。

constwrap=fn=>(...args)=>fn(...args).catch(args[2])app.get('/',wrap(async(req,res,next)=>{constcompany=awaitgetCompanyById(req.query.id)conststream=getLogoStreamById(company.id)stream.on('error',next).pipe(res)}))

パーツごとに説明していきます。

まずは以下の部分です。

constwrap=fn=>(...args)=>fn(...args).catch(args[2])

wrap関数は、fnを受け取り、(...args) => fn(...args).catch(args[2])という関数を返します。私がはじめにこれを見たときは「カリー化なのかな・・」と思ったのですが、そうではありませんでした。

今回では、fnにはPromiseを返す関数が指定されます。wrap関数の引数に指定されているasync (req, res, next) => …という関数が、正にこれに当たります。async関数は暗黙的にPromiseを返すのでした。

次に、

(...args)=>fn(...args).catch(args[2])

という関数では何をやっているのでしょうか?

それは以下のとおりです。

  • 今回の場合、fnにはasync関数が指定されます。async (req, res, next) => …のことです。
  • 引数...args(今回の場合は、expressから渡されるreq, res, next)をfnに渡してfnを実行します。
  • fnを実行するとPromiseが返ります。そのPromiseのcatchにargs[2]、つまりexpressから渡される引数の3番目であるnextを指定しています。nextは次のExpressミドルウェアを呼び出す関数です。Promiseでrejectとなった場合のコールバックとして、nextを登録しているのです。

fn(つまりasync (req, res, next) => …)の実行中にエラーが発生すると、Promiseがrejectされます。すると、catchに登録したコールバックであるnextが実行されます。発生したエラーは、nextに引数として渡されます。

その結果、Expressのエラーハンドリング用のミドルウェアが実行され、無事にエラーが捕捉されるというわけです。

この技法を使うことで、「async即時関数でcatch」というコードが散在してしまう、という事態を避けることができます。

また

stream.on('error',next).pipe(res)

についても説明します。

これはストリームなどを使う場合のみ該当する話なのですが、async関数の中であっても、イベント・エミッター (ストリームなど) により、例外がキャッチされないことがあります。そこでストリームのerrorイベントが発生したときに、確実にnextが呼び出されるようにしています。

注意点

上記のベストプラクティスが通用するのは、async関数内で呼び出す非同期関数がPromiseを返す場合だけです。awaitはあくまでもPromiseがresloveされるかrejectされるのを待つ機能だからです。

ですので、上記のasync関数内でPromiseを返さない非同期関数を呼び出す場合、はじめに説明したアンチパターンと同じことになってしまいます。つまり、コールバック関数でnext(err)の実装を忘れてしまう、というリスクがあります。

今どき、Promiseを返さない非同期関数を使わなきゃいけない、というケースは少ないかなと思いますが、もしそういったケースに当てはまる場合は、以下のうちいずれかを選択しましょう。

  • Promiseを返さない非同期関数から、Promiseを返す関数を自動生成し、後者を呼び出すようにする。Node.js標準のutil.promisify()を使うことで、自動生成を実現できます。こちらが参考になります。(以前は、Bluebird等のライブラリで実現していましたが、Node.js 8から標準で組み込まれました。)
  • 「Promiseを返さない非同期関数」をラップした関数を定義して、Promiseを返すようにする。こちらが参考になります。
  • 1つ目のアンチパターンの難点を受け入れ、コールバック関数でnext(err)を忘れずに実装する、と堅く誓う(ソースコードレビューが大変…)。

参考

https://expressjs.com/ja/advanced/best-practice-performance.html#handle-exceptions-properly

https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/

https://developer.ibm.com/articles/promises-in-nodejs-an-alternative-to-callbacks/

http://blog.manaten.net/entry/util-promisify

以上です。Expressのハンドラ関数で発生するエラーを、確実にハンドリングしていきましょう!

都道府県名を都道府県コードに変換

$
0
0

都道府県名を次で定義されている都道府県コードに変換する方法です。
都道府県コード

変換に使うJSONファイル

prefecture.json
{"北海道":1,"青森県":2,"岩手県":3,"宮城県":4,"秋田県":5,"山形県":6,"福島県":7,"茨城県":8,"栃木県":9,"群馬県":10,"埼玉県":11,"千葉県":12,"東京都":13,"神奈川県":14,"新潟県":15,"富山県":16,"石川県":17,"福井県":18,"山梨県":19,"長野県":20,"岐阜県":21,"静岡県":22,"愛知県":23,"三重県":24,"滋賀県":25,"京都府":26,"大阪府":27,"兵庫県":28,"奈良県":29,"和歌山県":30,"鳥取県":31,"島根県":32,"岡山県":33,"広島県":34,"山口県":35,"徳島県":36,"香川県":37,"愛媛県":38,"高知県":39,"福岡県":40,"佐賀県":41,"長崎県":42,"熊本県":43,"大分県":44,"宮崎県":45,"鹿児島県":46,"沖縄県":47}

bash での使用例

test_bash.sh
##for name in{"北海道","宮城県","栃木県","静岡県","広島県"}do
    echo-n$name"  "
    jq .\"$name\" prefecture.json
done#

実行結果

$ ./test_bash.sh 
北海道   1
宮城県   4
栃木県   9
静岡県   22
広島県   34

Python3 での使用例

test_python.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
#
#   test_python.py
#
#                   Apr/29/2020
#
# ------------------------------------------------------------------
importsysimportosimportjson#
#
# ------------------------------------------------------------------
deffile_to_str_proc(file_in):str_out=""try:fp_in=open(file_in,encoding='utf-8')str_out=fp_in.read()fp_in.close()exceptExceptionasee:sys.stderr.write("*** error *** file_to_str_proc ***\n")sys.stderr.write(str(ee))#
returnstr_out# ------------------------------------------------------------------
sys.stderr.write("*** 開始 ***\n")#
prefecture_json=sys.argv[1]#
sys.stderr.write(prefecture_json+"\n")#
json_str=file_to_str_proc(prefecture_json)dict_pref=json.loads(json_str)#
name_aa=["北海道","宮城県","栃木県","静岡県","広島県"]fornameinname_aa:code=dict_pref[name]print(name,code)#
sys.stderr.write("*** 終了 ***\n")# ------------------------------------------------------------------

実行結果

$ ./test_python.py prefecture.json
*** 開始 ***
prefecture.json
北海道 1
宮城県 4
栃木県 9
静岡県 22
広島県 34
*** 終了 ***

node.js での使用例

test_node.js
#! /usr/bin/node
// ---------------------------------------------------------------//  test_node.js////                  Apr/29/2020//// ---------------------------------------------------------------varfs=require("fs")// ---------------------------------------------------------------console.error("*** 開始 ***")constprefecture_json=process.argv[2]console.log(prefecture_json)constjson_str=fs.readFileSync(prefecture_json,'utf8')constdict_pref=JSON.parse(json_str)name_aa=["北海道","宮城県","栃木県","静岡県","広島県"]for(varitinname_aa){constname=name_aa[it]constcode=dict_pref[name]console.log(name,code)}console.error("*** 終了 ***")// ---------------------------------------------------------------

実行結果

$ ./test_node.js prefecture.json
*** 開始 ***
prefecture.json
北海道 1
宮城県 4
栃木県 9
静岡県 22
広島県 34
*** 終了 ***

Go の使用例

test_go.go
// ---------------------------------------------------------------////  test_go.go////                  Apr/29/2020// ---------------------------------------------------------------packagemainimport("os""fmt""encoding/json""io/ioutil")// ---------------------------------------------------------------funcmain(){fmt.Fprintf(os.Stderr,"*** 開始 ***\n")file_json:=os.Args[1]fmt.Printf("%s\n",file_json)buff,_:=ioutil.ReadFile(file_json)json_str:=string(buff)vardict_prefmap[string](int)json.Unmarshal([]byte(json_str),&dict_pref)name_aa:=[5]string{"北海道","宮城県","栃木県","静岡県","広島県"}for_,name:=rangename_aa{code:=dict_pref[name]fmt.Printf("%s\t%d\n",name,code)}fmt.Fprintf(os.Stderr,"*** 終了 ***\n")}// ---------------------------------------------------------------

実行結果

$ go run test_go.go prefecture.json
*** 開始 ***
prefecture.json
北海道   1
宮城県   4
栃木県   9
静岡県   22
広島県   34
*** 終了 ***

Boxウェブアプリ統合を作ってみた

$
0
0

今回作るもの

Boxのウェブアプリ統合をnode.jsで作ってみます。

ウェブアプリ統合とは、標準のBoxの画面にボタンを表示し、そのボタンをクリックしたら外部で連携されたアプリケーションが何らかの処理を行うという仕掛けのことです。

Boxのテナントの設定にもよりますが、DocuSignなど、予めおすすめのボタンがいくつかついていたりします。あのボタンは自前で追加可能です。

今回は、任意のファイルを、3分間ロックしてダウンロードも禁止にするという使いみちのないウェブアプリ統合を作ってみます。Boxにはそもそも編集中に他人からファイルを変更されないように悲観ロックを行う機能がついているのですが、単純にこの機能を外部から利用してロックをかけるというものを作ってみます。

仕組み

基本的なしくみとしては以下のような流れです。

  1. 外部に自前のカスタムアプリケーション・サーバーを用意する。
  2. Boxの開発者コンソールからアプリの登録をし、ウェブアプリ統合を有効にする。
  3. Boxの画面からウェブアプリ統合のボタンを押すと、カスタムアプリケーション・サーバーにHTTPでリクエストが飛ぶ。多くの場合カスタムアプリケーション側からBoxにアクセスすると思うが、AuthCodeを飛ばせるので、これをaccessTokenに変換することで、Box側にアクセスが可能になる
  4. カスタムアプリケーションで好きな処理を行う

上記の流れは、基本的にWebhookや、Skillsでも細かい差異はありますが、基本の流れはほぼ同じようです。
ウェブアプリ統合のトリガーはボタンのクリックなのに対して、WebhookやSkillsのトリガーはコンテンツのライフサイクルフックなところも異なりそうです。
今回のサンプルでは、カスタムアプリケーションをHerokuに置き、node.js、box-node-sdkを利用して作ってみます。

事前に必要なもの

今回のウェブアプリ統合を試すためにひつようなものは、以下の通りです。

  • Node.js
  • Herokuのアカウント登録(無料)と、herokuコマンド
  • git

手順

Boxでアプリケーションの作成

開発者コンソールを開き、アプリケーションを作成します。

「カスタムアプリ」、または「企業統合」のどちらかを選びます。
どちらを選ぼうが、認証方式を、「標準OAuth 2.0(ユーザー認証)」を選べば大丈夫です。
OAuth 2.0資格情報を後で使うのでひかえておきます。
統合に関しての設定も後で必要になりますが、外部アプリケーションを作成してから設定します。

外部アプリケーションの準備

外部アプリケーション用のプロジェクトを作ります。

# 任意のフォルダを作りますmkdir web-integ-file-locker
cd web-integ-file-locker

# yarnでもnpmでもおすきなものを・・
yarn init -y# box-node-sdk、express、momentを追加しておきます 
yarn add box-node-sdk express moment

生成されたpackage.jsonを改造する scripts/startを入れる。(Herokuがstartを実行する)

package.json
{"name":"web-integ-file-locker","version":"1.0.0","main":"app.js","license":"MIT","scripts":{"start":"node app"},"dependencies":{"box-node-sdk":"^1.32.0","express":"^4.17.1","moment":"^2.24.0"}}

任意のエディタで、web-integ-file-lockerフォルダ直下にapp.jsを作ります。
認証に関しては、標準OAuth2.0 (3-legged)のやり方になります。
https://github.com/box/box-node-sdk/blob/master/docs/authentication.md#traditional-3-legged-oauth2

constexpress=require("express");constmoment=require("moment");constboxSDK=require("box-node-sdk");constapp=express();app.use(express.urlencoded({extend:false}));app.post("/lockfile",async(req,res)=>{try{// clientIDと、clientSecretは、構成のクライアントIDとクライアント機密コードconstsdk=newboxSDK({clientID:process.env.clientID,clientSecret:process.env.clientSecret,});// 統合のメニューで、コールバックパラメーターにPOSTで指定したものが、bodyの下に入る// authCodeというのは、本来、BOXのログイン画面にリダイレクトして認証が通った後に戻されるAuthCode(だと思う)。// ここでは認証画面はスキップしてる。constauthCode=req.body.authCode;constfileId=req.body.fileId;// authCodeをBoxAPIに投げて、tokenInfoを取得する。// authCodeはaccessTokenではないので、accessTokenをつかってClientを作っても動かない。// tokenInfoは、accessToken, refreshToken等が含まれているオブジェクト。consttokenInfo=awaitsdk.getTokensAuthorizationCodeGrant(authCode);// box-node-sdkの場合、sdk.getPersistentClient(tokenInfo, tokenStore)// を利用して、accessTokenが期限切れになると自動的にrefreshTokenをつかって// リフレッシュする仕組みも使えるが、単発の処理で終わるWebApp統合では不要なため、// シンプルに今とれたaccessTokenの生存期間内に処理が終わる前提で、// basicClientにaccessTokenのみを渡して利用する。constclient=sdk.getBasicClient(tokenInfo.accessToken);// lockに渡すオプション。今から3分後までロックし、ダウンロードも禁止するconstoptions={expires_at:moment().add(3,"minutes").format(),is_download_prevented:true,};// 受け取った対象のファイルに対してロックをかけるawaitclient.files.lock(fileId,options);res.status(200).json({message:"file locked",});}catch(e){res.status(500).json({error:e.message});}});constPORT=process.env.PORT||"4000";app.listen(PORT,(result)=>{console.log("Express Server started on port: "+PORT);});

Herokuにアプリケーションを作り、デプロイします。

web-integ-file-lockerフォルダ直下で実行。

# Heroku上にアプリケーションを作る
heroku create
# (オプション)わかりやすいようにリネームしておく
heroku rename web-integ-file-locker
# gitを有効にする
git init
# gitにここまでの変更をCommitする
git add .
git commit -m"init"# Herokuにデプロイする
git push heroku master
# Herokuに環境変数を設定する。構成のOAuth 2.0資格情報です。(スペース区切りで複数セット可能)
heroku config:set clientID=0gde912gixav1244tjm5gbxxxxxxxxxx clientSecret=kIJC8i9avpsVXlB5SolnNxxxxxxxxxx

# HerokuのURLを確認(後で統合の設定で使う)
heroku info

これで外部アプリケーションは出来上がり。

次に、Boxの統合の設定

Boxで統合の設定

最初に作成したアプリの画面に戻ります。

左のタブから統合を選択します。

「ウェブアプリ統合を作成」ボタンを押します。

以下の値を入れます

  • 統合名:FileLocker (任意のもの)
  • 説明:適当な内容
  • コールバック設定 > クライアントコールバックのURL

    • ここに、上記のHeorkuでつくったURL + "/lockfile"を入れます。
    • 例:https://web-integ-file-locker.herokuapp.com/lockfile
  • コールバック設定 > プロンプトメッセージ:(適当なもの)

  • コールバック設定 > ユーザーエクスペリエンス: 統合をサーバー側でのみ実行する に変更

  • コールバックパラメータに以下の項目を追加します

方法パラメータ名パラメータ値
PostAuthCode##auth_code##
PostfileId##file_id##

上記以外は全てデフォルトのままでOK。

スクリーンショット 2020-04-29 22.22.44.png

画面右上のボタンで保存します。

検証

Boxから使ってみる

任意のファイルのコンテキストメニューから統合を選ぶと、FileLockerが表示されています。
スクリーンショット 2020-04-29 22.31.57.png

このボタンを押してみると、確認画面が表示され、、

スクリーンショット 2020-04-29 22.32.55.png

確認画面でOKを押すと、ファイルにロックがかかりました。

スクリーンショット 2020-04-29 22.33.33.png

こんな感じでサクッと統合が試せました。
今回は試していませんが、アップギャラリーに登録して、パブリックに公開することも可能です。
今度、なにか作ってアップギャラリーに登録してみようと思います。

LINEBotをみんなで作ろう〜環境構築編〜【GWアドベントカレンダー1日目】

$
0
0

この記事は下記の #GWアドベントカレンダーの 1日目の記事になります。

楽しそうなのでやってみる!( @inoue2002) | GWアドベントカレンダー

はじめに

全く触った事がない人でも読み進められる記事を目指して、「実際に動くもの」を作ることを重視して解説をしていきます。

最初は環境構築から始め、最終的にはサーバレスで動くLINEBotを作成し、色々な物を作ってもらえたら嬉しいです。
僕の持っている知識を全部書いていこうと思いますのでよろしくお願いします!
もし誤表記やっ訂正すべき点がある場合、コメントいただけると幸いです。
あと、基本的にはMac環境で説明していきますのでよろしくお願いします。
では1週間よろしくお願いします!

今日やる事

LINE Developerのサイトにログインできるようにする。
ngrokをインストールする
node.js(v10以上推奨)をインストールする
gitをインストールする
エディタ(どのエディタでもいいですが、VSCodeをリンクに入れています。) をインストールする

LINE Developerのサイトにログインする

LINE Developerのサイトに行くと以下のような画面が出る
スクリーンショット 2020-04-29 22.23.48.png
自分のLINEアカウントでログインする。
もしメールアドレスが登録していない、わからない時はLINEをスマートフォンから開き、メールアドレスの設定をする。
スクリーンショット 2020-04-29 22.25.29.png
このような画面がでてログインボタンが押せれば成功。

ngrokをインストールする

サイト上部のバーよりダウンロードをクリック、以下のような画面が出ると、お使いの環境に合わせたバージョンをダウンロード。
ダウンロードしたあと、解凍すれば完了。
スクリーンショット 2020-04-29 22.46.09.png

node.jsをインストールする

サイトに行き、推奨板をインストール、解凍すれば準備完了

gitをインストールする

サイトにいき、インストール、解凍するだけ。

VSCodeをインストール

サイトに行ってインストールをポチっと!!

一通りできたら確認

*ターミナルを開く

ngrokのバージョンを調べる

ngrok -v
と打ち込みenterを押す。
ngrok version 2.3.35このような形で表示されればインストールできています。
でてこない場合はインストールできていません。

node.jsのバージョンを調べる

node -v
と打ち込み、enterを押すと
v12.13.0
のように表示されると成功です。

gitのバージョンを調べる

git --version
と打ち込みenterを押すと
git version 2.23.0のように表示されると成功です。

エディタがインストールできたか確認

上記のエディタをインストールするに書いたリンクからインストールした人は以下のアイコンがパソコン内にあれば成功!
スクリーンショット 2020-04-29 22.12.23.png

最後に

これらを使って明日からLINEBotを作っていきます!よろしくお願いします!

Node.jsを使ってHTTPサーバを作ってみる

$
0
0

はじめに

この記事はなんとなくJSとかを勉強している学生がメモ代わりに書いているものです。内容は期待しないでください。

1.仮想環境を構築する

今回はUbuntuで行うのでiTerm2で仮想環境を起動する。

起動したところで
1.Virtual Box(バーチャルボックス)
2.Vagrant(ベイグラント)
という2つのソフトウェアを使った仮想環境でUbuntuを使用します。
cd ~/vagrant/ubuntu
vagrant up
vagrant ssh

Ubuntuがインストールされたディレクトリに移動。vagrant upは仮想的なPCにインストールされたUbuntuを起動するコマンドで,vagrant sshはVagrantの仮想マシンがセットされている状態でSSHに接続します。

2.プログラムのひな形(テンプレート)を用意する

ディレクトリ内に以下を記述します。
yarn init
echo "'use strict';" > ファイル名

1行目はyarnで新しいプロジェクトを始める際に記述するものです。

3.プログラムを書く

先ほど記述したファイルに以下を記述します。
'use strict';
const http = require('http');
const server = http.createServer((request, response) => {
response.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
});
response.write(request.headers['なんでもいい']);
response.end();
});
server.listen(8000, () => {
console.log('Listening on 8000' );
});

1行目はJSをstrictモードで利用するための記述です。

2行目はhttpモジュールを引数httpに代入しています。

3~9行目はサーバに関する記述です。
引数に代入したhttpモジュールを使ってサーバを構築しています。記述方法は以下参照。
http.createServer( サーバー側の処理 )

今回はサーバ側の処理でアロー関数を使用しており,引数の一つ目にはリクエストが二つ目にはレスポンスが代入されています。
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
});

このコードは200という成功を示すステータスコードと共に、サーバが扱う情報の設定をレスポンスヘッダを書き込んでいます。
7行目はwrite関数を使用してリクエストのヘッダーに文字列を表示しています。表示したい内容によっては以下のように書くこともできます。
res.write(
'<!DOCTYPE html><html lang="ja"><body><h1>文字列</h1></body></html>'
);

8行目はサーバの書き出しが終わったことを示しています。

10行目以降はサーバ起動するポートを8000としてlisten関数で特定のポートからリクエストがないかを永続的に調べています。今回はリクエストがあり次第コンソールで文字列を表示しています。

4.サーバを起動してみる

以下を記述してREPLで動作を確認します。
node ファイル名

先ほどのコンソールの文字列が表示されたら成功です。お疲れ様でした!!

yarnとは

yarnはnodeをインストールすると自動でインストールされるnpmと役割は同様のパッケージマネージャー(https://yarnpkg.com/en/)。 
並行処理でのインストールによってnpmよりも高速にパッケージをインストールできる。

httpモジュールとは

「httpモジュール」はHTTPサーバーやHTTPクライアントとしての機能を構築するために使われます。自分のwebサイトをネット上に公開したり、フォームなどからデータを送受信できます。もちろん、静的なWebサイトだけでなくTwitterのような大きなWebサービスを構築することも可能になります。

ポートとは(参照:https://www.nic.ad.jp/ja/basics/terms/port-number.html)

TCP/IP通信においては,IPアドレスがあればネットワーク上のコンピュータを一意に識別することができますが,該当コンピュータのどのプログラムに通信パケットを届けるかは,IPアドレスだけでは決定できません。どのプログラムに通信パケットを渡すのかを決定するために,ポート番号を使用します。

Puppeteerでexampleコードsearch.jsを試すときにハマったこと(動的サイトのセレクタ確認、Headless解除でのデバッグ)

$
0
0

はじめに

スクレイピングをnode.jsでできるPuppeteer(スペルミス多発)を初心者が使ってみたところ、
私的なハマりどころが複数あったので備忘録として残します。

ハマりポイント(順番に)

  1. search.jsは2018年に作成されたものなので、一部スクレイピング対象サイトのセレクタが変わっていた。
  2. 検索窓に入力した際のポップアウトが動的で、developer toolでDOMをelementを見つけるのに苦労した。
  3. レスポンシブデザインのbreakpointのサイズを考えずに組んでいたので、Headlessで立ち上がっているChromeと挙動の差異があったことに気付いていなかった(Navigation Drawerが出てきてしまっていた)。

解決策

  1. ちゃんとdeveloper toolでelementを確認する。
  2. JavaScriptのdebugと同じ要領でBreakpointを設定してポップアウトが出た瞬間に止めて、elementを確認する。
  3. Headlessモードを解除し、実際にChromeが動いている様子を確認してロジックを組む。

3についてはpuppeteerにより立ち上がるChromeのブラウザサイズを指定することで対応できないかと思ったのですが、
自分が調べた範囲では、スクリーンショットのサイズは変更可能ですが、ブラウザサイズの変更はできないみたいです。
(もしどなたか、やり方を知っていたら教えてください)

結局のところ

  1. developer toolsを使って対象サイトの構成をしっかり把握しよう
  2. Breakpointを駆使しよう(JS書くならデバッグのやり方把握しないとですよね)
  3. HeadfullでChromeの挙動を確認しよう

対象

  • Puppeteerの初心者
  • Puppeteerのexampleのsearch.jsを試そうとしたけどそのままでは動かなかった人

私のレベルとしてはDOM操作の一通りの知識があり、バックエンドはnodeとExpressを少し触った程度です。
用語の正確性がボロボロかもしれませんのでよろしければご指摘ください。

開発環境

一つ一つ見ていきます

Puppeteerの導入については公式のgithubをご覧ください
https://github.com/puppeteer/puppeteer

examplesの実行については以下となります。

Assuming you have a checkout of the Puppeteer repo and have run npm i (or yarn) to install the dependencies, the examples can be run from the root folder like so:
NODE_PATH=../ node examples/search.js

https://github.com/puppeteer/puppeteer/tree/master/examples
こちらからsearch.jsを見てみます。

search.js
'use strict';constpuppeteer=require('puppeteer');(async()=>{constbrowser=awaitpuppeteer.launch();constpage=awaitbrowser.newPage();awaitpage.goto('https://developers.google.com/web/');// Type into search box.awaitpage.type('#searchbox input','Headless Chrome');// Wait for suggest overlay to appear and click "show all results".constallResultsSelector='.devsite-suggest-all-results';awaitpage.waitForSelector(allResultsSelector);awaitpage.click(allResultsSelector);// Wait for the results page to load and display the results.constresultsSelector='.gsc-results .gsc-thumbnail-inside a.gs-title';awaitpage.waitForSelector(resultsSelector);// Extract the results from the page.constlinks=awaitpage.evaluate(resultsSelector=>{constanchors=Array.from(document.querySelectorAll(resultsSelector));returnanchors.map(anchor=>{consttitle=anchor.textContent.split('|')[0].trim();return`${title} - ${anchor.href}`;});},resultsSelector);console.log(links.join('\n'));awaitbrowser.close();})();

コードの説明は省きますが、コメントアウトで書かれている通りの流れとなっています。
https://developers.google.com/webにアクセスして検索窓に入力し、出てくるポップアウトの中のボタンを押し、遷移したページの結果を取得するというものです。

1. スクレイピング対象サイトのHTML Elementが変わっていた問題への対応

さて、このまま実行すると

UnhandledPromiseRejectionWarning: Error: No node found for selector: #searchbox input

といったエラーが出ます。2018年段階ではsearchboxというIDで選択できたのですが、2020/4/29時点では変更されています。
修正しなければいけない部分は以下になります。検索窓を見つけてpage.type()で入力する処理です。

search.js
'use strict';constpuppeteer=require('puppeteer');(async()=>{// 省略// Type into search box.awaitpage.type('#searchbox input','Headless Chrome');//<- ここ!//省略})();

ChromeのDeveloper toolsを開き、検索窓をinspectします。
以下の画像のようになっています。赤い部分が検索窓に対応するHTML Elementです。
Screen Shot 2020-04-30 at 9.41.37.png

ここで注意することとしては、複数のElementと被らないIDやclassをセレクタすることです。

私が試したパターンは以下の三つとなります。

search.js
// await page.type('input[name="q"]', 'Headless Chrome'); //OK// await page.type('.devsite-search-field', 'Headless Chrome'); //OKawaitpage.type('.devsite-search-query','Headless Chrome');//OK

以上のどれかに変更することで第一関門は突破できました。
この時は以下のコードをpage.type()の下に記述して確認しておりました。

awaitconsole.log('type success')

もしくはスクリーンショットを以下のように撮ることで確認しておりました。

awaitpage.screenshot({path:'search.png',fullPage:true});

しかし、後述しますが、デバッグはHeadlessを解除して行えば一発です。

2. 検索窓入力後のポップアウトのHTML Elementの取得

developer toolsで普通にやるとポップアウト部分のHTML Elementを見ようとするとポップアウトが消えてしまいます。
dynamic html.gif

そこで、以下の記事を参考にしてBreak inを設定します。そうすることによって、JavaScriptが中断され、ポップアウトが開いたままになります。
How to Inspect Dynamic HTML Elements (that keep disappearing!) in Chrome
breakin.gif

ここでは結果的に、以下のexampleコードのままで良いことが判明しました。
classが2018年と変更なしということです。

search.js
'use strict';constpuppeteer=require('puppeteer');(async()=>{//省略// Wait for suggest overlay to appear and click "show all results".constallResultsSelector='.devsite-suggest-all-results';awaitpage.waitForSelector(allResultsSelector);awaitpage.click(allResultsSelector);//省略})();

BreakpointsはDOM Breakpointsから削除しておきます。もしくは更新すれば消えます。
DOM breakpoints.png

3.Headlessを解除してHeadfullでデバッグを行う

ここまでクリアすれば問題ないと思っていたのですが、以下のエラーが出てきてしまいました。

UnhandledPromiseRejectionWarning: TimeoutError: waiting for selector ".devsite-suggest-all-results" failed: timeout 30000ms exceeded

結果的に解決策としては、puppeteer.launch()puppteer.launch({slowMo:100})として、一つ一つの実行に100ms挟むか、
もしくは、以下のようにtypeする前の部分で、検索アイコンをクリックして検索窓を開かせることで解決しました。

search.js
'use strict';constpuppeteer=require('puppeteer');(async()=>{// 省略constsearchIconSelector='.devsite-search-button'awaitpage.waitForSelector(searchIconSelector);awaitpage.click(searchIconSelector);// Type into search box.awaitpage.type('.devsite-search-query','Headless Chrome');//省略})();

重要なのはHeadfullにして、実際に動いているChromeの挙動を確認することでした。

Headlessを解除してHeadfullにするには以下のようにします。

puppeteer.launch({headless:false})

そうすると、Headfullで立ち上がり、挙動を確認することができます。
headfull.gif

このブラウザのサイズは800px × 600pxで固定とのことです。
私が調べた範囲ではスクリーンショットサイズは変更できましたが、ブラウザサイズは変えられませんでした。
レスポンシブデザインの場合は横幅が800pxの状態でどう表示されるか確認する必要があると思います。

また、他の私のハマったところとしては、検索アイコンをクリックして検索窓を開かせる場合に、Navigation Drawerにも適用されているclassを指定してしまったことです。

constsearchIconSelector='.devsite-search-button'//OK// const searchIconSelector = '.devsite-header-icon-button' // NG

上のNGの方を使うと、以下のようにNavigation Drawerを開いてしまい、入力はできても、サジェストの全結果を開くボタンを押すことができませんでした。

error_navigationdraw.gif

最終結果は以下のように出力されたら成功です。

Getting Started with Headless Chrome - https://developers.google.com/web/updates/2017/04/headless-chrome
Automated testing with Headless Chrome - https://developers.google.com/web/updates/2017/06/headless-karma-mocha-chai
Headless Chrome: an answer to server-side rendering JS sites - https://developers.google.com/web/tools/puppeteer/articles/ssr
Puppeteer - https://developers.google.com/web/tools/puppeteer
Chrome DevTools Protocol - https://developers.google.com/chrome-developer-tools/docs/debugger-protocol
Examples - https://developers.google.com/web/tools/puppeteer/examples
New in Chrome 59 - https://developers.google.com/web/updates/2017/05/nic59
All Updates tagged: headless - https://developers.google.com/web/updates/tags/headless?hl=ja
Running the examples - https://developers.google.com/web/tools/puppeteer/examples?hl=ja
Troubleshooting - https://developers.google.com/web/tools/puppeteer/troubleshooting?hl=ja

この後の調査予定

  • 他のexampleも試し、詰まりながら仕様を理解していく。

長くなりましたが以上となります。お疲れ様でした。

参考記事まとめ

Node - ES6 imports cannot find module

$
0
0

Problem

index.js
importHttpConnectfrom'./src/Library/Connect/HttpConnect';.........
node --experimental-modules index.js
$ node --experimental-modules index.js
internal/modules/run_main.js:54
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '\home\johnny\test\src\Library\Connect\HttpConnect' imported from \home\johnny\test\index.js
    at finalizeResolution (internal/modules/esm/resolve.js:272:11)
    at moduleResolve (internal/modules/esm/resolve.js:652:10)
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:695:13)
    at Loader.resolve (internal/modules/esm/loader.js:97:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:243:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:42:40)
    at link (internal/modules/esm/module_job.js:41:36) {
  code: 'ERR_MODULE_NOT_FOUND'

Solution

index.js
importHttpConnectfrom'./src/Library/Connect/HttpConnect.js';<-includingthefileextension.........

https://stackoverflow.com/questions/60059121/nodejs-es6-imports-cannot-find-module

Node.jsのrequest-promiseモジュールでresponseの内容が表示できずはまった件

$
0
0

やりたかったこと

Node-REDでリビングのGoogle homeにメッセージを喋らせるREST APIを作り、Node.jsでそのREST APIを呼び出すプログラムを作りたかった。REST APIの呼び出しにはrequest-promiseモジュールを利用。

はまったところ

REST APIを以下のコードで呼び出したが、responseの内容を表示しようとしてもコンソールにundefinedと表示されてしまう。

 var options = {  
        url: 'http://XXX.XXX.XXX.XXX:1880/sendtogh',  
        method: 'POST',  
        form: {"message": message},  //前段の処理でmessageにgoogle homeに喋らせたい内容をセット
      }  
    request(options)  
    .then(function(response){  
        console.log(response.statusCode);  //コンソールに「undefined」と表示される  
    })  
    .catch(function(err){  
    (省略)

解決方法

request-promiseのドキュメントを見ると、optionsにresolveWithFullResponse: trueを指定しないと、bodyの内容しか返却されないとのこと。
https://github.com/request/request-promise#get-the-full-response-instead-of-just-the-body

さくらVPSにmaria db + nginx + headlessCMSのstrapiを入れる

$
0
0

さくらインターネットでVPSを契約

基本環境はCentOS7を選択

nginx インストール

yum -y install nginx

marina dbをインストール

yum install mariadb-server
systemctl enable mariadb
systemctl start mariadb

Node.js インストール

curl -L git.io/nodebrew | perl - setup
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
source ~/.bash_profile
nodebrew install-binary stable
nodebrew use stable

strapiのインストール

npm install strapi@alpha -g
cd /usr/share/nginx/html
strapi new blog

nginxの設定の追加

/etc/nginx/nginx.conf
location/{proxy_passhttp://localhost:1337;proxy_http_version1.1;proxy_set_headerUpgrade$http_upgrade;proxy_set_headerConnection'upgrade';proxy_set_headerHost$host;proxy_cache_bypass$http_upgrade;}

pm2による自動起動の設定

https://qiita.com/kuryus/items/fbdc373f23d3236ebb04

nuxtのプロジェクト作成

$ yarn create nuxt-app my_blog
yarn create v1.15.2
[1/4] 🔍  Resolving packages...
warning create-nuxt-app > sao > download-git-repo > download > mkdirp@0.5.4: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
warning create-nuxt-app > sao > micromatch > snapdragon > source-map-resolve > resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
warning create-nuxt-app > sao > micromatch > snapdragon > source-map-resolve > urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
warning Your current version of Yarn is out of date. The latest version is "1.22.4", while you're on "1.15.2".
info To upgrade, run the following command:
$ brew upgrade yarn
success Installed "create-nuxt-app@2.15.0" with binaries:
      - create-nuxt-app

create-nuxt-app v2.15.0
✨  Generating Nuxt.js project in my_blog
? Project name my_blog
? Project description My world-class Nuxt.js project
? Author name Rickey
? Choose programming language JavaScript
? Choose the package manager Yarn
? Choose UI framework Vuetify.js
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)

Netlifyの登録

https://qiita.com/isihigameKoudai/items/e3b136e9964f1d30d73d
https://ja.unflf.com/tech/netlify/route53/


Node.jsを使ってサーバにアクセスログを出力してみた

$
0
0

はじめに

この記事はなんとなくJSなどを勉強している学生がメモ代わりに書いている記事です。内容は期待しないでください。

1.仮想環境を構築する。

今回はUbuntuで行うのでiTerm2で仮想環境を起動する。

起動したところで
1.Virtual Box(バーチャルボックス)
2.Vagrant(ベイグラント)
という2つのソフトウェアを使った仮想環境でUbuntuを使用します。
cd ~/vagrant/ubuntu
vagrant up
vagrant ssh

Ubuntuがインストールされたディレクトリに移動。vagrant upは仮想的なPCにインストールされたUbuntuを起動するコマンドで,vagrant sshはVagrantの仮想マシンがセットされている状態でSSHに接続します。

2.作成したサーバにログを表示する

以下の内容をhttp.createServerのアロー関数内に記述します。
console.info(
'[' + new Date() + '] Requested by ' + reqest.connection.remoteAddress
);

今回はreqestという変数にサーバへのリクエストを代入しています。今回はリクエストが送られたIP情報を出力します。console.infoというものを使用していますがinfoは以下のように分類されます。

関数名内容出力
info,log普段から残す情報標準出力
warn,error警告エラー標準出力

3.作成したサーバにエラーを出力する

on('error', (e) => {
console.error('[' + new Date() + '] Server Error', e);

以上のon関数をアロー関数後に記述します。

4.実際にサーバを起動してみる

node ファイル名

実際にサーバを起動してエラー等のコンソールが表示されれば成功です。

初心者がWordPressでタイピングのリアルタイム対戦ゲームを作ってみました

$
0
0

はじめに

この記事は、WordPressでしかホームページを作れないプログラミング初心者が、無理やりWordPressを使って、タイピングのリアルタイム対戦ゲームを作ってみた記事です。
alt
タイピング初心者用に、70近くあるステージをクリアしていくモードもあります。

IT系の集まりで、「WordPressでこのサイト作ったよ」って言ったらどよめきが起こり、「Qiitaに書いてみれば?」と言われたので書いてみます。

ちなみに、タイピンガーZというサイトです。

このサイトをWordPressでどうやって作ったのかを、ざっくり書いていこうと思います。

テーマのテンプレート

はるか昔に購入した有料テンプレートを使ってみましたが、改造しすぎて原型をとどめていないので、なんでも良かったと思います。

記事投稿と固定ページテンプレート

タイピンガーZは、対人対戦を除いて、ステージをクリアしていく仕組みになっているのですが、それらのステージはWordPressの記事投稿で作りました。

記事には、敵キャラのセリフだったり、敵の強さだったり、音楽・背景だったりのパラメーターを記入します。
↓こんな感じ

<!--セリフ--><spanid="pan2serifu">(たけしさんブリーフ増えてる・・・)brbr
なんすかそれ,
ブリーフ型ターバンヲ発明シマシタ。22,
ヨガタケシデース22,
カレーガンジーヨガファイア22,
インド人に謝れ,
ヨガソーリー22,
さて、今回もがんばろう。
,*-,*del,((</span><!--速度基準--><spanid="speedlinesetting1">20</span><!--遅い--><spanid="speedlinesetting2">30</span><spanid="speedlinesetting3">40</span><!--早い--><!--ミス基準--><spanid="misslinesettei1">60</span><!--正確性低い--><spanid="misslinesettei2">70</span><spanid="misslinesettei3">85</span><!--正確性高い-->

それぞれのパラメーターは、Javascriptでゲットできるように、idかclassを付けておきます。
セリフに「22」とか、「*del」とか書いてますが、これはJavascript側に何かしてほしいときの自作コマンドみたいなものです。
このコマンドで、セリフを話してるキャラが消えたり、セリフが大きくなったり、BGMが変わったりと、いろいろ操作することができるようにしてます。

また、これだけではただのブログ記事のデザインにしかならないので、自分で作った固定ページテンプレートを当てます。

「初心者練習ステージテンプレート」「CPU対戦用テンプレート」などをPHPで書いて用意しました。

あとはパラメーターを書いた記事を大量に投稿して、それぞれに固定ページテンプレートを当てれば、ステージがどんどん増えていきます。

alt

リアルタイム対戦

リアルタイム対戦を導入するにあたってNode.jsを使いたかったのですが、僕が使っているロリポップレンタルサーバーが対応してなかったので、仕方なくAWSも使うことにしました。

AWSを「オッス」と読んでたくらい何も知識がなかったのですが、1年目が無料なのと、2年目以降もだいぶ格安っぽい感じだったので使うことにしました。

AWSでNode.jsを導入するにあたって、下記の記事をそのままやりました。

AWS EC2でNodeを動作させる

この記事でやってることが何なのか、何も理解しないまま人形のように作業したのですが、なんと普通にNode.jsが動きました。
記事の筆者さんありがとう。

しかし、この記事のままだとhttpsでのやり取りができず、そこではまりました。
何も理解しないまま進めると、応用が利かないから困ります。

なんとか解決したものの、どうやって解決したのか忘れてしまったという。。。
たしかロードバランサ―の部分でhttpsを受け入れてなかったとか、そんな感じだった気がします。

Socket.io

Node.jsのライブラリであるSocket.ioを使って、リアルタイム対戦を実現しました。
Socket.ioまじですごい。アホでも使えるようにしてくれてる。

基本的には、下の四つだけ覚えたら使えました。

.on() 受信
.emit() 送信
.broadcast() 自分以外に送信
.join() 部屋作る

Firebaseとやらがもっと簡単という噂を聞きましたが、Socket.ioも初心者でも扱えるとてもシンプルなものでした。

処理のフローをまとめると

無理やりWordpress使って、サーバーも2つ使ってるという構造なので、無駄に複雑です。

リアルタイム対戦の処理をまとめると・・・

1、サーバー1(WordPress)からクライアントにページ情報を送信
2、クライアント側でHTMLとかJavascriptが動く
3、クライアントからサーバー2(AWS、Node.js)に情報送信
4、サーバー2から対戦相手のクライアントに情報送信
5、以下サーバー2を通してクライアント同士で情報送受信

という流れです。

Qiitaの天才達、ありがとう

開発前は、プログラミング素人の自分が、リアルタイム対戦なんてできるのか?
とか思ってましたが、やってみればできるもんです。
10年前だったら不可能だったと思いますが、先人の天才達が素人でもできるように、資料やライブラリを公開してくれているので、なんとかなった感があります。
Qiita、めちゃくちゃ参考にしました。

天才達、ありがとう。

Twitter:@pant2taicho
ホームページ:タイピンガーZ

LINEBotをみんなで作ろう〜LINEBot is 何?編〜【GWアドベントカレンダー2日目】

$
0
0

この記事は下記の #GWアドベントカレンダーの 2日目の記事になります。

楽しそうなのでやってみる ( @inoue2002) | GWアドベントカレンダー

はじめに

こちらの内容は超初心者向けです。
公式ドキュメントを読める方はこちらをお読みいただく方が正確です。

昨日の記事をご覧になってない方はぜひ。
こちらの記事はGWアドベントカレンダーを通してLINEBotをサーバレスで作れるようになろう!ということを目標に書いている記事です。

LINEBotって何なん?って思われる方にまずお読みいただきたいです。
できるだけ専門用語を使わず、噛み砕いて書いていきます。

LINEBotとは

「bot」という単語は robotの短縮形であり、みなさんが想像されるロボットの短縮形という認識で大丈夫です。

ロボット=人間の代わりに仕事をしてくれる

つまり
LINEの中で、人間の代わりにメッセージのやりとりをしてくれるロボット → LINEBot と呼ばれているわけです。

「人間の代わりにメッセージのやりとりをしてくれる」というのは具体的にどんなものでしょうか。
よくみなさんも目にするであろう企業のLINE公式アカウントを思い出してみてください。定期的にメッセージが送られてきたり、トーク内でメッセージを送ったりすると、即答でメッセージが返ってくる経験をした事があるはずです。
最近で有名な公式アカウントを例にあげると、

ローソン: AIを搭載しており、ユーザーに合った商品をレコメンドしてくれる
ローソンのLINEBot
https://www.lawson.co.jp/lab/tsuushin/art/1372267_4659.html

クロネコヤマト: 再配送を依頼したり、荷物がいつ頃届くのか確認したりできる
人狼GM
http://www.kuronekoyamato.co.jp/ytc/campaign/renkei/LINE/

日々増え続けているのでぜひ「LINEBot 企業」などで検索をかけてみると面白いBotに出会えるかもしれません。

少しはLINEBotというものがどういうもので、どんな物に活用されているのかを掴んでもらう事ができましたでしょうか。

ここで、企業は色々と客に対して便利なサービスを提供できるけど、個人で開発したところで何の役にもたたんやろ。と思っておられる方もおられるのではないでしょうか。

実際に筆者が、過去2ヶ月で作ったLINEBotをみていただき、個人開発でどんな事ができるのか。可能性を広げていただければなと思います。こちらの動画では5種類の実際にリリースしたbotを紹介しています。↓登壇動画 2020/4/29
bot紹介動画

LINEBotが動く(Messaging API)の仕組み

Messaging APIを使い、リクエストは、JSON形式でHTTPSを使って送信されます。

1.ユーザーが、LINEBotにメッセージを送信します。
2.LINEプラットフォームからボットサーバーのWebhook URLに、Webhookイベントが送信されます。
3.Webhookイベントに応じて、ボットサーバーからユーザーにLINEプラットフォームを介して応答します。
API

LINEBotでできること

・応答メッセージを送る
・プッシュメッセージを送る
・さまざまなタイプのメッセージを送る
・ユーザーが送ったコンテンツを取得する
・ユーザープロフィールを取得する
・グループチャットに参加する
・リッチメニューを使う
・ビーコンを使う
・アカウント連携を使う
・送信メッセージ数を取得する

メインで使う機能としては、

・応答orプッシュメッセージを送る
・ユーザープロフィールを取得する
・リッチメニューを使う

辺りだと思います。言葉だけでも覚えておくと良いでしょう。

終わりに

明日からサンプルコードを利用しながら、実際にLINEBotを作っていきます!頑張っていきましょう!

Heroku で Node.js アプリを動かすときにつまづいた内容

$
0
0

デプロイ方法などをとりあえず知る。

解決策:公式ドキュメントを読む。サンプルコードを手元にダウンロードして、実際にやってみると良いです。Procfileとか事前に知れるので。
参考:Getting Started on Heroku with Node.js

ClearDBの接続情報がわからない

解決策:heroku configで、特定のフォーマットで教えてくれます。
参考:heroku CLIでMySQL(clearDB)の設定をする

heroku configでエラーが出たとき

原因:コマンド実行するときのディレクトリが間違っていただけでした。
参考:[Herokuエラー]Error: Missing required flagが出た

デプロイ後にApllication Errorが出力されるとき

原因:ポート番号を3000とかで固定していたのが原因でした。
参考:HerokuでNode.jsアプリを実行する際のポート番号について

ClearDBのMySQLだとAuto_Incrementが10ずつ増える

原因:仕様でした。とりあえず練習用に作ったアプリだったので放置。
参考1:herokuでmysql(ClearDB)を使うとidが10ずつ増える
参考2:MySQLのauto_incrementについてメモ

ClearDBから引っぱってきた内容が文字化けしてる現象

原因:文字コードを設定してあげよう。
参考:Heroku+ClearDBで文字化けしていた件

Node.jsでFetch APIで取得したShift_JISのテキストをTextDecoderではなくnpmのパッケージを使ってデコードする

$
0
0

Overview

Node.jsでFetchでHTMLのデータを取得してresponse.text()でテキストを取得したら文字化けが:scream:
どうしてだろうと思ったらHTMLがShift-JIS、かつtext()はまさかのUTF-8専用とのこと。
https://developer.mozilla.org/ja/docs/Web/API/Body/text

レスポンスは常に UTF-8 としてデコードします。

さすがモダンなAPIだな、この割り切り嫌いじゃない:wink:

それならばとブラウザ同様TextDecoderを使ってShift-JISに変換しようとしたらエラー[ERR_ENCODING_NOT_SUPPORTED]が:fearful:
Node.jsのドキュメントみてもShift-JISサポートあるやん!
https://nodejs.org/docs/latest/api/util.html#util_class_util_textdecoder

ググってみたらICUとかいうのが必要なようで…
マイナーなデータは別途提供するから自分で取り込んでねとごもっともな意見。
http://var.blog.jp/archives/80396639.html

私はCloud Build(CI/CD)やCloud Functions(FaaS)上でも動作するようなお手軽なのを求めているため、ICUは使わない方向で検討しました。

ちなみにブラウザの場合は、ブラウザ自体が多数の文字コードを扱っているため工夫いらずで変換できる模様。
@kerupani129さんが記事を書かれています。
https://qiita.com/kerupani129/items/6646eb920c23658bc525

Target reader

  • Shift_JISのテキストをnpmのパッケージだけでデコードしたい方

Prerequisite

  • バックエンドはCloud Build(CI/CD)やCloud Functions(FaaS)を利用する、つまりNodeの起動オプションを指定するようなことはできない。
  • Node.jsのバージョンはCloud Functionに依存しているため、現時点ではV10系とする。
  • ソースコードはimport/exportを使ってブラウザと記述を統一できるよう、esmというパッケージを利用している。

Body

2つの選択肢

npmだけで行こうとすると選択肢は二つある。

  • full-icuをインストールしてTextDecoderを利用する。
  • iconv-liteの類の独自の文字コード変換で処理する。

今回何より大事にしたいのはどこでもインストールエラー等なく動くことを最優先とする。
前者のfull-icuはICUをダウンロードするようで、サイズの大きさの懸念と、何よりダウンロードがCloud Buildで利用できるかの懸念がある。
ローカルマシンと比較してフルマネージドサービスではいろいろと制約があるため、インストール時に権限等により失敗することが少なくない。
また、Cloud FunctionsではNode.jsの起動オプションは指定できず、環境変数についても動作するか不明。

ということで、後者のパッケージで文字コード変換を完結するものを利用する。

iconv-lite

iconv-liteの週間ダウンロード数は2千万でversionが1.00に到達していないが十分すぎる人気。
https://www.npmjs.com/package/iconv-lite

Node.jsなのでパッケージサイズは気にならないが、念のため調べるとMINIFIED + GZIPPEDで150KBなので、フロントで利用するわけじゃないので合格。
package.json覗いてみたところ、依存関係も1つと素晴らしい。
https://bundlephobia.com/result?p=iconv-lite@latest

そして、私が作ったFetchAPIのソースコード。
Shift-JIS対策だけではなく、タイムアウトや認証エラーが含まれるため少し複雑になっている。
Shift-JISの変換は、fetchText()のoption.sjisの部分で、iconv-liteのdecode()を利用しているだけ。
decode()にはBufferを渡す必要があるため、Buffer.from()でresponseを変換している。

responseをみて文字コード指定も考えたが、ヘッダーになかったり指定が間違っていることもありえるので、割り切ってShift-JISのURLの場合にはsjisオプションを付与してコールしている。

utils/fetch.js
importiconvfrom'iconv-lite';importfetchfrom'node-fetch';importAbortControllerfrom'abort-controller';// for "reason: unable to verify the first certificate"// see: https://github.com/node-fetch/node-fetch/issues/15#issuecomment-533869809importhttpsfrom"https";constagent=newhttps.Agent({rejectUnauthorized:false});constfetchCore=async(url,option={})=>{// set timeout after 15sconstcontroller=newAbortController();consttimeout=setTimeout(()=>{controller.abort();},option.timeout||15000);try{constresponse=awaitfetch(url,{method:'GET',mode:'cors',cache:'default',agent:option.resolveUnauthorized?agent:undefined,signal:controller.signal,// for timeout...option,});if(!response.ok){constdescription=`status code:${response.status} , text:${response.statusText}`;thrownewError(description);}returnresponse;}finally{clearTimeout(timeout);}}constfetchJson=async(url,option)=>{constresponse=awaitfetchCore(url,option);returnawaitresponse.json();}constfetchText=async(url,option)=>{constresponse=awaitfetchCore(url,option);if(option.sjis){returniconv.decode(Buffer.from(awaitresponse.arrayBuffer()),"shift_jis")}returnawaitresponse.text();}export{fetchJson,fetchText};

Conclusion

ブラウザを使っているともはや文字コードなんて気にすることもなかったが、改めブラウザ大変だなと実感。
Shift-JISはあと十年くらいは生存していそうなので、CI/CD環境下ではiconv-lite様様といったところ。

Have a great day!

Viewing all 8821 articles
Browse latest View live