20201227のNode.jsに関する記事は5件です。

glitchからGASにpost通信する

初書:2020/12/27

前書き

GlitchからGoogle App Script(GAS)にPOST通信をしたかったので、その時のメモ。
ちなみに、今回はGlitchを使用しているのでGlitchと書いているが、内部はnode.jsなので、実質node.jsとGASのpost通信と言っても過言ではない(ハズ)

glitch側

内部がnode.jsのため、普通のXMLHttpRequestやajaxは使用できない1
そのため、今回はaxiosというのを使用してみる

ちなみにgoogle検索をかけると、requestというのもあるが、どうやら既に非推奨らしい。

インストール

glitchではpackage.jsonに書き足すだけでいいので、単純にpackage.jsonの"dependencies"の欄に"axios": "^0.21.0"を書く。

post通信

では実際に通信していく。今回はjsonを使用してデータのやり取りを行うことにする。

const axiosBase = require("axios"); // どこかに書いておく。
const url = "/macros/s/xxxxx/exec"; // gasのドメイン以降のurl
const data = {"key" : "value"}; // 送信するデータ


const axios = axiosBase.create({
    baseURL: "https://script.google.com", // gas以外の場合はそれぞれ書き換え
    headers: {
        "Content-Type": "application/json",
        "X-Requested-With": "XMLHttpRequest",
    },
    responseType: "json",
});
axios.post(url, data)
    .then(async function (response) {
        const responsedata = response.data; // 受け取ったデータ一覧(object)
    })
    .catch(function (error) {
        console.log("ERROR!! occurred in Backend.");
        console.log(error);
    });

簡単な説明

urlのところはgasから取得できるurlを入れる。
dataは送信するpostデータを入れる。ちなみにconst設定しているが、後に追加するならletとかの方がいいかもしれない

axios.post(url, data)で送信し、thenで受け取る。エラーがあればcatchへ行く。
受け取ったデータはresponseに入るので、ここで自分の好きなようにデータを扱う。

GAS側

GAS側はdoPostで受け取るだけなのだが、通常のe.parameterでは取得できないため、e.postData.contentsを使用する。

function doPost(e) {
  let parameter = {}; // post値を受け取った時
  try{
    parameter = JSON.parse(e.postData.contents);
  }catch(e){
    // エラー処理
  }
  // 通常処理
  const obj = {};
  return ContentService.createTextOutput(JSON.stringify(obj));
}

parameterに受け取ったpost値が入っているので、これを使用して処理する。
そして最後にobjをjsonにして返す。

終わりに

一度書ければあとは使いまわせる簡単なものなので、とりあえずこれを使おうかなと思う。
ちなみにglitch及びGASは両方触りたてなので、もしおかしいところがあれば突っ込んでください。

参考サイト

[axios] axios の導入と簡単な使い方 - Qiita


  1. 厳密には専用のnpmをインストールしたら出来そうではあるが、試してない 

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

オウム返しWebチャットボットにQ&Aコミュニケーション機能を追加する

オウム返しWebチャットボットにQ&Aコミュニケーション機能を追加する

はじめに

この記事は前回記事の続きです

https://qiita.com/abemaki/items/cd0bfa99cc2fab4660f5
説明が細かくなりすぎないように今回の記事は完成品のソースコードと
各ソースコードに対して簡易な説明を入れるようにしています。

 

完成品のサンプル画面はこちら

※herokuなので初回立ち上げ少し遅いです。
https://ponkotsueasychatbot.herokuapp.com/

image.png

 

すべて無料で実現できる

ので、お勉強や検証やアイディア創出に最適かと思います。

 
 

この記事でやること

前回作成した単純オウム返しのWebチャットボットに簡単なQ&Aコミュニケーション機能を追加します

image.png

Q&Aコミュニケーションのナレッジベースはエクセルで管理します。
全文検索を用いたマッチ度で質問を判定して、回答を返します。
image.png

使用するライブラリ と 導入手順

Sheets.js

エクセルを読みこんでプログラム上で利用するために活用するライブラリ。
https://www.npmjs.com/package/xlsx

Elasticlunr.js

ブラウザ検索およびオフライン検索用のJavascriptの軽量全文検索エンジン。
http://elasticlunr.com/

導入方法は前に書いた以下の記事と同様
https://qiita.com/abemaki/items/2f1d5ec7e09ce7371431
※今回は完成品ソースコードを公開しているので、細かい導入手順は割愛。
 各ソースに対して簡易な説明を書いています。
 

 

エクセル読み込みライブラリ と 全文検索ライブラリでQ&Aコミュニケーションボットを実現するためのイメージ図

image.png

ソースコード一式 と 解説

ソースコード一式

https://github.com/makiabe/easyqnawebbot

 
 
 

解説

今回追加したファイル
image.png

knowledgeBaseフォルダ
QANDA.xlsx
Q&Aやコミュニケーションのためのナレッジを保持しています。
登録しているデータは以下の通り。

Question Answer
こんにちは こんにちは。 お元気ですか?
おはよう 今起きたのですね。 おはようございます。
お腹がすいた 僕もお腹がすきました
寒いね 寒いんですか! 風邪ひかないようにしてください。
暑いね 暑いんですか? 僕はロボなので暑さは感じません。
おやすみなさい もう寝るんですか? おやすみなさい。
2020年の流行語大賞は? 2020年の流行語大賞は以下のようになっています。 ■年間大賞 3密(小池百合子さん/東京都知事) ■トップテン 愛の不時着(ヒョンビンさん/俳優) あつ森(あつまれ どうぶつの森)/任天堂株式会社 あつまれ どうぶつの森 開発チームさん アベノマスク(特定非営利活動法人サラダボウルの皆さん) アマビエ(湯本豪一さん) オンライン○○(株式会社 東北新社の皆さん) 鬼滅の刃(吾峠呼世晴さん) GoToキャンペーン(GoToトラベル、イートを活用した皆さん) ソロキャンプ(ヒロシ さん/芸人) フワちゃん(フワちゃん さん/ユーチューバー、芸人)
ぴえん 悲しいんですか? 私もぴえんです
ソーシャルディスタンスってなんですか? 社会距離拡大戦略は、感染症の拡散を停止または減速させることを目的とした、医薬品を使わない感染抑制のための手段のことです。
社会距離拡大戦略ってなんですか? 社会距離拡大戦略は、感染症の拡散を停止または減速させることを目的とした、医薬品を使わない感染抑制のための手段のことです。

libフォルダ
bot.js
botの基本動作を管理するために作成
※LINE BOT用のコードが少し混じっているので、後日削除します。

elasticlunrsearch.js
全文検索ライブラリ・形態素解析ライブラリを活用して
ユーザーからの質問に対し回答を作成するために作成

knowlegeBase.js
エクセルからデータを抽出して、QとAにマッピングするために作成

lunr.jp.js
lunr.stemmer.support.js
検索エンジンを日本語対応させるために必要

TinySegmenterのインスタンス生成処理を若干修正しています
//var segmenter = new lunr.TinySegmenter(); // インスタンス生成
var segmenter = new (require('./tiny_segmenter-0.2.js'))(); // インスタンス生成

tiny_segmenter-0.2.js
日本語トークナイザ
requireできずにエラーになるので最終行に以下を追加しました
module.exports = TinySegmenter;

 
.env
BOTが質問を理解できないと判断するさいの閾値をデフォルトで25%としています。
BOTの確信度が25%を超えている場合、質問に対する回答を返答。
25%を下回る場合、オウム返し。
image.png

BOTが質問を正確に理解するために必要な点は以下の2点
・形態素解析の精度を高める
・ナレッジの精度を高める

上記以外だと中間閾値で複数選択回答をする等のやり方もありますが
Webでこれを実現する場合、複数選択用のデザインを作成しなければいけないため今回は割愛しました。

次回はGoogle Analyticsと連携して
質問の問い合わせの傾向分析 や メンテナンスについて記事を書こうかと思います。

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

NestJS+ClamscanでセキュアファイルアップロードAPI

はじめに

Webシステムでファイルアップロードする場面って色々ありますよね。
イントラ内の特定利用者が使うシステムならある程度信頼してもいいのかもしれませんが、
コンシューマー向けで不特定多数の人が利用する場合、悪意が無くてもウイルスファイルをアップされるリスクありますよね?
ファイルを受領した側が送信者の言われるがままに、拡張子を変えたりしてマルウェアに感染ってことも想定されます。

今ではSassサービスなどもあると思いますが、ここではバックエンドAPIでClamAVを使ったスキャンの例を実装してみます。

前提/環境/注意事項

<前提>
NodeJS / Express / NestJS がある程度分かっている人(細かい説明は割愛するので)

<環境>
・Windows10
・VSCode
・DockerDesktop
・Node(Express)
・NestJs

<注意以降>
・セキュリティに関わる部分なので、あくまで参考程度にお願いいたします。
実際は、外部から受領するファイルに関して、様々なバリデーション(拡張子、MIME、ファイルヘッダ)が必要となります。
実際のサービスに実装する場合、テストを十分に行い、セキュリティ専門家の監査を受けた上でサービスインする事をお勧めします。

準備1(プロジェクトスケルトン作成)

Nest CLIを使い、プロジェクトスケルトンを作成します。

$ nest new uploadfile-viruscheck-example

準備2(ライブラリインストール)

利用するライブラリ(nestjs-clamscan)をnpm install します。
利用するライブラリ(multer)をnpm install します。

$ cd fileupload-api
$ npm i nestjs-clamscan --save
$ npm install multer --save 

準備3(clamAVサーバー起動)

NestJSはVSCode上でnpmスクリプトからビルド&実行します。clamAVに関しては、Dockerで予め起動させておきます。

$ docker run --name clamav -d -p 3310:3310 quay.io/ukhomeofficedigital/clamav:latest
$ docker ps
CONTAINER ID        IMAGE                                       COMMAND                  CREATED             STATUS              PORTS                    NAMES
abb51b742e80        quay.io/ukhomeofficedigital/clamav:latest   "/docker-entrypoint.…"   44 seconds ago      Up 36 seconds       0.0.0.0:3310->3310/tcp   clamav

停止は docker stop clamav
2回目以降の起動は docker start clamav

実装1コントローラー作成

クライアント側から、「multipart/form-data」で送られてきたデータを受け取り、
レスポンスは「HTTP 202 Accepted 」でレスポンスを返します。内部では、ウイルススキャンを実行しファイルを格納します。
NestJSでは一連の処理をデコレーターインタセプタで記述できるので、Express素で実装するよりコード量が減ります。

app.controller.ts
import {
  Controller,
  HttpCode,
  HttpStatus,
  Param,
  Post,
  UploadedFile,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import { AppService } from './app.service';

@Controller('api/v1')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post('files/:id')
  @UseInterceptors(
    FileInterceptor('file', {
      storage: memoryStorage(),
    }),
  )
  @HttpCode(HttpStatus.ACCEPTED)
  async uploadFile(
    @Param('id') id: string,
    @UploadedFile('file') file: Express.Multer.File,
  ): Promise<void> {
    await this.appService.scanFile(id, file);
  }
}

実装2ウイルススキャン実施部分実装

サービスクラスでウイルススキャンを行います。

今回はclamdjsをNestJSのServiceとして提供されている「Nestjs-clamscan」を利用します。
・npm
・git

app.service.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ClamScanService } from 'nestjs-clamscan';

@Injectable()
export class AppService {
  constructor(private readonly clamScanService: ClamScanService) {}

  async scanFile(id: string, file: Express.Multer.File): Promise<void> {
    const result = await this.clamScanService.scanBuffer(file.buffer);
    if (!result) {
      console.log('file is bad(infect)');
      console.log(`result=>${result}`);
      throw new HttpException('upload file is bad', HttpStatus.BAD_REQUEST);
    }
    console.log('file is good');
  }
}

動作確認(テスト)

動作確認に関しては、定番?のテスト用マルウェア(無害)を用いいます。
これが検知できればOKです。(実際のマルウェアではくれぐれも試さないでください)
今回は、ファイルとしてではなく、Bufferとしてオンコードしました。

app.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { ClamscanModule } from 'nestjs-clamscan';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  let appController: AppController;
  const testFileContents = {
    good: 'test-contents',
    bad: 'X5O!P%@AP[4PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*',
  };

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      imports: [
        ClamscanModule.forRoot({
          host: process.env.HOST || '127.0.0.1',
          port: Number(process.env.PORT) || 3310,
        }),
      ],
      controllers: [AppController],
      providers: [AppService],
    }).compile();

    appController = app.get<AppController>(AppController);
  });

  describe('uploadFile', () => {
    it('file is good should be output log "file is good"', async () => {
      const logSpy = jest.spyOn(console, 'log');
      await appController.uploadFile('f0001', createFile(testFileContents.good));
      expect(logSpy).toHaveBeenCalledWith('file is good');
      return;
    });

    it('file is bad should be output log "file is bad(infect)"', async () => {
      const logSpy = jest.spyOn(console, 'log');
      await appController.uploadFile('f0002', createFile(testFileContents.bad));
      expect(logSpy).toHaveBeenCalledWith('file is bad(infect)');
      return;
    });
  });

  const createFile = (contents: string): Express.Multer.File => {
    return {
      fieldname: 'file',
      originalname: 'file0001',
      encoding: 'UTF-8',
      mimetype: 'text/plain',
      size: Buffer.from(contents, 'utf-8').length,
      buffer: Buffer.from(contents, 'utf-8'),
      stream: undefined,
      destination: undefined,
      filename: undefined,
      path: undefined,
    };
  };
});

test/app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { HttpException, HttpStatus, INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  describe('POST /api/v1/files/:id', () => {
    it('good file is status 202', async () => {
      return request(app.getHttpServer())
        .post('/api/v1/files/001')
        .attach('file', Buffer.from('test', 'utf-8'), {
          filename: 'good.txt',
          contentType: 'text/plain',
        })
        .expect(202);
    });
    it('bad file is status 400', async () => {
      return request(app.getHttpServer())
        .post('/api/v1/files/002')
        .attach(
          'file',
          Buffer.from(
            'X5O!P%@AP[4PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*',
            'utf-8',
          ),
          {
            filename: 'bad.txt',
            contentType: 'text/plain',
          },
        )
        .expect(400, HttpException.createBody('', 'upload file is bad', HttpStatus.BAD_REQUEST));
    });
  });
});

その他

ファイルアップロードのバックエンドAPIの基本的な部分を作成してみました。
後は、さらに応用で作りこみですね!

要件によっては、ウイルススキャン後にオブジェクトストレージへ格納したり、
データしてインポートしたり等になると思います。
データとしてインポートする際には、スキーマバリデーションなども必要かもしれません。
ファイルメタデータも管理しないと、いつだれが送ったファイルかわかりませんしね。

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

M1 MacでNode.js(arm64)をNVMを使ってインストールする

最新のNVMとNode.jsをインストールすればいけると思ってたら罠でした。
多くの方は迷わずインストールできると思いますが、arm64版以外のNode.jsをイン

TL;DL

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

これをbashなりzshなりで実行するだけです。現在バイナリが用意されていないので、ソースコードをダウンロードした後にビルドが始まります。
ビルド完了後、node -p process.archと入力しarm64と表示されれば成功です。x64と表示された方はもう少しお付き合いください。

環境

$ uname -a
Darwin Mac-mini.local 20.2.0 Darwin Kernel Version 20.2.0: Wed Dec  2 20:40:21 PST 2020; root:xnu-7195.60.75~1/RELEASE_ARM64_T8101 arm64

$ sw_vers
ProductName:    macOS
ProductVersion: 11.1
BuildVersion:   20C69

$ nvm --version                                                             
0.37.2

arm64に対応しているNode.jsのバージョンは15以上です。

node -p process.archarm64以外が表示される

Pythonのアーキテクチャがx86_64の場合にNode.jsのビルドに失敗します。arm64版のHomebrewからPythonを再インストールし終えたらNode.js v15の再インストールを行ってください。

結果

$node -p process.arch
arm64

参考

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

【解決!】Vue-Cliをインストール...できない!

状況

・Vue-Cliを使った本格的なアプリ開発をするためにVue-Cliをインストールしようとした
・過去にもVue-Cliをインストールして使った経験あり!
・コマンドプロンプトを開いて「いざ!やるぞ!」と思っていた矢先、早速出鼻をくじかれた...という話です。

起こったこと①

・コマンドプロンプトで下のようにVue-Cliをインストール

npm -g install vue-cli

・以下エラーが発生

npm WARN deprecated vue-cli@2.9.6: This package has been deprecated in favour of @vue/cli
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen)
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm ERR! code EEXIST
npm ERR! path C:\Program Files (x86)\Nodist\bin\node_modules\vue-cli\bin\vue
npm ERR! dest C:\Program Files (x86)\Nodist\bin\*vue.cmd*
npm ERR! EEXIST: file already exists, cmd shim 'C:\Program Files (x86)\Nodist\bin\node_modules\vue-cli\bin\vue' ->'C:\Program Files (x86)\Nodist\bin\vue.cmd'
npm ERR! File exists: C:\Program Files (x86)\Nodist\bin\vue.cmd
npm ERR! Remove the existing file and try again, or run npm
npm ERR! with --force to overwrite files recklessly.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\Owner\AppData\Roaming\npm-cache\_logs\2020-12-24T15_55_11_169Z-debug.log

対策①

ポイントは5行目~11行目。
要約すると
「既にインストールしているデータがある時のエラーだよ!」(5行目)
「具体的にはProgram Files (x86)\Nodist\bin\vue.cmdのことだよ!」(8~9行目)
「このfileを消してもう一回やってみて!」(10行目)
とある。
・冒頭書いた通り、一度Vue-Cliはインストールしたことあるので、その時インストールしたファイルたちが残っていた。
 どうもそのファイルが今回のインストールを邪魔している、らしい。
・なので、一旦Program Files (x86)\Nodist\binにあるvue.cmdを消して再度npm -g install vue-cliを実行

起こったこと②

・開発へ進める!と思ったら、またしても以下エラー発生

npm WARN deprecated vue-cli@2.9.6: This package has been deprecated in favour of @vue/cli
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated coffee-script@1.12.7: CoffeeScript on NPM has moved to "coffeescript" (no hyphen)
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm ERR! code EEXIST
npm ERR! path C:\Program Files (x86)\Nodist\bin\node_modules\vue-cli\bin\vue
npm ERR! dest C:\Program Files (x86)\Nodist\bin\vue
npm ERR! EEXIST: file already exists, cmd shim 'C:\Program Files (x86)\Nodist\bin\node_modules\vue-cli\bin\vue' -> 'C:\Program Files (x86)\Nodist\bin\*vue*'
npm ERR! File exists: C:\Program Files (x86)\Nodist\bin\vue
npm ERR! Remove the existing file and try again, or run npm
npm ERR! with --force to overwrite files recklessly.

・さっきと同じようなエラー。よくよく見ると...

npm ERR! EEXIST: file already exists, cmd shim 'C:\Program Files (x86)\Nodist\bin\node_modules\vue-cli\bin\vue' -> 'C:\Program Files (x86)\Nodist\bin\vue'
npm ERR! File exists: C:\Program Files (x86)\Nodist\bin\vue

・消すべきファイルの名前が変わっている。

対策②

・なるほど。過去インストールしたいくつかのファイルが邪魔して今回インストールできないわけだ、と悟り/同じく今回もvueファイルを消す。
・結局、「消す⇒npm -g install vue-cli実行⇒エラー(○○を消してね!)⇒消す⇒...」を10回ほど繰り返した
 ○○に指定されて消したファイルは以下の通り
vue-cli.png
・こんなに何回もエラーを頻発した理由は、これらのファイルたちはnpm -g install vue-cliを実行するとまた最新verが生まれてくるということ。
で、その生まれたファイルたちがエラーの原因となってしまうこともあった。
・結論、上図のファイルたちを一気に消してnpm -g install vue-cliで正常にインストール完了!

エラーの原因

・今回のエラーの原因はどうも先ほどお見せしたファイルたちのインストールした日時らしい。
・元々インストールしたファイルたちは2020/12/20にインストールしたモノだったので、これがある限りエラーを発生しまくった。
・そこで上記のようにファイルたちを一気に消してnpm -g install vue-cliを実行し最新版のファイルが作成され、うまくいった!という流れ。

最後に

大したことではないですが、ググって同じようなエラーコード出ている方々はいらっしゃいましたが、ほとんどの方はMacユーザーの方でした。
そういった方の対策案を参考にした自分はこのエラーも出していました。

スクリーンショット 2020-12-26 205610.png

ここで初めてWindowユーザーである自分は"sudo"や"rm"などのLinuxが使えない、ということを学びました。
参考までに。

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