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

react-dropzone、Express、Firebaseを使った画像のアップロード

$
0
0
components/FileUpload.ts
const onDrop = (acceptedFiles: File[]) => {
     console.log(formData)
 }

return (
     <div>
         <DropZone  onDrop={onDrop}>
             {({getRootProps, isDragActive}) => (
                 <div {...getRootProps()}>
                     {isDragActive ? "Drop it like it's hot!" : 'Drag a file to upload!'}
                 </div>
             )}
         </DropZone>
     </div>
 )

react-dropzoneを使ってこんな感じに書いて、描画された部分に画像をドロップしてみると

lastModified: 1571463132902
lastModifiedDate: Sat Oct 19 2019 14:32:12 GMT+0900 (日本標準時) {}
name: "hoge.PNG"
path: "hoge.PNG"
size: 353348
type: "image/png"
webkitRelativePath: ""

こんな感じのFileっていう型の配列がもらえる。
んじゃあこのデータ配列そのままサーバ側にぶん投げて、サーバ側でfirebase storageにpath指定してあげりゃあ画像のアップロード実装できんじゃん!
って思ってたんだけど、見てわかる通りpathがfullpath(?)になってない。どうしよう...

最終的に、サーバ側に一時的に画像を保存して、そこで生成されたpathをfirebase側に指定してあげたらうまく行った。

そのために、まずはreact-dropzoneから受け取ったFile配列をFormDataとやらに変換して、それをPOSTデータとしてぶん投げた。

components/FileUpload.ts
 const onDrop = (acceptedFiles: File[]) => {
     const formData = new FormData();
     acceptedFiles.forEach(file => {
         formData.append('Files', file)
     })
     dispatch(postFiles.request(formData))
 }

参考にさせていただいたサイト様様 → https://qiita.com/uryyyyyyy/items/9954205a620b6f3c1f24

んで、これをExpress側で受け取るんだけど、このデータはmultipart/form-dataというものらしく、普通にやったらうまく受け取れなかった。なんやそれ。知らんがな。

app.ts
import multer from 'multer';

const upload = multer({ dest: './uploads/' });

app.post('/files', upload.fields([ { name: 'Files' } ]), postStorageController);
controller/postStorageController.ts
export function postStorageController(req: Request, res: Response, next: NextFunction) {
    const files: Express.Multer.File[] = req.files['Files'];

こういう風に書いてあげると、filesの部分で以下のような感じで値がもらえる

[Object: null prototype] {
  Files:
   [ { fieldname: 'Files',
       originalname: 'hoge.PNG',
       encoding: '7bit',
       mimetype: 'image/png',
       destination: 'uploads/',
       filename: 'd18eb9674f13a81898e7c25bb0c3bda6',
       path: 'uploads/d18eb9674f13a81898e7c25bb0c3bda6',
       size: 353348 } ] }

参考にさせていただいたサイト様様 → https://qiita.com/uryyyyyyy/items/9954205a620b6f3c1f24
(さっきと同じ記事。圧倒的感謝)

あとは、Google先生の解説を見ながらfirebase storageにあげれば終わり!

controller/postStorageController.ts
export function postStorageController(req: Request, res: Response, next: NextFunction) {
    const files: Express.Multer.File[] = req.files['Files'];

    files.forEach(fileInfo => {
        const uploadFilePath = `files/${fileInfo.originalname}`
        bucket.upload(fileInfo.path, { destination: uploadFilePath, contentType: fileInfo.mimetype }, error => {
            if (error) {
                console.log(`failed storage post ${fileInfo.originalname}`);
                console.log(error.message);
                return res.sendStatus(404);
            } else {
                console.log(`success storage post ${fileInfo.originalname}`);
                return res.sendStatus(200);
            }
    }
}

↓↓↓今回書いたソースコード

(フロント)
https://github.com/ShotaroOkada/fileupload_react_client

(サーバ)
https://github.com/ShotaroOkada/fileupload_express_api


nodejs, npm, nパッケージ構築備忘録【Ubuntu18.04 LTS】

$
0
0

n packageによるnpm環境構築

nパッケージを使うことで常に最新版の導入や、nodejs自体のバージョン管理を簡単に行うことが出来るようになり、またnpmパッケージも自動で入れてくれます。

nパッケージの構築に必要なパッケージ共をaptからinstall

とりあえずnodejsnpmaptでインストールします。あとで消しますが。

sudo apt update && sudo apt upgrade
sudo apt install nodejs npm

キャッシュクリアとnパッケージのインストール

sudo npm clean all
sudo npm install n -g

nパッケージによるnodejsとnpmのインストール

sudo n latest # もしくはstableで安定版をインストール
sudo ln -sf /usr/local/bin/node /usr/bin/node
node -v
# v13.0.1

aptで入れたnodejs, npmパッケージの削除

sudo apt purge nodejs npm

削除後もnpm, nodejs, nパッケージが動作することを確認して下さい。

node -v
# v13.0.1
which node
# /usr/local/bin/node

npm -v
# 6.12.0
which npm
#/usr/local/bin/npm

n --version
# 6.1.0
which n
# /usr/local/bin/n

# 後始末
sudo apt clean all
sudo apt autoremove

終わり

大丈夫 きっと明日には 忘れてる

obnizOS on M5StickCでハマった件 #yorohack #obniz

$
0
0

ちょうど届いたので、#yorohackで触ってみてました。

【乾杯スタートで居酒屋ハック!】養老乃瀧ハッカソン2019

まず電源がつかない (でも焦ってはいけない)

木戸さんに教えてもらった記事M5StickC[obnizOS + Hobby Lite]でLピカする。を見るとこんな記載が

スクリーンショット 2019-10-27 11.39.46.png

なんと電源が付いても何も変化がありません。

M5StickCが起動しない時に抵抗を挟んでゴニョゴニョって話題が一時期ありましたが、それとは全くの別問題みたいです

PCとシリアル通知をするとシリアル表示があるので、これで電源が入ってることを確認しましょう。

Arduino IDEのシリアル通信が上手くいかない?

これはイマイチobnizOS側なのかArduino IDE側なのか分かってないです。

シリアル通信でデバイスキーの入力を求められます。この辺のドキュメントを見てdevicekeyを入力します。

ただ、僕の環境だと永遠にdevicekeyを求められました。

入力しても反応がありません。

環境が macOS Catalina / Arduino IDE 1.9ベータ という先を行き過ぎている感だったので僕の環境が悪いような気がしています。

screenコマンドでリトライしたらつながった

ターミナルでscreenコマンド使ったら大丈夫でした

$ screen /dev/cu.usbserial-xxxxxxxxxx 115200

ここでdevicekeyを聞かれてエンターで進めたらうまいこと認証やwifi設定なども出来ました。

obnizOSで音鳴らす

この辺まで来たら良い感じです。 Speaker Hatを使って音を鳴らせました。(+Lチカ)

npm init -y
npm i obniz
app.js
const Obniz = require('obniz');
let m5 = new Obniz('xxxxxxxxxx'); //ID

m5.onconnect = async function() {
  console.log("connected");

  console.log("ledON");
  m5.io10.output(false);

  const speaker = m5.wired("Speaker", {signal:26, gnd:1});
  speaker.play(1000); //1000hz
}

Speaker Hatは26番指定らしいです。

参考: M5StickC + Speaker Hat で音を出す
参考: https://obniz.io/ja/sdk/parts/Speaker/README.md

まとめ

  • obnizOSの初期セットアップは結構分かりにくい
  • シリアル通信はscreenコマンド使おう
  • Wi-Fiに繋がるまでが一苦労
  • Wi-Fi繋がってしまえばカンタン

node.js,express,yarn,postgresql,sequelizeのインストールコマンド集

$
0
0

動作環境

ホストos:win10
ゲストos:ubuntu
virtualbox:5.2.34
Vagrant 2.2.5

以上の条件ではなくとも仮想化が終えられている方であれば以下のインストールは問題ないかと思われます。

node.jsのインストール

まずnode.jsのバージョン管理をするnvmをインストールし、nvmを使ってnode.jsをインストールします。

nvmインストールコマンドの引用元

https://github.com/nvm-sh/nvm

$curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

以下のコマンドで変更を反映させます。
$source ~/.bashrc

node.jsインストールコマンドの引用元

nvm install --lts
nvm use --lts

yarnインストールコマンドの引用元

JavaScriptのパッケージマネージャであるyarnをインストールします。

yarn公式

https://yarnpkg.com/ja/docs/install#alternatives-stable

yarnをインストール
$curl -o- -L https://yarnpkg.com/install.sh | bash
$source ~/.bashrc

expressインストールコマンドの引用元

node.jsのフレームワークであるexpressをインストールします。

express公式

https://expressjs.com/en/starter/installing.html

expressをインストール
$yarn add express

パッケージインストール
yarn install

postgresql

DBに今回はpostgresqlを使用します

postgresqlインストールコマンドの引用元

https://www.postgresql.org/download/

postgresqlをインストール
$sudo apt install -y postgresql

sequelize

DBをexpressで使うためにsequelizeをインストールします。

sequelizeインストールコマンドの引用元

https://yarnpkg.com/en/package/sequelize

sequelizeのインストール
yarn add sequelize
yarn add pg
yarn add pg-hstore

急性中耳炎の重症度が分かるLINE Botの作成

$
0
0

概要

プログラムの勉強を始めて4か月ほどの開業医です。
病気のおすすめの診断法や、治療法などを記載した診療ガイドランというものがあります。診療ガイドラインに従うことは強制ではないのですが、エビデンスに基づいた治療をするためにはガイドラインは重要です。しかし、忙しい臨床現場ではガイドライン通りに診療を進めることが難しい場合もあります。
今回、小児急性中耳炎の診療ガイドライン(2018年-日本耳科学会)に沿って、急性中耳炎の重症度と推奨治療が簡単に分かるLINE Botを作成しました。
医師が臨床現場で使えることはもちろんですが、一般の方でも医療機関で「鼓膜の赤さ・腫れ・耳垂れ」の程度を医師に聞いていただくと急性中耳炎の重症度が判定できます。

実装

LINE上で質問に答えていくとスコアリングが行われ、急性中耳炎の重症度と推奨治療が分かるLINE Bot。

概念図

node.js expressでLINE bot APIを連携しました。
line nodejs.png

作成方法

1. Botアカウントを作成する

2. Node.jsでBot開発

3. ngrokでトンネリング

上記の1~3を以下の参考記事の通りに行います。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017

4. プログラム作成

コードを以下のように書き換えます。
今回は、LINE Developersのクリックリプライを使いました。
LINE Developers

'use strict';

const express = require('express');
const line = require('@line/bot-sdk');
const PORT = process.env.PORT || 3000;

const config = {
    channelSecret: 'channelSecretを入力',
    channelAccessToken: 'channelAccessTokenを入力'
};


const app = express();

app.get('/', (req, res) => res.send('Hello LINE BOT!(GET)')); //ブラウザ確認用(無くても問題ない)
app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);

  //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
  if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
    res.send('Hello LINE BOT!(POST)');
    console.log('疎通確認用');
    return; 
  }

  Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});


const client = new line.Client(config);

function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

  // LINE botのプログラム 
  let message = event.message.text;
  let question = "";
  let label1, label2, label3 = "";
  let text1, text2, text3 = "";

  let [mes, currentScore] = message.split(":");  

  if (message == "開始") {    
    currentScore = 0;
    question = "2歳未満ですか?";
    label1 = "いいえ";//0
    text1 = "2歳以上 トータルスコア:"+String(currentScore);
    message = text1;
    label2 = "はい";//3
    text2 = "2歳未満 トータルスコア:"+String(currentScore+3);
    message = text2;
    label3 = "選択できません";
    text3= "開始";
    message = text3;
  } else if (mes == "2歳以上 トータルスコア"|| mes==  "2歳未満 トータルスコア") {
    question = "耳は痛いですか?";
    label1 = "いいえ";//0
    text1 = "耳は痛くない トータルスコア:"+String(currentScore);
    message = text1;
    label2 = "痛い、痛かった";//1
    text2 = "耳が痛い、痛かった トータルスコア:" + String(Number(currentScore)+ 1);
    message = text2;
    label3 = "とても痛いのが続いている";//2
    text3= "耳がとても痛いのが続いている トータルスコア:" + String(Number(currentScore)+2);
    message = text3;
  } else if (mes == "耳は痛くない トータルスコア"||mes=="耳が痛い、痛かった トータルスコア" ||mes== "耳がとても痛いのが続いている トータルスコア" ) {
    question = "熱はありますか?";
    label1 = "37.5未満";//0
    text1 = "37.5未満 トータルスコア:" + String(currentScore);
    message = text1;
    label2 = "38.5未満";//1
    text2 = "38.5未満 トータルスコア:" + String(Number(currentScore)+1);
    message = text2;
    label3 = "38.5以上";//2
    text3= "38.5以上 トータルスコア:" + String(Number(currentScore)+2);
    message = text3;
  } else if (mes == "37.5未満 トータルスコア"||mes== "38.5未満 トータルスコア"||mes== "38.5以上 トータルスコア") {
    question = "泣いたり、機嫌が悪いですか?";
    label1 = "いいえ"//0
    text1 = "機嫌は悪くない トータルスコア:" + String(currentScore);
    message = text1;
    label2 = "はい";//1
    text2 = "泣いたり、機嫌が悪い トータルスコア:" + String(Number(currentScore)+1);
    message = text2;
    label3 = "選択できません";
    text3= "開始";
    message = text3;
  } else if (mes == "機嫌は悪くない トータルスコア" ||mes== "泣いたり、機嫌が悪い トータルスコア" ) {
    question = "鼓膜が赤いですか?";
    label1 = "いいえ";//0
    text1 = "鼓膜は赤くない トータルスコア:" + String(currentScore);
    message = text1;
    label2 = "一部だけ赤い";//2
    text2 = "一部だけ赤い トータルスコア:" + String(Number(currentScore)+2);
    message = text2;
    label3 = "全体に赤い";//4
    text3= "全体に赤い トータルスコア:" + String(Number(currentScore)+4);
    message = text3;
  } else if (mes ==  "鼓膜は赤くない トータルスコア" ||mes== "一部だけ赤い トータルスコア" ||mes== "全体に赤い トータルスコア") {
    question = "鼓膜は腫れていますか?";
    label1 = "いいえ";//0
    text1 = "鼓膜は腫れていない トータルスコア:" + String(currentScore);
    message = text1;
    label2 = "一部だけ腫れている";//4
    text2 = "一部だけ腫れている トータルスコア:" + String(Number(currentScore)+4);
    message = text2;
    label3 = "全体に腫れている";//8
    text3= "全体に腫れている トータルスコア:" + String(Number(currentScore)+8);
    message = text3;
  } else if (mes == "鼓膜は腫れていない トータルスコア"||mes=="一部だけ腫れている トータルスコア" ||mes== "全体に腫れている トータルスコア" ) {
    question = "耳垂れは出ていますか?";
    label1 = "いいえ";//0
    text1 = "耳垂れは出ていない トータルスコア:" + String(currentScore);
    message = text1;
    label2 = "少し出ている";//2
    text2 = "少し出ている トータルスコア:" + String(Number(currentScore)+2);
    message = text2;
    label3 = "たくさん出ている";//4
    text3= "たくさん出ている トータルスコア:" + String(Number(currentScore)+4);
    message = text3;
  } 
  console.log(message);

  if (mes == "耳垂れは出ていない トータルスコア"  || mes == "少し出ている トータルスコア" || mes == "たくさん出ている トータルスコア" ) {
    let result;
    if (currentScore <= 5) {
      result="急性中耳炎は軽症です。抗生物質は使わずに3日間様子を見ましょう。"
    } else if (currentScore < 11) {
      result="急性中耳炎は中等症です。 AMPCを高用量で3~5日間投与しましょう。"
    } else {
      result="急性中耳炎は重症です。AMPCを高用量で使うか、CVA / AMPC1:14製剤を通常量で使うか、CDTR-PIを高用量で使いましょう。鼓膜切開も考慮しましょう。"
    }
    return client.replyMessage(event.replyToken, {
        type: 'text', 
        text:result    
    });
  } else {
    return client.replyMessage(event.replyToken, {
      "type": "text", 
      "text": question,
      "quickReply": { 
        "items": [
          {
            "type": "action", 
              "action": {
              "type": "message",
              "label": label1,
              "text": text1
            }
          },
          {
            "type": "action",
              "action": {
              "type": "message",
              "label": label2,
              "text": text2
            }
          },
          {
            "type": "action", 
            "action": {
              "type": "message",
              "label": label3,
              "text": text3
            }
          }
        ]
      }
    });
   } 
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);

動作確認

ちゃんと動いています。
クイックリプライボタンで表示されます。
耳痛1.png

耳痛.png
鼓膜発赤3.png

IMG-1.png

動画です。


考察

LINE Developersのクリックリプライを使いUIのなかなか良いLINE Botを作ることができました。handleEvent(event)を通るとScoreが0に戻ってしまうので、Scoreを足していくプログラムを書くのが難しかったです。サーバーで情報を管理できればもっと簡単にできそうなのでサーバー管理の勉強をしたいと思いました。

JavaScriptのコールバック関数

$
0
0

JavaScriptのコールバック関数についてのメモです。

コールバック関数とは、ある関数の引数に渡される関数のことです。
Java Scriptはソースコードを上から順番に実行してくれる訳ではありません。
意図した順番に処理を実行するために使われるのがコールバック関数です。

コードは全てプレイグラウンドで実行できますので、試してみてください。

コールバック関数を使わない場合

hello,byeの順番に出力したいのですが、
次のようなソースコードだとbye=>helloの順に出力されてしまいます。

//1秒待ってhelloと出力する関数の定義
function hello() {
   setTimeout(() => {
      console.log('hello')
   }, 1000);
}
//byeと出力する関数の定義
function bye(){
  console.log('bye');
}
//処理の実行
aisatsu();
function aisatsu() {
   hello();
   bye();
}

出力結果

bye
hello

コールバック関数を使った場合

では、hello=>byeの順番に出力するためにコールバック関数を使ってみましょう。
byeの関数をhelloのコールバック関数として渡しています。

byeの関数の書き方も変わってますね。byeの関数をそのまま渡すのではなく、一度変数に入れるようにしました。
アロー関数の書き方については、こちらを見てみてください。

//1秒待ってhelloと出力する関数の定義
//コールバック関数でbyeを渡す
function hello(callback) {
   setTimeout(() => {
      console.log('hello');
      callback(); //ここでコールバック関数のbyeを実行
   }, 1000);
}

//byeと出力する関数の定義を変数byeに入れる
const bye = () =>
  console.log('bye');

//処理の実行
hello(bye);

出力結果

hello
bye

hello、byeの順番で出力できましたね。

(参考)setTimeout関数 

次の場合は1秒後に処理が実行されます。

setTimeout(() => {
   //処理
}, 1000);

Mac OSX 導入したもの備忘録:開発編2-2 ~gulp~

$
0
0

備忘録 Macにインストールしているもの

色々開発用にMacに入れたもので手順が煩雑になりがちなものがあったので備忘録として残します。

Gulp

Node.jsで動いているタスクランナーと言われるツール。
最近だとwebpackにとって変わられている感がありますが厳密には違うカテゴリのツール。
( 個人の主観ですが、元来webpackはjsをバンドルするのが仕事で、
scssをcssに変換するのは範疇外のはずでした )

plugins

  • Gulp(タスクランナー) : Sassのコンパイルなどを自動化するツール
  • gulp-sass : node-sassを実行するためのモジュール
  • gulp-autoprefixer : cssにプレフィックスをつけるためのモジュール
  • gulp-sourcemaps : .mapファイルを生成するモジュール
  • gulp-plumber : gulpがエラーで終了するのを防止するモジュール
  • gulp-notify : gulpのエラー内容を通知するモジュール

導入手順

terminal
# プロジェクトのディレクトリを掘る
$ mkdir hoge

# プロジェクトのディレクトリへ移動
$ cd hoge/

# moduleをインストールして、 package.jsonを生成する
$ npm init

# gulpをローカルにインストール
$ npm install --save-dev gulp

# ローカルのgulpをaliasで使えるようにする
## 追記:package.jsonにscript書いた方が安全かも・・・
$ echo "alias gulp='./node_modules/.bin/gulp'" >> ~/.bash_profile

# エイリアスを使ってインストールされているか確認
# ※プロジェクトにインストールしたのでディレクトリに注意
$ gulp -v

# gulpfile.jsを作る←これにタスクを記述していく
$ touch gulpfile.js

# gulpのプラグインを入れていく
# 引き続きプロジェクトのディレクトリ
 $ npm install gulp-sass --save-dev
 $ npm install gulp-autoprefixer --save-dev
 $ npm install gulp-sourcemaps --save-dev
 $ npm install gulp-plumber --save-dev
 $ npm install gulp-notify --save-dev

設定ファイル

gulpfile.js
'use strict';
const gulp = require('gulp');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const sourcemaps = require('gulp-sourcemaps');
const plumber = require('gulp-plumber');
const notify = require('gulp-notify');
const SCSS_SRC = './src/sass/**/*.scss';
const CSS_DEST = './public/assets/css/';
const scss_options = {
  outputStyle: 'compressed',
};
const prefix_options = {
  browsers: ['last 6 version', 'ie >= 9'],
  cascade: false,
};

var notifyMsg = {
  errorHandler: notify.onError("ERROR: <% error.message %>")
};

gulp.task('build:scss', function() {
  gulp.src(SCSS_SRC)
    .pipe(plumber(notifyMsg))
    .pipe(sourcemaps.init())
    .pipe(sass(scss_options))
    .pipe(autoprefixer(prefix_options))
    .pipe(sourcemaps.write('./'))
    .pipe(gulp.dest(CSS_DEST));
});

gulp.task('scss:watch', function() {
  gulp.watch(SCSS_SRC, ['build:scss']);
});

gulp.task('default', ['scss:watch']);
package.json
{
  "name": "hoge", // 任意で設定
  "version": "0.1.0", // 任意で設定
  "private": true, // 任意で設定
  "scripts": {
    "gulp": "gulp"
  },
  "dependencies": {
    // 必要なモジュールが色々
  },
  "devDependencies": {
    // 必要なモジュールが色々
  }
}

使い方

terminal
# プロジェクトのディレクトリに移動
$ cd hoge/

# Gulp 開始
$ gulp

# 追記:package.jsonを使う場合の使い方
$ npm gulp 

# Gulp 停止
ctrl + C # コマンドではなくキー入力

あとがき

Gulpnpm でインストールしてるのに何故 vue-cliyarn なのか?
Gulpは普段全く使ってないので、後から記事を分離したので時系列がバラけてしまった感がある。
フロント系のツールは移り変わりが早くて参ってしまう・・・。

GulpのBrowsersyncで立ち上げたローカルサーバに、スマホからアクセス出来ない時

$
0
0

概要

ブラウザの自動リロードを実現してくれるBrowsersyncという便利なプラグインがあり、ローカルIPアドレスでサーバーを立ち上げる事が出来ます。

同じLANネットワークの端末からアクセス出来るので、スマホ・タブレットでの表示確認に便利です。

割り当てられるIPアドレスは自動であるため、ホストOSに複数のIPアドレスが割り当てられている場合、想定したIPアドレスが割り当てられず、同じLAN内の端末からアクセス出来ないケースに遭遇しました。

仮想ソフトを導入し、複数のNICが割り当てられているローカル開発環境も多いと思いますので、そのようなケースで特定のIPアドレスを割り当てる方法をメモしておきます。

同じLAN内の端末からアクセス出来ない例

私の環境の場合、192.168.56.1という、本来デフォルトゲートウェイ(ルータ)に割り当てられるようなIPアドレスでサーバーが立ち上がり、同じLAN内の端末からアクセス出来ない状況でした。

このIPアドレスは何なのか

VirtualBoxを導入してゲストOSを立ち上げた時に、ホストOS(つまりGulpを動かしているPC)に割り当てられるIPアドレスのようです。

なぜこのIPアドレスが割り当てられるのか

公式ドキュメントより
Incorrect External URL

Sometimes, depending on your network, your OS will report multiple external IP addresses. If this happens, currently Browsersync just picks the first one and hopes for the best.

You could use a tool like dev-ip to list all possible external URLs on your machine, and then provide the host option in your configuration

IPアドレスは自動で割り当てられるので、正しくない場合はhostオプションで指定してね」との事です。

割り当てるIPアドレスの確認

割り当てるべきIPアドレスは、「インターネットに繋がっているIPアドレス」です。

上記dev-ipというツールで確認してもよいでしょうし、単にipconfig/ifconfig等で調べてもよいと思います。

BrowsersyncでローカルIPアドレスを指定する

Browsersync options - Host

hostにIPアドレスを指定します。

gulpfile.js
var gulp = require('gulp');
var browsersync = require("browser-sync").create();

// サーバーを立ち上げる
gulp.task('browser-sync', function (done) {
  browsersync.init({
    server: {
      baseDir: './',
    },
    //ローカルIPアドレスでサーバーを立ち上げる
    open: 'external',
    //IPアドレスを指定する
    host: '192.168.XXX.XXX'
  });
  done();
});

// ファイル監視
gulp.task('watch', function(done) {
  gulp.watch('./**/*.html', gulp.task('browser-reload'));
  done();
});

// ブラウザのリロード
gulp.task('browser-reload', function (done){
  browsersync.reload();
  done();
});

// タスクの実行
gulp.task('default', gulp.series(gulp.parallel('browser-sync', 'watch')));

以上で、同じLAN内の端末からアクセス出来るようになります。

参考資料


アプリエンジニアがNode.jsに入門してみる(概念理解、導入、HelloWorldまで)

$
0
0

背景と目的

エラー監視にNode.jsが使用されていたからです。私の所属するチームではアプリで発生したエラーをslackに通知する形でエラー監視を行なっているんですが、その実現にNode.jsを使用していました。今後通知するエラーの条件変更などを行う際にNode.jsとサーバサイドの知識が必要になるので、Node.jsの基本的な使い方と導入方法について理解しようと思いました。
じゃあ早速中身に入っていきましょう!!

1.Node.jsとは?

Node.js公式サイト
公式では以下のように説明されています。

Node.js® は、Chrome の V8 JavaScript エンジン で動作する JavaScript 環境です。

わけがわかりませんでした。
Node.jsとは何かを理解するより先に、Node.jsで何ができるのかを調べてみます。
こちらのサイトが分かりやすかったです。
フロントエンドエンジニアにおけるNode.jsのススメ

1-1.Node.jsでできること

一言で言えばサーバサイドプログラムの作成でした。
サーバサイドとはサーバーの内部で動く処理のことで、フロントからの指示を受けてDB依存の処理やフロントで行うのに適していない複雑な処理を行うプログラムのことを指します。JavaScriptはフロントエンドの言語として長く使用されてきましたが、これをサーバサイドでも使用できるようにした実行環境がNode.jsであるとわかりました。

1-2.つまりNode.jsって何?

一言で言うとJavaScriptを使用したサーバーサイド実行環境である。
ということが分かりました。

1-3.Node.jsの特徴

Node.jsは以下の特徴を持っています。

  • サーバサイドとフロントエンドを同じ言語で書ける。
    • つまりフロントエンドができる人であれば学習コストが低め。
    • つまりフロントとサーバサイドで同じ言語やライブラリが使えるため、開発効率が上がる。
  • 非同期処理を簡単に書ける
    • 一つのスレッドで大量の接続を処理できる。

こちらの記事から引用して挙げています。
https://paiza.hatenablog.com/entry/2018/06/08/paizacloud_nodejs
https://ameblo.jp/ca-1pixel/entry-11476850674.html

2.Node.jsの導入方法

最新版をインストールする場合は公式サイトのトップページからインストーラを取ってきて開くだけです。
Node.js公式サイト

しかしその方法ではDL時のバージョン以外の利用することができませんし、今後特的のバージョンによって動作しない機能があった時バージョンを切り替える必要が出てくるので、nvm(node version manager)を使います。

2-1.nvmとは

Node.jsのバージョン管理ツールです。
nvを使用することでNode.jsを指定したバージョンに切り替えることができます。
nvm公式Githubレポジトリ

<補足>
Node.jsのバージョン管理ツールとしてnodebrewも挙げられますが、2019年10月現在ではnvmを使用しているのが主流みたいです。
参考:nvm(Node Version Manager)を使ってNode.jsをインストールする手順

2-2.nvmの導入

nvmをインストールする方法は三つあります。
* 手動インストール
* Gitによるインストール
* Homeblewによるインストール

参考にしたサイトではほとんどがGitによるインスールを推奨していました。
理由はnvmのバージョン管理自体がGitだと楽になるからだそうです。
と言うわけでGitでnvmをインストールしてみます。

Gitによるnvmのインストール手順

ユーザープロファイルのルートにnvmのリポジトリを複製します。

$ git clone https://github.com/nvm-sh/nvm.git .nvm

nvmのリポジトリに移動し、最新バージョンにチェックアウトします。

$ cd ~/.nvm
$ git checkout v0.35.0

nvmをアクティブ化します。
これをしないとnvmは使えないのかな?
試さないまま進んでしまったので分かりませんが、公式の手順にもあるので必要な手順なのでしょう。

$ source ~/.nvm/nvm.sh

ここまでできたらnvmのインストールが完了しているはずです!
以下のコマンドでインストールされたかどうか確認できます。

$ command -v nvm

nvmと出力されたらインストールできています。

nvmによるNode.jsのインストール

nvmを使用してNode.jsをインストールしていきます。
Node.jsは偶数のバージョンがLTS版(安定版)、奇数のバージョンが最新版となっているようです。
LTS版は長期間に渡って安定的にメンテナンスされているバージョンのことです。
最新の機能を使うためにどうしても最新版のnodeを使用する必要が場合を除けば、LTS版を使用するのがベターだと思います。

インストール可能なNode.jsのLTS版は以下のコマンドから確認できます。

$ nvm ls-remote

今回はLTS版の中で最新のバージョンであるv12.13.0をインストールしてみます。(2019/10/25現在)
LTS版のうち最新のバージョンのNode.jsをインストールする。

$ nvm install --lts

このように表示されたらインストールできた証拠です。

Now using node v12.13.0 (npm v6.12.0)

バージョンを指定してインストールする場合はnvm installの後にバージョンを直打ちします。
今後Node.jsのバージョンを切り替えたい場合は同様nvm installコマンドで切り替えたいバージョンを指定すればOKです。

$ nvm install {インストールするバージョン名}

以下のコマンドで現在使用中のnodeのバージョンを確認できます。

$ node -v

デフォルトのNode.jsバージョンを設定する。

nvmによるNode.jsのインストールはできました。
しかし今のままではターミナルの再起動の際にまた同じことをやる必要があります。
そうならないために設定する必要があるのが、デフォルトのNode.jsバージョンの指定です。

以下のコマンドでデフォルトのNode.jsバージョンを設定します。

$ nvm alias default {Node.jsのバージョン}
# default -> v12.13.0と表示される

続けて.bash_profileを編集し、ターミナル起動の際にnvmコマンドが読み込まれるようにします。

$ vi ~/.bash_profile

# 以下を追加する
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_com

これでターミナルを再起動してみます。

$ node -v
# v12.13.0

無事デフォルトのNode.jsバージョンを設定できました。

3.Node.jsでHelloWorld

nvmを使ったNode.jsのインストールができたので早速Node.jsを触っていきましょう。
とりあえずHelloWorldします。
いつだって最初の一歩はHelloWorldです。

まずはterminalでjsを実行してみます。

$ node
>

nodeと打って実行すると何やら>マークが出てきます。
この状態をREPLと言い、javascriptの文法やNode.jsのモジュール確認に使用することができます。

3-1.REPLでHelloWorld

# 計算する
> 22 * 3
66

# Hello Worldを出力する
> var test = "Hello World";
undefined
> console.log(test);
Hello World
undefined

REPLは終了するときはcontrol + dを叩きます。

3-2 httpモジュールからブラウザでHelloWorldしてみる

今度はローカルにサーバーを立ててブラウザ上にHelloWorldを出力してみます。
terminalから実行するためのjsファイルを作成します。

作成したjsファイルに以下のコードを記述します。

// httpモジュールの作成
var http = require('http');

// Hello Worldを返すサーバーを立てる
var server = http.createServer(function(req, res) {
  res.end("Hello World");
});

// ポート8000で接続可能なサーバーを起動する
server.listen(8000);

このjsファイルを保存し、terminalからnodeで実行します。

$ node HelloWorld.js

実行しても何も表示されませんが、サーバーは起動しています。
http://localhost:8000/
↑このurlをブラウザで開いてみましょう。
以下のように表示されたら成功です。
スクリーンショット 2019-10-27 23.05.37.png

まとめ

  • Node.jsとはJavaScriptを使用したサーバーサイド実行環境である。
  • Node.jsには以下の特徴がある
    • サーバサイドとフロントエンドを同じ言語で書ける。
    • 非同期処理を簡単に書ける
  • Node.jsの導入、管理にはnvmを使用すると良い。

参考

公式
Node.js公式サイト
nvm公式Githubレポジトリ

Node.js自体について
フロントエンドエンジニアにおけるNode.jsのススメ

導入
nvm(Node Version Manager)を使ってNode.jsをインストールする手順
いまアツいJavaScript!ゼロから始めるNode.js入門〜5分で環境構築編〜

HelloWorld
Node.jsで動くものを作ってみる、Helloworld

ESLint v6.6.0

$
0
0

v6.5.0 | 次 (2019/11/23 JST)

ESLint 6.6.0 がリリースされました。
小さな機能追加とバグ修正が行われています。

これまで ESLint は隔週リリースを行ってきましたが、アクティブなメンテナが減少していることから、今後は月間リリースとなります。

質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。

🏢 日本語 Issue 管理リポジトリ
👫 日本語サポート チャット
🏢 本家リポジトリ
👫 本家サポート チャット

🚀 本体への機能追加

context.getCwd()

🔖 RFC035, #12389

各ルールから現在のディレクトリを取得できるようになりました。

module.exports = {
    meta: {},
    create(context) {
        console.log(context.getCwd()) //→ /home/a-user/projects/foo
        return {}
    },
}

この値は基本的に process.cwd() と同じですが、いくつかの場合に異なる値になります。

  • CLIEngine オブジェクトを初期化した後に process.chdir() 関数によって CWD を変更した場合
  • CLIEngine のコンストラクタ オプションとして独自の CWD を指定した場合

また、ブラウザ上で実行している場合など、CWD が不明な場合は undefined を返します。

💡 新しいルール

特になし。

🔧 オプションが追加されたルール

no-param-reassign ignorePropertyModificationsForRegex

🔖 #11275

指定した正規表現にマッチする名前の引数について、プロパティの変更を許可するオプションが追加されました。

/*eslint no-param-reassign: [error, { props: true, ignorePropertyModificationsForRegex: ["^req"] }]*/

//✘BAD
function f1(req, res) {
    res.value = 0
}

//✔GOOD
function f2(req, res) {
    req.value = 0
}

Online demo

use-isnan enforceForIndexOf

🔖 #12379

浮動小数点数の仕様により、NaNとの比較は常にfalseになります。これを報告するuse-isnanルールに、Array#indexOfメソッドも報告するオプションが追加されました。代わりに、Array#findIndexメソッドを利用したり、NaN を特別に処理する新しいメソッドArray#includesを利用したりしましょう。

/*eslint use-isnan: [error, { enforceForIndexOf: true }]*/

//✘BAD
var indexOfNaN= myArray.indexOf(NaN)
var hasNaN = myArray.indexOf(NaN) >= 0

//✔GOOD
var indexOfNaN = myArray.findIndex(Number.isNaN)
var hasNaN = myArray.includes(NaN)

Online demo

no-unsafe-negation enforceForOrderingRelations

🔖 #12414

比較演算子の左辺をオペランドとする否定演算子を警告するオプションが追加されました。

/*eslint no-unsafe-negation: [error, { enforceForOrderingRelations: true }] */

//✘BAD
if (! a < b) {}

//✔GOOD
if (!(a < b)) {}

Online demo

no-extra-parens enforceForNewInMemberExpressions

🔖 #12436

new式に直接プロパティアクセスを繋げているときに、そのnew式を囲む括弧を無視するためのオプションが追加されました。

/*eslint no-extra-parens: [error, all, { enforceForNewInMemberExpressions: false }] */

//✔GOOD
var obj = (new Foo()).prop;

Online demo

✒️ eslint --fix をサポートしたルール

特になし。

⚠️ 非推奨になったルール

特になし。

[養老乃瀧ハッカソン2019]ひたすらビールを注文するコースターを作った

$
0
0

目次

  • はじめに
  • 機能概要
  • 実装コード
  • 感想と反省点

はじめに

◆飲み会で、周囲の人のグラスが空いていたら、「何か飲まれますか?」と聞いて飲み物を注文せねばならぬ。
◆俺はただ、ビールが飲みたいんだ!飲む物は決まってるんだから注文せずにズッて持ってこいや!

って思うこと、ありませんか?

少なくとも、私は常々思ってます。
なので、空いたグラスをドッと置いたらビールがズッと来るデバイスを作れないだろうかと思って、実際に作りました。
また、居酒屋にあると嬉しい(主に私が)ので、養老乃瀧ハッカソン2019で発表してきました。

機能概要

タイトル:自動お酒注文コースター「飲ま飲まWay!」
DSC_0037_copy.JPG

コメント 2019-10-28 224058.png

☆使い方☆
1.コースターに空いたグラスを置く
DSC_0028_copy.JPG

2.LEDが光るので、店員が飲み物を提供!
コメント 2019-10-28 224017.png

3.飲む!

実装コード

nomanomaWay.js
'use strict';

// Obniz用定数
var Obniz = require('obniz');
var obniz = new Obniz('yourObnizNumber');
var obniz2 = new Obniz('yourObnizNumber');
var stringWeight;

// Obniz関数
obniz.onconnect = async function ()
{
    obniz.display.clear();
    // ロードセル
    var hcsr04 = obniz.wired("hx711", { gnd: 0, dout: 1, sck: 2, vcc: 3 });

    while (true)
    {
        hcsr04.offset = 88815;
        hcsr04.scale = 88925;
        var val = await hcsr04.getValueWait(1);
        stringWeight = Math.abs(roundFloat(val, 4) * 10000);
        console.log(Math.abs(roundFloat(val, 4) * 10000));
        await obniz.wait(10);
        if (Number.isInteger(stringWeight) == false)
        {
            hcsr04.powerDown();
            obniz.close();
        };
    }
    function roundFloat(number, n)
    {
        var _pow = Math.pow(10, n);
        return Math.round(number * _pow) / _pow;
    }
}

obniz2.onconnect = async function ()
{
    obniz2.display.clear();
    // ソレノイド
    var solenoid = obniz2.wired('Solenoid', { gnd: 0, signal: 1 });
    await obniz2.wait(3000);
    if (obniz.connectionState == 'closed')
    {
        solenoid.on();
        await obniz2.wait(3000);
        solenoid.off();
        obniz2.close();
    }
}

感想と反省点

実際に作ったとき、これは賞とれるだろ!と思ったのですが、取れませんでした。
原因を自分なりに考えた時、養老乃瀧ハッカソン2019のテーマが「皆が笑顔になる」ことがメインだったようで、それとはちょっと合わなかったのかなと。
また、ほかの作品たちがインパクトありまくりで、印象に残らなかったというのもあったのかなと。ぶっちゃけ自分の作品地味だし

ただ、結構悔しかったので、リメイクしてリベンジしたいと思ってます。
改善案としては、コースターを光らせたり、なにか音を出したりすることを考えてます。

なお、個性的な作品たちの詳細は、以下サイトをチェックだ!
protopedia
※タグ検索で 'yorohack2019' を入力してね!

WebComponents だけでアプリの骨格を作ってみる

$
0
0

はじめに

WebComponents、何がいいかというとブラウザでネイティブに対応してるとこですね。

そろそろ本家、3rdパーティーともライブラリが充実してきて、Vue とか React とかのフレームワークを使わずに WebComponents だけでいけそうな感じになってきたので、とりあえず下のようなアプリの骨格を作って検証してみます。

画面収録-2019-10-28-13.05.57.gif

オフィシャルの material.ioGitHub にはメジャーな

  • material-components-android
  • material-components-ios
  • material-components-web
  • material-components-flutter

の他に

ってのがあります(名前が非常にまぎらわしい><)。GUI コンポーネントとして今回これを使わせてもらいます。

また router として、

を使わせてもらいます。

早速作ってみましょう。

準備

node, npm をダウンロードします

https://nodejs.org
この記事は node v12.4.0, npm 6.12.0 で検証しています

ディレクトリを掘って cd して node のプロジェクトを作成します。

$ mkdir wc
$ cd wc
$ yarn init

es-dev-server をインストールして動かしておきます。

$ npm install -g es-dev-server
$ es-dev-server --node-resolve --watch

8000 番のポートが使われてなければ
es-dev-server started on http://localhost:8000
と表示されます。ブラウザでここにアクセスしてください。

--node-resolve オプションが何故必要かについて知りたい場合は以下を参照してください。

https://github.com/material-components/material-components-web-components/blob/master/README.md

作成

使うコンポーネントを登録

$ yarn add @material/mwc-drawer
$ yarn add @material/mwc-icon-button
$ yarn add @material/mwc-top-app-bar
$ yarn add @vaadin/router

メイン画面に表示する WebComponent を作成

Main.js
class
Main extends HTMLElement {
    constructor() {
        super();
        this.attachShadow( { mode: 'open' } ).innerHTML = '<div>MAIN</div>'
    }
}
customElements.define( 'jp-main', Main )
About.js
class
About extends HTMLElement {
    constructor() {
        super();
        this.attachShadow( { mode: 'open' } ).innerHTML = '<div>ABOUT</div>'
    }
}
customElements.define( 'jp-about', About )

まとめあげ

index.html
<!doctype html>
<html lang=zxx>
    <title>drawer demo</title>

    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet">

<body>
    <script type="module">
        import '@material/mwc-drawer'
        import '@material/mwc-top-app-bar'
        import '@material/mwc-icon-button'
        import './Main'
        import './About'

        import { Router } from '@vaadin/router'

        class
        App extends HTMLElement {
            constructor() {
                super();
                this.attachShadow( { mode: 'open' } ).innerHTML = `
                    <mwc-drawer hasHeader type="dismissible">
                        <span slot="title">Drawer Title</span>
                        <span slot="subtitle">subtitle</span>
                        <div>
                            <p>Drawer content</p>
                            <a href="/">Main</a><br />
                            <a href="/About">About</a><br />
                        </div>
                        <div slot="appContent">
                            <mwc-top-app-bar>
                                <mwc-icon-button slot="navigationIcon" icon="menu"></mwc-icon-button>
                                <div slot="title">Title</div>
                            </mwc-top-app-bar>
                            <div id="outlet" />
                        </div>
                    </mwc-drawer>
                `

                const router = new Router( this.shadowRoot.getElementById( 'outlet' ) )
                router.setRoutes(
                    [   { path: '/'         , component: 'jp-main'  }
                    ,   { path: '/about'    , component: 'jp-about' }
                    ]
                )

                const drawer = this.shadowRoot.querySelector( 'mwc-drawer' )
                drawer.parentNode.addEventListener(
                    'MDCTopAppBar:nav'
                ,   _ => drawer.open = !drawer.open
                )
            }
        }
        customElements.define( 'jp-app', App )

    </script>
    <jp-app />
</body>

勘所

navigationIcon

mwc-top-app-bar の navigationIcon スロットに入れられたボタンが押されると、mwc-drawer の parentNode に対して MDCTopAppBar:nav イベントが送られます。(すみません、ドキュメントが見当たりません><、レポジトリの demos/drawer/のあたりを読んで発見しました)

shadowRoot

WebComponent は constructor の中で attachShadow ってやると、shadowRoot が使えるようになります。プライベートな DOM みたいなものです。これを実際の DOM に connect して表示するようになります。
constructor の中ではまだ実際の DOM にコネクトされていないので、shadowRoot に対して getElement... とか querySelector とか発行して、エレメントをとります。

shadowRoot に対して、今回の記事では innerHTML をスタティックテキストでセットしているだけですが、実際にはダイナミックに生成したい場合が多いと思われます。そんなときは polymer の lit-element を使うという選択肢があります。lit-html という HTML テンプレートライブラリをラップしたものです。

古いブラウザのサポート

Edge とか IE11 とかをサポートするにはポリフィルが必要です。詳しくは以下を参照してください。

https://github.com/material-components/material-components-web-components/blob/master/README.md

最後に

shadowRoot を更新する際に既存のエレメントを再利用して描画の高速化を図るようなベースクラスをたぶんそのうち誰かが作ってくれる気がします。もし知っていたら教えてください。

【Firebase】Cloud FunctionsとVideo.jsで簡単にストリーミング動画配信する

$
0
0

Firebase Cloud FunctionsとVideo.jsで動画のストリーミング配信に挑戦してみました。

node.jsとExpressでサーバーを作り、htmlファイルを用意し、Video.jsを使いsafari以外のブラウザでHLSを再生できるようにします。

(この記事はざっと書き上げたので、細かい部分説明は追記で書き足していきます。。)

まずExpressは割愛するとして、ともかくNode.jsのRequestモジュールを利用して実装しますのでインストールします。

Requestのインストール

$ npm install --save request

Requestには、HTTP通信を行うために必要で便利なモジュールがたくさん入っています。

Request - Simplified HTTP client
https://www.npmjs.com/package/request#streaming

ここでのストリーミングは、データファイルを断片でロードして、クライアントにちょっとずつ送信していきます。

Cloud Functionの内容

FirebaseでFunctionを作成します。
Functionのコード内容は以下の通り

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const express = require('express');
const request = require('request');
const app = express();

app.get("/embed/v/",function(req,res){
    const videopath = '動画ファイルのURL'

    request('videopath')
    .on('response', (response) => {
      console.log(response.statusCode);
    })
    .on('error', function (err) {
      res.sendStatus(404);
      console.log(err);
    })
    .pipe(res);
});

exports.sample = functions.https.onRequest(app);

とてもシンプルでこれだけです。
動画ファイルのURLは、Cloud StorageならそのファイルURLで。
(ルールとか細かい話はここではナシ)

video.jsでHLSを再生できるように

以下のスクリプトが必要なのでインストールします。

<!DOCTYPE html>
<html lang="ja">
<head>
    <title></title>
    <link href="http://vjs.zencdn.net/7.0/video-js.min.css" rel="stylesheet">
    <script src="/js/video.min.js"></script>
    <script src="/js/videojs-contrib-media-sources.js"></script>
    <script src="/js/videojs-contrib-hls.js"></script>
</head>
<body>
  <video id="player" class="video-js" controls preload="none" width="100%" height="100%" poster="http://vjs.zencdn.net/v/oceans.png" data-setup="{}">
    <source src="/embed/v/" type="video/mp4">
    <p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
  </video>

  <script type="text/javascript">
  var player = videojs("player", {
    html5: {
      hls: { withCredentials: true }
    }
  });
  </script>
</body>
</html>

以上です。
Expressを使っているので、ルーティングパスでファイルIDを取得して、EJSのテンプレート使って動的に動画配信すると良いでしょう。

bot用フレームワークなしでslack botを作る

$
0
0

はじめに

以前slack botを作ろうとbotkitを用いて作成していたのですが、herokuで稼働した時にちょっとした問題があったのでbotkitなしでslack botを作った際の知見を共有したいと思います。
*記事書くのは初めてなので拙いと思います。

botkitとherokuの問題点と解決策

問題は2つ
1. botkit内でexpressを使用しているため、1つのプロセス内に他の機能を盛り込めないこと
2. herokuの無料枠では1つのプロジェクトにつき1つのプロセスしか動作できないこと

この2つがいい感じに噛み合ってしまい、slack botの機能と外部に提供するAPI機能の両立ができないのです。
この問題の解決策として、機能を追加するための利便性とbot稼働とAPIサーバを両立するためにはbotフレームワークなしで自作するという考えに至りました。
自分でexpressを立てればbot以外の機能を盛り込みつつ、1つのプロセスで動かすことができるからです。

環境

Node.js (v11.11.0)
ESMAScript2015

Babelは導入するのが面倒臭かったので使わず、拡張子をmjsにして動作させています。
参考にした記事: Node.js(v8.5.0以降)でBabelやwebpackを利用せずにimport/exportを利用する

まずは公式ページでbotを作成

https://api.slack.com/
ここからStart Building → Create New Appでslack botを作成します。
start_building.png

今回のbot名は偶々目に入った可愛いeevee(イーブイ)にしました。
ワークスペースは共用の場だと色々と迷惑になるので自分専用のワークスペースです。

このままではワークスペースに登録するbotがないのでBotUSerページのAdd a Bot Userで追加します。

Oauthを通す

https://api.slack.com/authentication/oauth-v2 のページ中程にあるフロー画像の通りに進めていきます。
そのために先ずはRedirect URLを設定します。
oauth_permission.png
Oauth&PermissionsページのAdd New Redirect URLにhttp://localhost:8000/oauthと設定します。
また後々Real Time Messaging APIを使ってWebSocketを通すので、その時用にScopesにAdmin, identify ,botの3つを追加しておきます。

以下oauth認証まで行うコードです。
access_tokenが取れればOKです。
CLIENT_IDやCLIENT_SECRETは.evnファイルに記述し、dotenvを使うことで環境変数としています。

// 環境変数用
import dotenv from 'dotenv';
dotenv.config();

import request from "request";
import express from "express";
import bodyParser from 'body-parser';

const app = express();

// port番号設定
app.set('port', (process.env.PORT || 8000));

// サーバ起動時
app.listen(app.get('port'), function () {
  console.log('server launched');
});

// bot oauth認証用
app.get('/oauth', function (req, res) {
  res.header('Content-Type', 'text/plain;charset=utf-8');

  let code = req.query.code;
  if(code){
    res.send('oauth setting is collect');
    oauth(code);
  }
  else{
    res.send('oauth setting is wrong');
  }
});

function oauth(code) {
  request.post({
    url: 'https://slack.com/api/oauth.v2.access',
    form: {
      code: code,
      client_id: process.env.CLIENT_ID,
      client_secret: process.env.CLIENT_SECRET
    }
  }, function (err, res, body) {
    let access_token = JSON.parse(body).access_token;
    console.log(access_token);
  });
}

コードの実行はterminalにnode --experimental-modules --es-module-specifier-resolution=node main.mjsで動きます。
実行するためにだらだらと書く必要がありますが、Babelの設定が要らないので環境導入でつまずく心配がないです。

次にoauthフロー画像の下にあるURLをブラウザに打ち込みます。
https://slack.com/oauth/v2/authorize?scope=incoming-webhook,commands&client_id=xxxxxxx.xxxxxxxxxxx
xxxxxxx.xxxxxxxxxxxは自身のCLIENT_IDに書き換えてください。
oauth_redirect.png
URLをブラウザに打ち込んでこのような画面が出てきたらOKです。
適当にチャンネルを選択してAllowを押し、エラーがなければterminalにaccess_tokenが出るのでメモっておきましょう。
もしメモり忘れてもOauth&PermissionsページのOAuth Tokens & Redirect URLsに記述されています。xoxbの方です。

RTMを用いてWebSocketを通す

RTMの詳しいことはこちらに書いてあります。https://api.slack.com/rtm
メモっておいたaccess_tokenも.envファイルに書き込んでおきます。

// サーバ起動時
app.listen(app.get('port'), function () {
  console.log('server launched');
  // 新しく追加
  rtmStart();
});

// Real Time Messageing APIの開始
function rtmStart() {
  request.get({
    url:'https://slack.com/api/rtm.start',
    qs: {
      token: process.env.ACCESS_TOKEN
    }
  }, function (err, res, body) {
    let websocketURL = JSON.parse(body).url;
    socket(websocketURL);
  });
}

import WebSocket from 'ws';
let ws = null;

function socket(url) {
  ws = new WebSocket(url);

  // websocketが繋がった時
  ws.on('open', function () {
    console.log('Open WebSocket');
  });

  // botが居るチャンネルで何か書き込まれた時
  ws.on('message', function (data) {
    data = JSON.parse(data);
    // slackに書き込んだ言葉を表示する
    console.log(data.text);
  });

  // websocketが閉じた時
  ws.on('close', function (data) {
    console.log('Close WebSocket');
  });
}

これでWebSocketが通りました。
botが居るチャンネルで何か打つとその言葉がterminalに表示されるます。
また、最初に必ずdata.typeがhelloだけを持つdataが通るのでundefindも出ているはずです。

slack botに機能追加

これでようやく準備が終わり、思うがままにslack botに機能を追加できます。
ws.on('message', function (data)内のconsole.log(data.text);の下にmainProccess関数を呼んであげるように追記します。

// ここがslackからデータを受け取るメイン処理
function mainProcess(data) {
  // 接続時にhelloが通るので
  if (data.type === 'hello')
    return;

  // ハウリングするのでbotには反応しない
  if (data.bot_id !== undefined)
    return;

  let type = data.type;
  if (type === 'message') {
    // subtypeを持っているmessage typeには反応しない
    if (data.subtype !== undefined)
      return;

    let text = data.text;
    let is_mention = text.match(/<@(.*)/);
    let mention_user = undefined;
    let mention_text = undefined;
    // メッセージではなくファイルならfile_idが存在する
    let file_id = data.files !== undefined ? data.files[0].id : undefined;

    // メンションが付いているなら文章本体だけを抜き出す
    if (is_mention) {
      let tmp = text.split('<@')[1];
      mention_user = tmp.split('> ')[0];
      mention_text = text.split('> ')[1];
    }

    // Message型はuser_id, text, channel_id, ts, is_mention, mention_user, mention_text, file_idのプロパティを持つ
    let message = new Message(data.user, text, data.channel, data.ts, is_mention, mention_user, mention_text, file_id);
    switchProcess(message);
  }
}

// メンション有り無しやコマンドに応じた分岐処理
function switchProcess(message) {
  // botにメンションなら
  if (message.mention_user === process.env.BOT_ID) {
    // 記事を表示
    if (message.mention_text === 'help')
      api.deleteMessage(message.channel_id, message.ts);
      api.postEphemeral(message.channel_id, 'Qiita記事のページだよ\nhttps://qiita.com/KessaPassa/items/a82b972b7fdedd2e13f7', message.user_id);
  }
}

// メッセージ削除
function deleteMessage(channel, ts, time = 30 * 1000) {
  setTimeout(function () {
    request.post({
      url: 'https://slack.com/api/chat.delete',
      form: {
        token: process.env.ACCESS_TOKEN,
        channel: channel,
        ts: ts,
        as_user: true
      }
    }, function (err, res, body) {
      if (err) throw err;
    });
  }, time);
}

// 普通にメッセージを送る
function postMessage(channel, text) {
  request.post({
    url: 'https://slack.com/api/chat.postMessage',
    form: {
      token: process.env.ACCESS_TOKEN,
      channel: channel,
      text: text
    }
  }, function (err, res, body) {
    if (err) throw err;
  });
}

@eevee helpとbotにメンションをつけてhelpとするとこのQiita記事のURLが返事として返ってきます。

因みにMessage型の中身はこちらになります。

const _user_id_message = Symbol();
const _text_message = Symbol();
const _channel_id_message = Symbol();
const _ts_message = Symbol();
const _is_mention_message = Symbol();
const _mentionUser_message = Symbol();
const _mentionText_message = Symbol();
const _file_id = Symbol();

export class Message {
  constructor(user_id, text, channel_id, ts, is_mention, mention_user, mention_text, file_id) {
    this[_user_id_message] = user_id;
    this[_text_message] = text;
    this[_channel_id_message] = channel_id;
    this[_ts_message] = ts;
    this[_is_mention_message] = is_mention;
    this[_mentionUser_message] = mention_user;
    this[_mentionText_message] = mention_text;
    this[_file_id] = file_id;
  }

  get user_id() {
    return this[_user_id_message];
  }

  get text() {
    return this[_text_message];
  }

  get channel_id() {
    return this[_channel_id_message];
  }

  get ts() {
    return this[_ts_message];
  }

  get is_mention() {
    return this[_is_mention_message];
  }

  get mention_user() {
    return this[_mentionUser_message];
  }

  get mention_text() {
    return this[_mentionText_message];
  }

  get file_id() {
    return this[_file_id];
  }
}

最後に

oauth認証してWebSocktを通せば後はただのプログラミングなのでslack botに色々な機能を持たせることができます。
是非switchProccess関数に色んな処理を追加して自分好みのslackにカスタマイズしてみてください!

この記事を書くに当たって、検証しつつ部分部分を取り除いた形になるのでわかりにくいところがあるかと思います。
ちょっとoauth関係が古いですが現在も弊研究室で稼働しているbotの中身がgithubで公開しているのでそちらを参考にしてみてください。
分報やチャンネル毎にメモ機能など便利なコマンドを追加しています。
https://github.com/KessaPassa/slackbot-extensions

【簡単】GitHub Actionsを使って静的ファイルをFirebase Hostingにデプロイをする【PlayCanvas】

$
0
0

GitHub ワークフローを使用してPlayCanvasのプロジェクトをFirebase Hostingにデプロイをする方法を紹介します。

PlayCanvasとは?

PlayCanvasは、インタラクティブなウェブコンテンツ用のビジュアル開発プラットフォームです。作成するツールとウェブアプリは、どちらもHTML5を使用しています。 プラットフォームはウェブでホストされているため、インストールするものは何もなく、対応されているウェブブラウザを実行する任意のデバイスからアクセスできます。

https://developer.playcanvas.com/ja/user-manual/introduction/

GitHub Actionsを使用する

GitHubのリポジトリで「Actions」を選択します。
1-2440c1b3-78e0-4618-a30d-39c1ea0da573.png

試しにNode.jsのワークフローを選択します。

2-a48c4ec4-c9e1-445c-98b3-9027c469fdc4.png

Start Commitを選択します。

3-1f0fbe35-a386-44dc-8fc9-c33d72bd0440.png

.github/workflowが作成されました。

jibi-9df78823-ef04-4554-895a-90e7a893f09c.png

PushされたためCIが動きます

4-ae86be86-bae4-49b5-9ef2-516e057aa200.png

package.jsonが存在していないリポジトリでスクリプトを実行しようとしているため失敗します。

5-9339acde-3901-47bd-8d32-467e23c8764a.png

nodejs.ymlを書き換えます

name: Node CI

on: [push]

jobs:
  deploy:
    runs-on: windows-latest
    strategy:
      matrix:
        node-version: [10.x]
    steps:
      - uses: actions/checkout@v1
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - name: install
        run: |
          yarn
      - name: yarn install, build, and test
        run: |
          yarn
          yarn playcanvas:init -t ${{ secrets.PLAYCANVAS_ACCESS_TOKEN }} -p ${{ secrets.PLAYCANVAS_PROJECT_ID }} -s ${{ secrets.PLAYCANVAS_SCENES }} -b ${{ secrets.PLAYCANVAS_BRANCH_ID }} -n ${{ secrets.PLAYCANVAS_PROJECT_NAME }} -r ${{ secrets.PLAYCANVAS_REMOTE_PATH }}
          cat playcanvas.json
          yarn playcanvas:download
          yarn deploy --token "${{ secrets.FIREBASE_TOKEN }}" --project "${{ secrets.PROJECT_ID }}"
        shell: bash
        env:
          CI: true

このままだとエラーが出るので環境変数を設定します。環境変数の設定方法「Settings → Secrests」を選択します。

hie-13fd042e-0b86-4dea-81c3-20146abbf196.png

PlayCanvasのAPIキーはこちらから取得します。
aaaa.PNG

 npx playcanvas-tools init
環境変数 playcanvas.jsonの値 取得先
PLAYCANVAS_ACCESS_TOKEN accessToken playcanvas.json
PLAYCANVAS_BRANCH_ID branchId playcanvas.json
PLAYCANVAS_PROJECT_ID projectId playcanvas.json
PLAYCANVAS_PROJECT_NAME projectName playcanvas.json
PLAYCANVAS_REMOTE_PATH remotePath playcanvas.json
PLAYCANVAS_SCENES scenes playcanvas.json

Firebaseの環境変数はこちらのコマンドを実行して取得します。

npx firebase-tools login:ci

Firebase Hostingをまだ使用していない場合には

npx firebase-tools init

こちらを使用してFirebase Hostingの設定を行います。

環境変数 取得先
FIREBASE_TOKEN token Firebase CLI
PROJECT_ID projectId Firebase CLI / Firebase管理画面

この設定をするとPushをされるたびにデプロイをすることができます。

push.PNG


コピペ爆速で各言語環境を構築する方法

$
0
0

とりあえずさくっと言語環境だけ入れたいことはままある。ググるのすらめんどくさい!なので、各言語ごとにコピペ(大事!)で出来る環境構築方法をまとめる。※随時更新予定

対応言語

  • Node.js
  • Ruby

Node.js

git clone git://github.com/creationix/nvm.git ~/.nvm
echo 'source ~/.nvm/nvm.sh' >> ~/.bashrc
nvm help
nvm ls-remote
nvm install v10.16.3
node -v
npm install yarn -g

Ruby

git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build 
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
rbenv --version
rbenv install --list
rbenv install 2.6.5
rbenv global 2.6.5

参考サイト

Node.jsでSOAP Clientを生成する際のoverridePromiseSuffixオプションについて

$
0
0

公式のgithubに書いてあるんだけど、備忘のために記載する。

node-soapでクライアントを生成すると、自動で「(オリジナルメソッド名)+Async」という非同期用のメソッドが生成される。
が、対象となるWSDLに「(オリジナルメソッド名)」「(オリジナルメソッド名)+Async」の2種類がもともと定義されてると、node-soapが作ろうとする「(オリジナルメソッド名)+Async」と元の定義がぶつかってエラーになってしまう。
このための回避オプションとしてoverridePromiseSuffixというのが用意されている。
overridePromiseSuffixでnode-soapがつくるメソッドのSuffixを書き換えることができる。

let option = {
       overridePromiseSuffix:"Async2"
    };
let client = await soap.createClientAsync(wsdl_url , option );

みたいな感じ。

ちなみに、これやってないとPromise側のエラー(Error: Cannot promisify an API that has normal methods)が出る。
ここに実例が書いてあるので参考にしましょう。(ここに記載されてるのはnode-soapではないけど)

PuppeteerでWebスクレイピングしてハマったところをまとめてみた

$
0
0

備忘録

Puppeteerを使ってWebスクレイピングをやっていた時にハマったことを整理しておく。

インストール編

Puppeteerがそもそも起動しなくて困ったことがあった。

puppeteerが動作しない(Ubuntu)

初めてpuppeteerを起動した際にChromiumが立ち上がらなかったことがあった。
ログにhttps://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md にアクセスしてみろと書かれてたのでアクセスしたら、どうやらパッケージをインストールする必要があるとのこと。
端末に以下のパッケージが抜けてないか確認し、必要に応じてインストールする必要があるらしい。

gconf-service
libasound2
libatk1.0-0
libatk-bridge2.0-0
libc6
libcairo2
libcups2
libdbus-1-3
libexpat1
libfontconfig1
libgcc1
libgconf-2-4
libgdk-pixbuf2.0-0
libglib2.0-0
libgtk-3-0
libnspr4
libpango-1.0-0
libpangocairo-1.0-0
libstdc++6
libx11-6
libx11-xcb1
libxcb1
libxcomposite1
libxcursor1
libxdamage1
libxext6
libxfixes3
libxi6
libxrandr2
libxrender1
libxss1
libxtst6
ca-certificates
fonts-liberation
libappindicator1
libnss3
lsb-release
xdg-utils
wget

すべてインストールしたら問題なく起動した。

puppeteerが動作しない(Windows10)

今度はWindows編。
Windowsで実行するとpuppeteer.launch

Error: spawn UNKNOWN

が発生してChromiumが起動しない。

とりあえず https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md を参考に

index.js
const browser = await puppeteer.launch({
  ignoreDefaultArgs: ['--disable-extensions'],
});

と書き直して再度実行したが動かないまま。
他に手段がないか模索していたところ、このGitHub Issueが参考になった。

どうやらpuppeteer経由でインストールされたChromiumに問題があるようだ。

試しに私も以下の手順でやってみた。
1. https://download-chromium.appspot.com/ からChromiumをダウンロード
2. chrome-win.zipDesktop上で解凍(重要)
3. node_module/puppeteer/.local-chromium/win64-n/内にあるchrome-winディレクトリを解凍したものと置き換える

その後再度実行すると問題なくChromiumが起動した。
しかし、ほかのところで解凍すると何故か上手くいかず一度ここでハマった。

パフォーマンスチューニング編

過去にWebスクレイピングを行うにあたり、対象のデータ全てを取得するのに同じサイトにリクエストを大量に投げなくてはならないことがあり、相手のサーバーに負荷をかけないように注意する必要があった(下手するとDoS攻撃とみなされかねない)。同時に自身の端末(サーバー)にも極度の負荷がかからないように調整する必要があり、そのために奮闘したことをまとめてみた。

処理時間が長いためLambdaが使えない

最初にこちらのリソースの確保にあたり、AWS Lambdaを使って対応できないか考えた。
LambdaはTimeoutの時間を最大15分に設定することができる。
しかしスクレイピングの処理時間は長いと数時間かかる上に、今回はリクエスト数が多いため複数立てても足りないことが分かった。

結局EC2を複数立ち上げて対応することにした。

sequencialに処理が行われない

方針として、各端末に対して事前に用意していたURL情報をテキストファイルに書き込み、その後fs.createReadStreamreadlineを使ってURLを一つずつ読み込みスクレイピングを実行するようにした。

しかし、試しに実行するとreadline.on全て非同期に処理が行われてしまう問題が発生した。(forEachと同じ類)
これでは仮にURLのリストが1万件あったとしたら、全てのリクエストが一斉に送られてしまう。
(先にソケットが切れるか端末のCPUやメモリが枯渇するだろうけど)

非同期で処理が行われないよう方法を模索した結果、ある程度の効率を考慮したうえで以下の方法で対応することにした。
1. URLリストのテキストファイルをあらかじめ複数に分割しておく
2. パラメータにテキストファイル名を持つ非同期関数を作成し、その中にfs.readFileSyncでテキストファイル内のURL情報を取得、その後splitで配列化してforループを回すことで逐次処理にした処理を記載する
3. パラメータにそれぞれのファイル名を渡して関数を実行。

メモリリークが発生

Failed to launch chrome!
(node:25974) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 exit listeners added. Use emitter.setMaxListeners() to increase limit

最初のエラーメッセージからおそらくpuppeteer周りで問題が発生していることがわかったので原因調査を行った。
原因は以下の箇所によってメモリリークが発生していたためだったことが分かった。

scraping.js
    for(let url of urls) {
        let browser = await puppeteer.launch();
        let page = await browser.newPage();
        await page.goto(url);

        // 処理

        await browser.close();
    }

要因として二つ考えられた。

  1. ブラウザ開きすぎ
    繰り返し文の中でpuppeteer.launch()をしているため、これではURLの数だけブラウザを立ち上げることになってしまう。
    ブラウザは一度だけ立ち上げれば十分。

  2. page.close()してない
    ページを開いたが閉じていなかった。
    使わないページは閉じておくこと。

修正して以下のように書き直したところ、メモリ使用量が改善されて問題が解消した。

scraping.js
 let browser = await puppeteer.launch(); // 呼び出すのは一度だけ
    for(let url of urls) {
        let page = await browser.newPage();
        await page.goto(url);

        // 処理

        await page.close(); // 不要なページは都度閉じる
    }
 await browser.close();   // ブラウザも忘れずに閉じる

Google Cloud Storageで画像操作したメモ

$
0
0

画像uploadするにはどうすればいいんだろう
いろいろな記事を参考にしてみた
うまくいかない?
ちょっと!よくよく見るとこの記事、リファレンスに書かれてるものとオプション名違ってるじゃん!そりゃ期待通り動かないよ!
ってことがあったので、メモっておきます。

前提

upload

const {Storage} = require('@google-cloud/storage');
const storage = new Storage({
    projectId: 'hogehoge-project',
    keyFilename: './serviceAccountKey.json', // 前提で取得済みのキーファイル
});

const BUCKET_NAME = 'hogehoge-project.appspot.com';

storage
.bucket(BUCKET_NAME)
.upload('./image.png' /* uploadしたいファイル */, { destination: 'uploaded_image.png' /* storageにこのファイル名でuploadされる */ })
.then(() => { /* success */ })
.catch((err) => { /* error */ });

delete

storage
.bucket(BUCKET_NAME)
.file('uploaded_image.png')
.delete()
.then(() => { /* success */ })
.catch((err) => { /* error */ });

リファレンス

バケットの操作周りをみれば良い
https://googleapis.dev/nodejs/storage/latest/Bucket.html

Electronインストール for Ubuntu18

$
0
0

Ubuntu18 PCへのElectron環境インストール手順とサンプルアプリの実行方法をまとめています。

Electron環境構築

Node.js等の必要なパッケージ類をインストールします。

$ sudo apt install nodejs
$ sudo apt install npm
$ sudo apt install libappindicator1

Electron本体(中身はChromium + Node.jsライブラリ群)はグローバルな環境にパスを通してインストールしておきます。

$ sudo npm install electron -g

サンプル実行

Quick start Electron demo app

よく出てくるElectronのクイックスタートサンプルです。

$ git clone https://github.com/electron/electron-quick-start
$ cd electron-quick-start
$ npm install
$ npm start

YouTube Music Desktop App

YouTube Musicのデスクトップアプリです。よく出来ています。

$ git clone https://github.com/adlerluiz/ytmdesktop
$ cd ytmdesktop
$ npm install
$ npm start
Viewing all 8838 articles
Browse latest View live