20200319のNode.jsに関する記事は8件です。

[node.js] npm ls 時の deduped とは

node.js の npm で ls を実行すると deduped が表示される

npm ls 時に表示される deduped とは、de-duplicated つまり duplicationreduce したという意味です。

日本語で言うと「同じ npm パッケージ及びバージョンのパッケージがインストール済みなので削除された依存パッケージである」という意味です。

npm dedupe コマンドを実行した際に削除された依存パッケージに表示されます。

/app # npm ls minimist
mastogetter@0.0.1 /app
+-- ava@3.5.0
| `-- update-notifier@4.1.0
|   `-- latest-version@5.1.0
|     `-- package-json@6.5.0
|       `-- registry-auth-token@4.1.1
|         `-- rc@1.2.8
|           `-- minimist@1.2.5  deduped ← これ
`-- eslint@6.8.0
  +-- file-entry-cache@5.0.1
  | `-- flat-cache@2.0.1
  |   `-- write@1.0.3
  |     `-- mkdirp@0.5.3
  |       `-- minimist@1.2.5  deduped ← これ
  `-- mkdirp@0.5.3
    `-- minimist@1.2.5 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Node.js] npm ls 時の deduped とは

node.js の npm で ls を実行すると deduped が表示される

deduped とは

/app # npm ls minimist
mastogetter@0.0.1 /app
+-- ava@3.5.0
| `-- update-notifier@4.1.0
|   `-- latest-version@5.1.0
|     `-- package-json@6.5.0
|       `-- registry-auth-token@4.1.1
|         `-- rc@1.2.8
|           `-- minimist@1.2.5  deduped ← これ
`-- eslint@6.8.0
  +-- file-entry-cache@5.0.1
  | `-- flat-cache@2.0.1
  |   `-- write@1.0.3
  |     `-- mkdirp@0.5.3
  |       `-- minimist@1.2.5  deduped ← これ
  `-- mkdirp@0.5.3
    `-- minimist@1.2.5 

npm ls コマンド実行時に表示される deduped とは、de-duplicated つまり duplicationreduce したという意味です。npm dedupe コマンドを実行した際に削除された依存パッケージが npm ls 時に deduped と表示されます。

日本語で言うと「同じ npm パッケージ及びバージョンのパッケージがインストール済みなので削除された依存パッケージである」という意味です。

参考文献

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

Node.jsからAutoML Vision の鼓膜画像分類モデルを使ってみる

概要

普段は耳鼻科の開業医をしています。

前回の記事はこちら
GCP Cloud AutoML Vision を使った鼓膜画像分類

今回は作成したAutoML Visionの鼓膜画像分類モデルをNode.jsから使ってみました。

作成

1.プロジェクトの作成

こちらを参考にしました
Cloud AutoML: Node.js Client

index.js
require('dotenv').config();//.envを読み込む

const automl = require('@google-cloud/automl');
const fs = require('fs');

// Create client for prediction service.
const client = new automl.PredictionServiceClient();

/**
 * TODO(developer): Uncomment the following line before running the sample.
 */

const projectId = `The GCLOUD_PROJECT string, e.g. "my-gcloud-project"`;
const computeRegion = `region-name, e.g. "us-central1"`;
const modelId = `id of the model, e.g. “ICN723541179344731436”`;
const filePath = `local text file path of content to be classified, e.g. "./resources/flower.png"`;
const scoreThreshold = `value between 0.0 and 1.0, e.g. "0.5"`;

// Get the full path of the model.
const modelFullId = client.modelPath(projectId, computeRegion, modelId);

// Read the file content for prediction.
const content = fs.readFileSync(filePath, 'base64');

const params = {};

if (scoreThreshold) {
  params.score_threshold = scoreThreshold;
}

// Set the payload by giving the content and type of the file.
const payload = {};
payload.image = {imageBytes: content};

async function test(){
  // params is additional domain-specific parameters.
  // currently there is no additional parameters supported.
  const [response] = await client.predict({
    name: modelFullId,
    payload: payload,
    params: params,
  });
  console.log(`Prediction results:`);
  response.payload.forEach(result => {
    console.log(`Predicted class name: ${result.displayName}`);
    console.log(`Predicted class score: ${result.classification.score}`);
  });
}

test();

2.AutoML用認証キーの作成

コンソール画面左上のナビゲーションメニューから『APIとサービス』➡『認証情報』
image.png

『認証情報を作成』➡『サービスアカウント』
image.png

サービスアカウント名を適当に決めて『作成』を選択
image.png

ロールは『AutoML 予測者』を選択し『続行』
image.png

『+キーを作成』を選択
image.png

『JSON』➡『作成』を選択
image.png
JSONファイルがダウンロードされます
image.png

3.AutoML用認証キーのつなぎ込み

ダウンロードされたJSONファイルをindex.jsと同じフォルダーに入れる
image.png

.envを作成

GOOGLE_APPLICATION_CREDENTIALS=./ここにダウンロードされたJSONファイル名を記入

必要なパッケージをインストール

$ npm install @google-cloud/automl 
$ npm install dotenv 

4.認証情報を設定

index.jsを以下のように書き換える

index.js
const projectId = "自分のプロジェクト名";
const computeRegion = "us-central1";
const modelId = "ICNで始まる番号";
const filePath = "テストしたいローカル画像のフルパス"
const scoreThreshold = "0.5";

modelIdはこちらの赤丸の部分
image.png

今回テストしたローカル画像はこちら
急性中耳炎の画像です

WIN_20190529_08_40_52_Pro.jpg

フルパスは以下のように区切らないとうまく動きませんでした(windows10)。

const filePath = "C:\\Users\\***\\data\\test\\aom\\WIN_20190529_08_40_52_Pro.jpg";

5.デプロイ
今回デプロイし終わるまで20~30分ほどかかりました。
image.png

ちなみにデプロイしたままにすると1日3000円ほど課金されますので。使わないときはデプロイを解除しましょう。
モデルのデプロイ解除の方法はこちら

テスト

index.jsを実行します。
Prediction class name:aom(急性中耳炎)と正しく判定されています。
Prediction class score(信頼度 0.0〜1.0の値が入ります)は1なのでかなり自信があるようです。

image.png

考察

Node.jsからAutoML Vision の鼓膜画像分類モデルを使うことができました。
次はLINE Botに組み込んでみたいと思います。

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

nodeサーバーで express session と passport.js を用いた認証処理

express session と passport.js を用いた認証処理

SPA アプリケーションのサーバーサイドでexpressフレームワークで作成したサーバーの認証処理について、
苦労した点が多かったのでまとめます。仕様は以下の通りです。

  • フォーム認証
  • passport.js を使用する
  • セッション管理は MemoryStore

1.認証処理の実装

認証処理を行うためのクラス Authenticetor.js を作成します。
memorystore モジュールを用いている理由としては、
express session で MemoryStore への保存する場合、 メモリリークを起こす可能性があるためは推奨されていません。
そのため、memorystore モジュールを用いています。
本来は redisDB などを用いた管理にするほうが懸命だと思います。

import passport from 'passport';
import LocalStrategy from 'passport-local';
import session from 'express-session';
import uuid from 'uuid';
const MemoryStore = require('memorystore')(session);
// ユーザー取得クラス
import User from './User';

/**
 * 認証処理を行うためのクラス
 */
export default class Authenticator {

/**
   * 初期処理
   * @param {Object} app
   */
  static initialize(app) {
    this._createSession(app);
    app.use(passport.initialize());
    app.use(passport.session());
    this.serialize();
    this.deserialize();
  }

  /**
   * セッション作成
   * @param {Object} app
   */
  static _createSession(app) {
    app.use(
      session({
        cookie: { maxAge: 6000000},
        store: new MemoryStore({
          checkPeriod: 6000000
        }),
        genid: () => {
          return uuid();
        },
        secret: 'しーくれっとこーど',
        rolling: true,
        resave: false,
        saveUninitialized: true
      })
    );
  }

  /**
   * セッション格納処理
   */
  static setStrage() {
    passport.use(
      new LocalStrategy(
        { usernameField: 'id' },
        async (id, password, done) => {
          try {
            // ユーザー情報取得
            const user = User.get(id);
            if (user && user.id === id && user.pass === pass) {
              // ユーザー情報を返却
              return done(null, user);
            }
            done(null, false);
          } catch (error) {
            done(null, false);
          }
        }
      )
    );
  }

  /**
   * serialize処理
   */
  static serialize() {
    passport.serializeUser((user, done) => {
      done(null, user);
    });
  }

  /**
   * deserialize処理
   */
  static deserialize() {
    passport.deserializeUser(async (req, id, done) => {
      done(null, {id});
    });
  }
}

2.認証APIにリクエストした際の認証処理

ログインするためのAPIの処理を記述します。

const express = require('express');
const router = express.Router();
const passport = require('passport');

router.post('/', passport.authenticate('local'), async (req, res) => {
  try {
    const userData = {
        id: req.id,
        hoge: 'fuga'
    }
    // 何かクライアント側に送りつけたい場合
    req.session.user = userData;
    res.send(req.user);
  } catch (error) {
    res.sendStatus(500);
  }
});

3.app.jsの設定

上記1.で作成したAuthenticatorクラスをapp.jsから呼び出します。
そして、認証のリクエスト以外すべてのリクエストに対して認証チェックを掛け、
上記2.で作成したエンドポイントに認証のルーティングをします。

import Authenticator from './modules/Authenticator';
:
:
const app = express();
Authenticator.initialize(app);
Authenticator.setStrage();
:
// 認証ルート以外のすべてのルートをチェック
app.use(/^(?!singin)/, function(req, res, next) {
  if (!req.isAuthenticated()) {
    res.sendStatus(401);
    return;
  } 
  next();
});
app.use('/singin', singin);
:

まとめ

実装中はめっちゃ苦労しましたが、まとめてみると短いコードでセッション認証をかけることができていると思います。
JWTトークンを用いた場合も似たような形で実装できました。認証もうこわくない!

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

Node.jsをバックグラウンドで起動させたい

コード

nohup node proxy.js &

止めたい時

プロセスを調べる

(例)

lsof -i :80
・
・
・
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node    10566 root   18u  IPv6  95714      0t0  TCP *:http (LISTEN)

プロセスを指定し、止める

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

Node.js: 文字列をUint8Arrayに変換する方法

Node.jsで文字列をUnit8Arrayに変換する方法です。

const str = 'Hello こんにちは'
const encoder = new TextEncoder()
const encoded = encoder.encode(str)
console.log(encoded)

実行結果:

Uint8Array(21) [
   72, 101, 108, 108, 111,  32,
  227, 129, 147, 227, 130, 147,
  227, 129, 171, 227, 129, 161,
  227, 129, 175
]

簡単な説明

  • TextEncoderencodeメソッドに文字列を渡すと、Unit8Arrayが返ってくる。
  • UTF-8のみサポートしている。
  • TextEncoderのインターフェイスはWHATWG Encoding Standard TextEncoder APIに準拠している。

公式ドキュメント

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

Node.jsのworker_threadsに使えるバイナリフォーマットを考えた件

本記事はNode.jsのworker_threadsに何を渡すべきかの続きです。
よろしければそちらも是非お読みになってください。

1. 前回のquickおさらい

  • 大量のメモリを使う処理を並列で動かしたい。並列であることと省メモリであることが条件。
  • Node.jsには、メモリを共有できるマルチスレッドの仕組みとしてworker_threadsが備わっている。
  • ただしメモリ共有にはSharedArrayBufferを使う必要がある。
  • SharedArrayBufferはバイナリ配列なので、JSON-likeな複雑な構造は直接は表現できない。
  • JSON.stringify等のシリアライズ結果をBufferに載せることはできるがデシリアライズしないと使えないので省メモリにならない。
  • デシリアライズせずにJSON-likeな構造を扱えるシリアライザーが欲しい!作ろう!

そして作ったものをにroJson(read only JSON)という仮称を付けました。
現時点(2020/3/19)のソースはGistにあります。

2. 早速ベンチマーク取ってみる

最初に言っておきますが、Buffer全体をデシリアライズしないで必要時に必要なところだけ読む仕組みである関係上、何度も何度も同じところを読むような処理だと、部分的なデシリアライズを何度もしているのと同じなので不利です。
結局は得手不得手があるのでケースバイケースで使い分けましょうって話なんですが、今回は自作シリアライザに有利な処理ロジックとしました。

条件です。

  • ObjectのArrayに対して検索する、をマルチスレッドで2回実行する
  • 配列のサイズは50万
  • Objectにはいくつかキーがあるが、必ず"name"というkeyがあって、Array内で一意である
  • 検索したいものとして上記50万Objectのうち100個の"name"を対象としたリストsearchTargetを用意する
  • 検索処理ではArray.filter(a => searchTarget.includes(a.name))として、50万レコードに対して"name"が「検索したいものリスト」に合致するものがあるものを選び、結果として返却する
  • worker_threadsに渡すデータはあらかじめシリアライズしておく。検索の度にはシリアライズ処理を走らせない

検索2回だとマルチスレッドの効果があんまり出ませんが、手持ちのCPUが2コア/4スレッドゆえ・・・。

3. ベンチマーク結果

時間の単位はmsでconsole.time()を使用。
メモリの単位はMBでprocess.memoryUsage()からrssの値を使用。

シリアライザ シリアライズ時間(a) 検索2回の平均処理時間(b) トータル処理時間(c) 使用メモリ(最大時)
(参考:シングルスレッド)生Object/Array - 3,022 7,394 229
生Object/Array - 13,957 20,704 597
JSON 1,857 4,800 7,449 1,026
messagePack(msgpack-lite) 4,392 10,883 16,084 997
roJson 3,620 6,376 10,653 407

どうですか?シングルスレッドが最強という、なんとも微妙な結果になりました。。。

処理時間

シングルスレッドが1位ですが、
理論的にはシングルスレッドは検索回数分だけ線形に処理時間が伸びるのに対して、マルチスレッド勢はCPUコア数分までは時間がほぼ同じに収まるはずなので、シングルスレッドとroJsonは3スレッドでほぼ同等、4スレッド以上なら逆転できることになり、まあ、、、まあまあですかね・・・。

マルチスレッド勢で最強はやっぱりJSONですね。JSON憎し。2並列でもシングルスレッドとほぼ同等です。
messagePackが意外と遅かったですね。roJsonに対しても遅いのはどうしてなんでしょう。後ほど考察します。

マルチスレッドの生Objectは顕著に遅かったです。
生ObjectをWorkerのworkerDataに渡すと、structured clone algorithmという仕組みを通して子スレッド側にデータがコピーされるのですが、これはJSONなどよりもかなり効率が悪いようです。1
マルチスレッドの他のシリアライザの場合は(a)+(b)=(c)がだいたい成り立つんですが、生Objectの場合はこれが成り立っていません。これはstructured clone algorithmが親スレッド側で結構な時間、しかも(まだ親スレッド内なので)直列で処理されているからだと思われます。

メモリ使用量

これは狙い通りですね。
シングルスレッドが1位なのは当たり前なので置いておくとして、2位はroJsonになりました。もちろん並列度を上げてもほとんどメモリ使用量は増えません。逆にJSONやmessagePackは並列度を上げるのに従ってメモリ使用量も増えていきます。

ベンチマーク結果に対する総括

使いどころが難しい感もあるんですが、うまく使えば処理時間とメモリ使用量の削減になることが分かりました。

4. roJson作成の勘所

ポイントは、処理時間とメモリ使用量のバランスを取ることでした。
理想のイメージは両方が90点。
どちらかを95点にするためにどちらかが50点になってしまうのだったらやめましょう、ということです。2

というところを念頭に置きつつ、JSONでちょっといまいちだなと思うところを補完しつつやってみた、そのポイントを挙げていきます。

Objectのkey,valueあるいは配列要素へのアクセス時間を定数にする

時間効率+、メモリ効率-
これは前回も書きましたね。先頭から順繰りに読んでいってる暇はないのです。戦う相手は生Object/Arrayなのです。メモリ効率がやや犠牲になりますがこれは譲れないラインです。
逆に一番めんどくさいところでもあります。

keyの検索時間を定数化するのはおなじみのハッシュマップですね。roJsonではこのハッシュマップもシリアライズされたバイナリの中に埋め込んでます。
また、keyに対応するvalueがバイナリの何バイト目から配置されているのかを持たせることでvalueへのアクセス時間も定数化しています。

配列要素へのアクセスもほぼ同じです。
配列要素「へのアドレス」を持ったテーブルを用意することで、アドレスは固定バイト数(現状4byte)なので、オフセットアドレス+要素番号×4の場所にアドレスが入ってる、と即わかるようになってます。

Number型は適切な数値型でバイナリ化

時間効率-、メモリ効率+
これはmessagePackからアイデアを拝借しています。
Number型は本来double型なので64bit=8byteで、型識別子を付けると9byteにもなってしまうのですが、JSONに頻出するのって0とか1とかだったりしませんか?1+1byteで表現できるところを常に9byteではあまりにもったいないです。
ということで、数値の範囲を判定するという余分な処理を加わることになりますが、数値はそもそもシリアライズ対象の値の他にも、ハッシュマップのサイズだったり配列のサイズだったり文字列長だったりと結構使うところが多いので、入れたほうが良かろうと判断して取り入れました。
なんと0~15の4bitで収まる範囲は、型識別子と合わせても1byteで済むようにしています。3

疎配列に対応

時間効率+、メモリ効率+、対JSON+
疎配列っていうのは、値が定義されていない要素のあるArrayのことです。

const a = new Array(100000);
a[5000] = undefined;
console.log(a)
// (100000) [empty × 5000, undefined, empty × 94999]

undefinedですらないことに注意。本当に何もないんです。4
が、これをJSON.stringifyに渡すとご丁寧にnullで埋めてくれるんですよね・・・。
同じことをすると時間もメモリも無駄なので定義されている要素番号だけ保持するようにしました。
Objectのkey/valueとほぼ同じように扱えるので、ロジックはほぼ共通になってます。

文字列はUTF-16

時間効率+、メモリ効率-?
JavaScriptの内部文字コードはUTF-16です。常に1文字に対して2byte(サロゲートペア除く)が必要になります。
大抵のデータでString型の箇所(Objectのkeyも含む)は、半角英数なことが多いと思いますので、UTF-8に比べて倍のメモリを食うことになります。
ただ、UTF-16をUTF-8に変換する処理はそれなりに重い処理になると想定されるのでやめました。これは後述するmessagePackとの比較でも考察しています。
それに全角文字が多用されてるデータかもしれませんしね。その場合はUTF-16の方が有利です。

対JSONで他にもやりたいことがあります

下記は未実装ですが入れたいなと思ってるものです。

(1) 循環参照

JSONでは無理ですね。JSONで頑張って表現する方法を考えてる方もいますが、JSON仕様にない以上は実装依存にならざるを得ないので、一般的にはならないでしょう。roJsonは値の場所をアドレスで示す仕組みがあるので後は「Object/Arrayが同じもの」ってわかりさえすれば同じアドレスを指すだけなので簡単です。バイナリフォーマット仕様的には既に対応済みと言っても良いでしょう。

(2) undefined

JSON.stringifyに渡すとvalueがundefinedだと対応するkeyを消してくれたりだの、そうでなくてもnullにしてくれたりだのおせっかいが過ぎます(私見です5)。JSON縛りがないのなら、undefinedはundefinedのままにしといた方が幸せではないかと思うのです。
これはいつでもできますね。やってないだけです。

(3) 同一の文字列値は共通する一か所に格納する

今回扱ってるような巨大なJSONだったら、まず間違いなく繰り返し構造が存在しているはずです。リストにしろツリーにしろ。
ということは同じkeyが何度も出てくるはずなので、その度に何バイトかの領域を使うのはもったいないです。同じ場所を参照させてもいいでしょう。
これはメモリに効くことは自明(かつJSONには絶対真似できない)なんですが、将来、仮に読み出し専用じゃなくて更新も可能にしたいなあ、、、なんて色気を出し始める可能性を考えて二の足を踏んでるのが現状です。その文字列がまだ使われてるのかいないのか参照数を意識しないと捨てることができなくなっちゃいますからね。

msgpack-liteより速かった理由を考える

ベンチマークのコードが誤ってる可能性もありますが・・・。
特に(a)のシリアライズ処理のところですね。(b)はworkerData全体をデシリアライズしてから検索するというroJsonに対する不利な点があるのでまあそういうこともあるかな、という感じです。

roJsonはmessagePackに大いに影響を受けているのでシリアライズ方法は結構似通っているんです。加えて、ハッシュマップなどを余分に作ってる分遅くなると思ったんですよ・・・なんか大事な処理が漏れてるんだろうかと心配になります・・・。
それ以外に思いつくものを挙げてみました。

(1) StringをUTF-8にエンコードしている

一番に思いつく原因。
先にも書きましたがroJsonはJSの内部文字コード(charCodeAt()で取れる値)であるUTF-16の値をそのままUINT16型の値としてバイナリに書き込んでいます。一方、messagePackはメモリ効率を考慮してUTF-8に変換しています。これが遅い可能性。これであればmessagePackのバイナリフォーマットに起因する問題なので、msgpack-lite以外のライブラリであっても同じ理由で遅くなっている可能性があります。

(2) Bufferに書き込む値をbyte単位に分割して入れている

例えばUINT16な整数は2byteになるわけですが、上位byteと下位byteに分割して書き込む、というのをmsgpack-liteは自前でやってるようです(確信なし)。
それに対してArrayBufferをDataViewを通して使うとbyte単位に分割する処理は不要で済みます。DataViewの効率はかなり良いのだと聞きます。であればmsgpack-liteもDataViewを使うようにすれば速くなる可能性があります。

なお、messagePackはRPC等のデータ交換用途も考慮しているのでエンディアンを意識する必要があり、それがためにbyte単位の処理をしている可能性もあります。が、DataViewもsetXXX/getXXXはデフォルトでビッグエンディアン固定、引数指定によりリトルエンディアン固定にもできるのでDataViewで済ますことはできそうです。

(3) 多環境/異常系を考慮した堅牢な作りになっている

はいごめんなさい。その辺roJsonは結構雑です。
堅牢な作りの分遅くなっている、という可能性はありえそうです。

5. 以上

作ってみてそれなりの成果は得られたものの、正直微妙・・・。
もうちょっと処理自体を見直すか、もっと有用なユースケースを見つけるかして、いずれちゃんとnpmとして公開できたらな、と思ってます。


  1. 効率と引き換えにかなり広い範囲の組み込みクラスをカバーしているという汎用性もありますので「だからこいつはダメなんだ」などというつもりはないです。 

  2. もちろん点数は感覚的なものなので絶対的な指標があるわけではありません。 

  3. 実は参考にしているmessagePackはもっとラディカルで、7bitまでの正の整数を識別子と合わせて1byteで表現しています。つまり識別子は1bitなわけです。恐ろしい・・・。 

  4. ただし、empty要素に対して例えばa[0]===undefinedとかやったりするとtrueだったりします。やっかいです。 

  5. もちろんJSONである以上はemptyもundefinedもないのでnullにするしかないという事情は理解してます。 

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

node.js @line/bot-sdk を使ってAWS lambdaからLINE Messaging APIのpushMessageを使用する時に1時間位右往左往した時の話

https://developers.line.biz/en/reference/messaging-api/#send-push-message
にて

const line = require('@line/bot-sdk');

const client = new line.Client({
  channelAccessToken: '<channel access token>'
});

const message = {
  type: 'text',
  text: 'Hello World!'
};

client.pushMessage('<to>', message)
  .then(() => {
    ...
  })
  .catch((err) => {
    // error handling
  });

でいけよ、
みたいに書かれていたので、
自分のパソコンからこれを適宜当てはめたら、
普通にpush出来たので、意気揚々とAWS lambdaで使用したら、
なんかエラー出ていた。

connectionが確立されてないよ、
とか言われたけど、なぜそんなエラーがlambdaの時だけ返ってくるのか、
意味が分かってないので、どうしたら良いのか悩んでいたのですが、
試しに、async/await形式にしたら、うまく行きました。という小話です。

結局意味は分かってないのですが、
多分、他の関数で非同期的な処理行うために、関数の頭にasyncを記述しているのに、
client.pushMessageをawaitしてなかったからだと思う。
とりあえず動いています。
以下、実際に動いているコード。

exports.handler = async function (event){
  const line = require("@line/bot-sdk");
  const config = {
    channelAccessToken: "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzzAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQ",
  }

  const client = new line.Client(config);

  await client.pushMessage("000011112222333344445555666677777",{type:'text',text:'hello!'});
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む