20210305のNode.jsに関する記事は4件です。

ContentType書き換えでImageMagickのリサイズを成功させる

はじめに

先日、「オブジェクト名の変更」でアップロード済み画像のサムネイル生成を簡単にという記事を書きました。
その作業の中で、ContentTypeの問題によって一部の画像でImageMagickのリサイズ(サムネイル生成)に失敗していることが分かりました。
今回はなぜそうなったのか、どう対処したのかについてご紹介します。

リサイズに失敗する原因

ContentTypeを指定しないと、application/octet-streamになる

FirebaseのCloud Storageにファイルをアップロードする時にContentTypeが指定されていないと、判別できない場合はアップロード方法に応じてapplication/octet-streamまたはapplication/x-www-form-urlencodedに設定されます。
実際、一部の画像がapplication/octet-streamになってしまっていました。

application/octet-streamではImageMagickで画像のリサイズができない

画像のリサイズはImageMagickを利用しています。
ImageMagickでContentTypeがapplication/octet-streamだと、画像のリサイズができません。

サムネイル生成できるようにする対処法

ContentTypeを書き換える

ContentTypeがapplication/octet-streamの画像が存在することでImageMagickのリサイズが失敗していることが分かりました。
そこでContentTypeを以下のように書き換えましょう。

// ファイルからメタデータを取得する
const file = await bucket.file(imagePath)
await file.getMetadata()

if (file.metadata.contentType == 'application/octet-stream') {
    // octet-streamだったら、image/jpegなどcontentTypeを書き換える
    const contentType = 'image/jpeg'
    await file.setMetadata({
        contentType: contentType,
    })
}

サンプルでは簡単なソースコードをご紹介するために、application/octet-streamだったらimage/jpegに決め打ちで置き換えるようにしています。
実際のソースコードはjpg, jpeg, png, gifなど、必要なContentTypeを拡張子で判定してセットしています。

このようなcontentTypeの書き換えを行ったところ、無事にImageMagickで画像のリサイズができるようになりました。

最後に

このようなContentTypeの書き換えが発生しないように、本来はアップロード時にきちんと指定するのが良いと思います。
とはいえアップロード済みの画像のContentTypeを書き換える場面もあるかもしれません。
そんな時はこちらの方法を試してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Apps Script練習 (Gmailの新着メールをLINEに転送)

GASを練習したいと思い、また自分自身Gmailのメールを見逃してしまう事が多いのでそれをなんとか出来ないかと思いスクリプトを作成しました。

gas.js
const LINE_NOTIFY_TOKEN = PropertiesService
  .getScriptProperties()
  .getProperty('LINE_NOTIFY_TOKEN')
const endPoint = 'https://notify-api.line.me/api/notify'

// 1. 転送したいメールの送信元アドレスを指定
const fromAddress = [''].join(' OR ')
// 2. トリガーの設定間隔と合わせる
const minutesInterval = 5

function main() {
  const notices = fetchNotices()

  if (notices.length === 0) {
    return
  }

  for (const notice of notices) {
    send(notice)
  }
}

function fetchNotices() {
  const now = Math.floor(new Date().getTime() / 1000)
  const intervalMinutesAgo = now - (60 * minutesInterval)
   // 3. 検索条件を設定
  const query = `(is:unread from:(${fromAddress}) after:${intervalMinutesAgo})`

  // 4. メールを取得する
  const threads = GmailApp.search(query)

  if (threads.length === 0) {
    return []
  }

  const mails = GmailApp.getMessagesForThreads(threads)
  const notices = []

  for (const messages of mails) {
    const latestMessage = messages.pop()
    const notice = `
--------------------------------------
件名: ${latestMessage.getSubject()}
受信日: ${latestMessage.getDate().toLocaleString()}
From: ${latestMessage.getFrom()}
--------------------------------------

${latestMessage.getPlainBody().slice(0, 350)}
`
    notices.push(notice)
    // 5. メールを既読にする
    latestMessage.markRead()
  }

  return notices
}

function send(notice) {
  if (LINE_NOTIFY_TOKEN === null) {
    Logger.log('LINE_NOTIFY_TOKEN is not set.')
    return
  }

  const options = {
    'method': 'POST',
    'headers': {'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`},
    'payload': {'message': notice},
  }

  UrlFetchApp.fetch(endPoint, options)
}

GmailにおけるThreadとMessageの違いについて理解に時間がかかりました。
 Thread: あるメールとそのメールに対する一連の返信(配列みたいになる)
 Message: 単体のメール1つ

GASもLINE APIも、本当便利…
GASって、他にもいろんな事できるんですね…Googleスプレッドシートを活用して議事録をいじったり…
まだまだ知らない事ばかりなので、一度ガッツリ時間を取って勉強したい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NRIハッカソン bit.Connect 2020 に参加してきたまとめ

はじめに

NRIハッカソン bit.Connect - Hack for NEWSTYLE というイベントに参加してきました。
https://bitconnect.nri.co.jp/

IBMクラウドのファンクションを活用していたのを評価され、IBM賞をいただきました!

企業さんの技術サポートも手厚く、第一線の方に直接Slackで手取り足取り教えてもらえたのでめちゃくちゃ贅沢な時間を過ごせました!

つくったもの

お地蔵さんデモ動画
https://www.youtube.com/watch?v=8mviNWsBKp8

構成図
コンセプト_page-0016.jpg

ハードウェア側の動作
ojizo_kasa_kick.gif

コンセプト_page-0015.jpg

余談ですが、最初は磁石にピップエレキバンを使ってみたんですが、意外と磁力が弱く、動作が安定しないので、100均で買った磁石に付け替えました。
IMG_0646.jpeg

ストーリーなど作品の詳細はこちら
https://protopedia.net/prototype/2151

ハードウェアの実装

今回は2人チームで参加し、私の担当がobnizを用いたハードウェアの部分だったので、実装について困ったことや役に立ったことなどをつらつらと書いていきます

IBMクラウドをはじめて触った友人(AWSは日常的に触ってる人)曰く、とても使いやすかったとのこと
・AWSみたいな迷子になりそうなUIではなく、やりたいことがどうしたらいいのかすぐわかるような素晴らしいUI
・ハッカソンで使うなら、初めてでもこっちの方が楽かもしれない
2021-02-25_22.28.51.png
2021-02-25_22.28.01.png

とのことなので、私も触ってみようかなと思いました。

構成について

今回の作品でobnizでやるべきことは以下の3点でした。
1. IBMクラウドのファンクションからAPIを叩いてハードウェアで動作をさせる
2. 1が起きてから元に戻るまでの時間を計測する
3. 2の結果をIBMクラウドのファンクションに知らせる

1~3の流れは、ユーザーがすぐ行動すれば一瞬ですが、なかなか行動しないユーザーもいると想定され、待機時間が読めません。
なので本来は、1の機能だけを請け負うobnizと2~3の機能だけを請け負うobnizの2台構成でやるべきですが、所持台数の制限と、ハッカソンという限られた時間の中で対応するために、1~3を一つのobnizで実装できるように試行錯誤を行いました。(obnizの木戸さんありがとうございました!)

obnizクラウドでWebhookURLを吐き出す

結論から言うとこれは、今回の要件には適していなかったので他のサービスを利用しました。

obnizクラウド はobnizのホスティングサービスで簡単にWebhookURLを吐き出せました(今回初利用)

obniz のコンソールにアクセスし、デバイスを選択します(デバイスをまだアカウントに紐づけてない人は紐付けをしてから)
スクリーンショット 2021-03-04 22.22.10.png
スクリーンショット 2021-03-04 22.22.20.png

いろんなテンプレートがありますが、今回は「空のプロジェクト」を選択
スクリーンショット 2021-03-04 22.22.34.png
スクリーンショット 2021-03-04 22.22.51.png

コードをちゃちゃっと書いて

<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" />
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script src="https://unpkg.com/obniz@3.x/obniz.js" crossorigin="anonymous" ></script>
  </head>
  <body>
    <div id="obniz-debug"></div>
    <div class="container">
      <div class="text-center">
        <p id="start_date-text">開始時間 : </p><p id="start_date"></p>
        <p id="end_date-text">終了時間 : </p><p id="end_date"></p>
      </div>
    </div>

    <script>
      //type in your obniz ID
      var obniz = new Obniz("OBNIZ_ID_HERE");

      obniz.onconnect = async function () {
        // obnizクラウド上での動作かどうかの判定
        if (Obniz.App.isCloudRunning()) {
          obniz.display.print('===== start');

          // ソレノイドを0,1に繋ぐ
          var solenoid = obniz.wired('Solenoid', {gnd:0, signal:1});
          solenoid.click();

          // マグネットスイッチを9,10,11に繋ぐ
          var ct10 = obniz.wired("CT10", {gnd:9, vcc:10, signal: 11});

          myFunc = async function(){
            const start_date = new Date();
            $("#start_date").text(start_date);
            obniz.display.print("start_date");
            obniz.display.print(start_date);

            // マグネットスイッチがONになる(笠が被される)のを待つ(ハッカソンみあふれるコード)
            await ct10.stateWait(true); 

            const end_date = new Date();
            $("#end_date").text(end_date);
            obniz.display.print("end_date");
            obniz.display.print(end_date);

            const score = end_date - start_date

            obniz.display.draw(score);
          }

          // APIをキックしてすぐだと、誤作動するので若干ディレイをかけて実行
          setTimeout(myFunc, 1000);

          // 実際に動かしたコードではないので雰囲気が伝われば
          $.ajax({
             type: 'POST',
             // このURLはすでに無効です
             url: 'https://b3fcdcbe.us-south.apigw.appdomain.cloud/ojizo/record',
             data: {
               score: score
             },
             dataType: 'json'
           });
        }
      }
    </script>
  </body>
</html>

アプリの設定からブラウザ実行にチェックを入れて設定を更新します
スクリーンショット 2021-03-04 22.25.47.png

デバイス一覧から「Webhook URLの確認」が選択できるようになります
スクリーンショット 2021-03-04 22.26.52.png

Webhook URLが発行されます
スクリーンショット 2021-03-04 22.27.12.png

注意点

obnizクラウドではawaitをかけて待機していても、接続が最大30秒までなので今回の要件的には適さないようでした。

待機ができる

以上のことをobnizの木戸さんに相談したところ、 repl.it というサービスで実現できるかもとの情報をいただき、試してみました。
repl.itではNode.jsのホスティングが無料ででき、APIとしても公開できます。

コードをnodejs用にすこし修正し(こちら大変たすけていただきました?‍♂️ありがとうございました?‍♂️)

const express = require('express');
const Obniz = require('obniz');
const fetch = require("node-fetch")
const app = express();
const port = 3000;
let solenoid = null;
let ct10 = null;
let obniz = new Obniz("xxxxxxxx");
obniz.onconnect = function(){
  console.log("connected")
  solenoid = obniz.wired('Solenoid', {gnd:0, signal:1});
  ct10 = obniz.wired("CT10", {gnd:9, vcc:10, signal: 11});
}
// トップページに来たときに
app.get('/', async (req, res) => {
 //とりあえずレスポンスは先に返す(ブラウザ対策)
 res.json({"status": "OK"});
 if (obniz.connectionState === "connected") {
    obniz.display.print('===== start');
    solenoid.click();
    const start_date = new Date();
    // $("#start_date").text(start_date);
    obniz.display.print("start_date");
    obniz.display.print(start_date);
    await ct10.stateWait(true); 
    const end_date = new Date();
    obniz.display.print("end_date");
    obniz.display.print(end_date);
    score = end_date - start_date
    // $.ajaxはnodejsで使えないのでfetchに変換
    const data = {
        user_id: obniz.id,
        start_date: start_date,
        end_date: end_date,
    };
    await fetch('https://b3fcdcbe.us-south.apigw.appdomain.cloud/ojizo/record'
    , {
      method: "POST",
      mode: 'cors',
      headers: {
      'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    })
  }
})
app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
});
console.log("wakeup")

これで10分は待機できるようになりました!

まとめ

・ひさびさのハッカソンはすごく楽しい
・ピップエレキバンの磁力はそんなに強くない
・ハードウェアは極力シンプルな動きにしないとツラくなる(今回で言うと、やっぱどうにかobniz二台構成にしたほうがやりやすかったろうなぁ)
・IBMクラウドのファンクションはわかりやすくて便利だぞ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【TypeScript】型付けしてエラーを片付ける

この記事の目的

TypeScriptを使うとJavaScriptで発生する予期せぬバグが減るっていわれてるけど、具体的にどのようなケースでエラーが減るの?という疑問を具体的な例で解決する。

どんなエラーが片付くか?

ケース1: タイプミスが減りバグが抑制される

TypeScriptは、JavaScriptで起こりうるタイプミスや型付けができないことによって発生するバグを抑制してくれます。

以下のJavaScriptのコードを見てみましょう。
このコードは最後のコンソール出力でnoteBook.colorを出力しようとしていますが、noteBook.colorrとなっているためタイプミスが発生していると考えられます。

sample.js
const noteBook = {
  color: "",
  price: 980,
  size: "A4"
}
console.log(noteBook.colorr); // colorをcolorrにしてある

こちらのJavaScriptのコードを実行してみると、undefinedが出力されます。
特にエラーの文言は出力していないため、この変数を使った処理で予期せぬバグが発生しない限り、このエラーには気付けません。

$ node sample.js
undefined

そこで、TypeScriptの登場です。
上記のsample.jssample.tsにコピペしてみます。

sample.ts
const noteBook = {
  color: "",
  price: 980,
  size: "A4"
}
console.log(noteBook.colorr); // Property 'colorr' does not exist on type '{ color: string; price: number; size: string; }'. Did you mean 'color'?

JavaScriptの場合だとタイプミスがあった場合でもundefined が返却されるだけでしたが、TypeScriptの場合はコーディング中に指摘してくれます。
エラーとしては、type '{ color: string; price: number; size: string; }' にはプロパティ 'colorr' が存在しません。colorのことでしょうか?と出力してくれています。
VsCodeでコーディングした場合はこのように指摘してくれます。(ありがたい...!!)
image.png

ケース2: 静的型付けで変数の型を制約させる

JavaScriptの変数は動的型付けとなり、どんな値でも変数に代入することができます。
しかし、複数人でアプリケーションを構築する場合、予期せぬ値を使用されてしまうことがあります。
例えば、「数値型を引数に取りたい関数に文字列を渡してしまう」などです。
TypeScriptでは予め変数の型を定義できるため、他の値が設定されそうになると「この変数(引数)は数値型なので文字列型は代入できませんよ」と伝えてくれる機能があります。

まずは以下のJavaScriptのコードを見てみましょう。

sample2.js
function sum(a, b) {
  console.log(a + b);
}

sum(1, 2);

こちらのコードを実行すると以下のように出力されます。

$ node sample2.js
3

当たり前ですね。笑
次に、sum関数の引数に文字列を入れてsample2.jsを実行してみようと思います。

sample2.js
function sum(a, b) {
  console.log(a + b);
}

sum(1, "2"); //2つ目の引数に文字列"2"を代入

こちらを実行したら、一般的にはエラーを出力してもらいたいところですがJavaScriptの場合は以下のようにエラーを出力せずに実行できてしまうんです。。

$ node sample2.js
12

このようにsum関数に文字列"2"が代入されたことによって、 JavaScriptさんはa + bを文字列の連結と認識してしまい"1" + "2" = "12"という処理をしてしまったようです。
これはsum関数を実装したプログラマーにとっては予期せぬ振る舞いですね?

こういったエラーですが、TypeScriptの型定義で解決できます!!
以下のコードは上記のsampe2.jsのsum関数の引数に対して、型定義を施してあります。
型定義の方法は簡単で、引数(変数)のとなりに:型を書くことで実現できます。
今回は、引数には数値型しか受け付けたくないので、引数の隣に:numberを記述しています。

sample2.js
function sum(a: number, b: number) {
  console.log(a + b)
}

sum(1, "2"); //Argument of type 'string' is not assignable to parameter of type 'number'.

お、TypeScriptさんはコード実行前に最後の行に対して、エラーを指摘しているようです。
string 型の引数は number 型のパラメータには代入できません。と指摘されています。
そのとおりですね。とても親切。

ちなみにVsCode上だとこのようにエラーが表示されます。
image.png

まとめ

以上がTypeScriptを使うことによって、エラーが解決される具体的なケースでした。
TypeScriptを使えば、エラーに迅速に気づけますし、バグを含んだコードをcommitするリスクも低減されるので手戻り工数も削減されます。

それでは、よいTypeScriptライフを!

もし、よろしければLGTMいただけると幸いです!(ブログを書く励みになります!)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む