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

sass-loader11.xはwebpack4以下では動かなくなった

$
0
0

GitHubに置いてあるVueのアプリケーション。先日、いつものようにdependabotが送り付けてくるプルリクエストを雑にマージしていると、突然CodePipelineからビルド失敗の通知が飛んできました。

ログ曰く

> flowerstand-frontend@1.0.2 build:prod /codebuild/output/src984942440/src/frontend/app
> vue-cli-service build --mode production


-  Building for production...
Starting type checking service...
Using 1 worker with 2048MB memory limit
 ERROR  Failed to compile with 1 error5:29:44 PM

 error  in ./node_modules/vuetify/src/components/VAlert/VAlert.sass

TypeError: this.getOptions is not a function


 @ ./node_modules/vuetify/lib/components/VAlert/VAlert.js 5:0-52
 @ ./node_modules/vuetify/lib/components/VAlert/index.js
 @ ./src/App.vue
 @ ./src/main.ts
 @ multi ./src/main.ts

 ERROR  Build failed with errors.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! flowerstand-frontend@1.0.2 build:prod: `vue-cli-service build --mode production`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the flowerstand-frontend@1.0.2 build:prod script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-03-26T17_29_44_078Z-debug.log

このときにバージョンを上げたのはsass-loaderで、ログを見るにいかにもそれっぽい感じのこけ方をしています。メジャーバージョンが上がっていたのでリリースノートを確認すると

⚠ BREAKING CHANGES
minimum supported webpack version is 5

はい。

Vue2.xはまだwebpack4なので、sass-loaderのバージョンを落とします。

package.json
"sass-loader":"^10.1.1",

$ npm install

これで解決。

しかしsass周りってどうもトラブルが多い気がする・・・。


Expressで一体何が行われているのか?

$
0
0

expressを使い始めて一体バックエンドで行われているのか不思議に思ったので調べてみました。
そもそも、httpリクエストやhttpレスポンスが何なのかを理解しなくてもアプリを作成出来ればよいはずなんだけど、理解しないと複雑なルーティングやリクエストやレスポンスを引き継いだり受け渡したりしないと作り上げられないという現実にぶち当たる・・・ので避けては通れないし、避け続けているとスキルアップしない、モチベーションの低下、退場となりかねないので、素人なりに分かるところまでやってみます!

まずはexpressのデフォルトを立ち上げてapp.jsにあるこの部分。

app.js
varexpress=require('express');//2行目varapp=express();//10行目

ここでまず混乱したのはroutes/index.js:1:2にある

var express = require('express');
var router = express.Router();

との整合性。検索してサイトの例でよく見る

app.get("/", (req, res) => {
    res.status(200).send("OK");
});

のような記載。変数appにgetやpost, put, deleteを付けるんならワザワザrouter = express.Router();としなくてもイイんじゃね?と・・・もっと言うとappとrouterで何が違うの?ってなった。
すぐに、app.get()router.get()の簡略化してサンプルを作成していると理解出来ます。app.jsはアプリケーションを起動するためにあって個別の細々した機能はそれぞれ別ファイルに書くべき、そのためにexpressのメソッドとしてRouterが独立している。
では、ドキュメントでそのあたり確認してみましょう!
Express APIリファレンスのナビゲーションウィンドウのRouterの説明文に

You can think of it as a “mini-application,” capable only of performing middleware and routing functions.

関数であること、よりわかりやすく'ミニアプリケーション'と記載されてます。(記載はしませんがapp側にはtop-levelとなっています)
これで、変数appで利用されるroutingと変数routerのそれがほぼ同じ役割、使い分けするためだけに分けているということが分かります。
もっと言うと、変数appがアプリケーション本体、変数routerはミニアプリケーションという階層構造になっていることも分かります。
では、expressのソースコードでも確認しましよう!

//application.js
var Router = require('./router'); //17行目
var methods = require('methods'); //methodsってなんだ?
var middleware = require('./middleware/init');

router.jsをrequireしていますね。反対にrouter.jsには反対のrequireはないので階層構造になっています。

さて、もう少し突っ込んでみます。では、app.get()router.post()の第2引数のコールバック関数で何でreq, res, nextを指定してるの?っていう疑問に進みます。いや、そんなの分かるに決まってるやん!とか、そのまま型として覚えておけばイイんだよ、とか、言われそうですが、疑問に思ったもんは仕方ありません。

router.get('/', function (req, res) {  //app.getでも構わない
  res.send('hello world')
})

これですね。第1引数がエンドポイント、第2引数がコールバック(コールバックについてはまた詳しくやりたいですが、今のところは関数内の関数くらいに思っておいてください)で、何を指定出来るのか、まずはリファレンスを見てみます。

.middleware function.
.series of middleware functions (separated by commas).
.An array of middleware functions.
.combination of all of the above.

となってます。取り敢えず、ミドルウェア関数ならカンマで繋げればどんなけでもつなげることが出来ると分かります。でも知りたいのはfunction(req, res)の部分。
色々調べて結果、ここに行き着きました。

//lib/routers/layer.js
function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn);
  }

  debug('new %o', path)
  var opts = options || {};

  this.handle = fn;
  this.name = fn.name || '<anonymous>';
  this.params = undefined;
  this.path = undefined;
  this.regexp = pathRegexp(path, this.keys = [], opts);

  // set fast path flags
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

おじさん、このあたりが`function(req, res)を書いているように思うんだ・・・・

未完

[メモ] eslint-config-prettierのv8.0.0以降でextendsの設定の仕方が変わっていた

$
0
0

概要

タイトルの通り、eslint-config-prettierのv8.0.0以降でextendsの設定の仕方が変わっていた。

結果、 prettier/reactprettier/@typescript-eslint等のプラグインを指定しなくても全て prettierに含まれるようになったらしい。

ソース: Prettier > Installation

information_source Note: You might find guides on the Internet saying you should also extend stuff like "prettier/react". Since version 8.0.0 of eslint-config-prettier, all you need to extend is "prettier"! That includes all plugins.

現場からは以上です。

Node.jsからSlackへのレスポンス際に「あなただけに表示されています」と表示されるのを、どうにかしたい

$
0
0

はじめに

  • Slackのslash commandのリクエストに対して、普通にレスポンスを返すと「あなただけに表示されています」という状態で投稿されます
  • 他の人に投稿した内容が見えるようにする方法を簡単にまとめます。

「あなただけに表示されています」を解消する方法

  • レスポンス内でresponse_type: 'in_channel'を指定します。
  • ちなみに、Slackの公式サイトにも以下のように記載されています。

The response_type parameter in the JSON payload controls this visibility, by default it is set to ephemeral, but you can specify a value of in_channel to post the response into the channel, like this:
{
"response_type": "in_channel",
"text": "It's 80 degrees right now."
}
参考URL : https://api.slack.com/interactivity/slash-commands

デフォルトでは、"response_type": "ephemeral"が設定されていると書いてありますね。

サンプルコード

body内で、response_type: 'in_channel'を指定すればOKです。

Node.js
constrequestPromise=require('request-promise');constoptions={url:responseURL,// SlackへのレスポンスURLheaders:{'Content-type':'application/json'},body:{response_type:'in_channel',// ★ ここで in_channelを指定text:response// Slackへのレスポンス内容},json:true};res=requestPromise.post(options);

progate

$
0
0

雑感

ログイン、ログアウト機能だけでも裏側でやっていることは色々あるんですね。

Slackへ勤怠連絡を入力するAlexaスキルを作ってみた

$
0
0

概要

弊社は出勤や休憩などの勤怠連絡をSlackのチャンネルへのメッセージ送信で行っています
毎日何度も行うことなので手入力は結構面倒です
そこで、最近我が家にやってきたAmazon Echo Showに話しかけて勤怠連絡を行えるAlexaスキルを作りました!
image.png

ちなみに、Echo Showが無くてもスマホのAlexaアプリでAlexaスキルを実行することはできます
音声入力で色々なものを操作するのは魔法感があって楽しいのでAlexaスキル開発はおすすめです!

システム構成

勤怠入力スキル構成図 (2).jpg

手順

手順は大きく分けて以下の4つです

  1. Alexaスキルセットアップ
  2. Slack連携
  3. Lambda関数セットアップ
  4. 配布

1. Alexaスキルセットアップ

まずは、ユーザーからの入力を受ける部分を作成します

1.1 スキル作成

AlexaデベロッパーコンソールにアクセスしてAmazonアカウントでログインします

その後、スキルの作成ボタンを押下して以下の設定でスキルを作成します
image.png
バックエンドリソースは「ユーザー定義のプロビジョニング」にします
「Alexa-hosted」にするとわざわざLambdaで関数を作成する必要がなくて楽ですが、テスト機能が不十分なのとデプロイが遅いので今回は使用しません

今回は一から作成するのでテンプレートは使用せず、「スクラッチで作成」を選択します
image.png

1.2 スキルの呼び出し方法について

Alexaスキルの呼び出し方法は様々ありますが、今回は以下のような発話を使って勤怠連絡を入力しようと思います

アレクサ勤怠入力出勤して」

この発話を3つの部分に分けて解説します

  • アレクサ
    • ウェイクワード
    • Alexaデバイスがユーザーの発話を聞き始めるトリガーとなる文言です
    • Alexaデバイス毎に設定することができます。「アレクサ」がデフォルトですが、その他にも「Amazon」、「Computer」などがあります
  • 勤怠入力
    • 呼び出し名
    • この文言でAlexaはスキルを判別します
  • 出勤
    • スロット
    • 同じ属性の文言を列挙型の様に定義できます
    • 定義したスロットの文言が発話に含まれていると、それがスキルリクエストに含まれてLambdaに渡されます

1.3 呼び出し名設定

スキルのセットアップ画面に入り、左のメニュー一覧から「呼び出し名」を開きます。
この画面で呼び出し名を設定することができます
image.png

呼び出し名は既存スキルと似たものにしてしまうと、うまく認識されません
また、2語以上を組み合わせた単語である必要があります(公式リファレンス)

今回は「勤怠連絡」を呼び出し名とします

1.4 スロット作成

「アセット」→「スロット」を開きます。
image.png

「+スロットタイプ」ボタンを押下してスロットを追加します
スロット名は「KintaiType」にします
image.png

必要の種類は「出勤」、「退勤」、「休憩開始」、「休憩終了」の4つです
image.png

1.5 インテント作成

「対話モデル」→「インテント」を開きます。
Alexaスキルにおけるインテントとは、ユーザーの発話によって引き起こされるアクションのことです。(公式リファレンス)
image.png

インテント名が「Amazon」から始まるものは標準ビルトインインテントです
「HelloWorldIntent」はサンプルなので削除します

それでは、「出勤して」などのコマンドを受け付けるためのインテントを作成するために「インテントを追加」ボタンを押下します
インテント名は「KintaiIntent」にしました
image.png

インテントには以下2種類のサンプル発話を設定しました
サンプル発話はなるべく多く登録した方が認識されやすいそうです
image.png
このとき、「{kintaiType}」と「して」の間に半角スペースが入っていないと以降のビルドでエラーになるので気を付けましょう

以上でインテントの設定は完了です

そうしたらページ上部の「モデルをビルド」ボタンを押します
これでこのスキルが設定した発話を認識できるようになります

1.6 発話テスト

ユーザーの発話がちゃんと認識されるかテストしてみましょう
テスト方法は以下の2種類があります

  1. Echo端末やスマホからAlexaに話しかける
  2. デベロッパーコンソールのテスト機能を使う

今回は2の方法でテストします。
PCのみで完結しますし、認識された発話から生成されたスキルリクエストも確認できるのでおすすめです

Alexa Developer Console上部の「テスト」タブを開きます
そして画面左上のテキストボックスへ発話を入力するか、マイクボタンを長押ししながらPCのマイクへ発話します
すると、それによって生成されたjson形式のスキルリクエストを確認することができます
image.png

以上でAlexaスキルセットアップは一旦完了です!

2. Slack連携

今回作成するアプリはSlackへのメッセージ送信を行うため、この作業が必要です

2.1 Slackアプリ作成

こちらのページの「Create New App」ボタンから新しいアプリを作成します
App Nameは「勤怠連絡」、
Development Slack Workspaceは勤怠連絡を送信したいワークスペースを設定します

作成ができたら編集ページに遷移します

2.2 権限設定

アプリからSlackを操作するには、使用するAPIに必要な権限を追加する必要があります。

今回使用するAPIはchat.postMessageです
今回はユーザーとしてこのAPIを使用するので、「ユーザースコープ」の「chat:write」権限が必要です

アプリ編集ページのOAuth & Permissionsタブを開き、Scopes → User Token Scopes欄から以下の様に追加すればOKです
image.png

2.3 ワークスペースへのインストール

アプリを使用するためにはワークスペースへのインストールが必要です
OAuth & Permissionsタブの上部の緑のボタンからインストールをします

2.4 Alexaスキルとのアカウントリンク設定

Alexaスキルを使用するユーザーが使用しているSlackアカウントとしてメッセージを送信するためには、Alexaスキルのアカウントリンク機能を使用します

まずは、Slackアプリの編集ページ → Basic Information → App Credentials欄から「Client ID」と「Client Secret」をコピーしておきます

次に、Alexa Developer Console → ツールタブ → アカウントリンクタブを開き、以下のように設定します
Web認証画面のURI: https://slack.com/oauth/authorize
アクセストークンのURI: https://slack.com/api/oauth.access
ユーザーのクライアントID: 先程コピーしたClient ID
クライアントのシークレット: 先程コピーしたClient Secret
スコープ: chat:write:user
image.png
Alexaのリダイレクト先のURL欄は次の手順で使用するので3つともコピーしておきます

先程コピーしたリダイレクト先のURLを以下の欄に全て登録します
Slackアプリの編集ページ → OAuth & Permissions → Redirect URLs
image.png

これで、Alexaアプリを使うためにはSlackアカウントとの連携が必要になりました

2.5 Slack連携テスト

実際にSlackとの連携ができるかテストしてみます

スマホのAlexaアプリ下部のその他タブ → スキル・ゲームページを開きます
そして、有効なスキルタブ → 開発タブ → 勤怠入力スキルへと進みます
勤怠入力スキルページが開けたら上部の「設定」ボタンを押下してアカウントのリンクを行ってみます
以下の様に表示されればOKです!
image.png

3. Lambda関数セットアップ

それではいよいよ、Alexaからのスキルリクエストを受け取って処理をするLambda関数を作ります
AWSアカウントは作成済みの前提です

3.1 関数作成

Serverless Application Repositoryのページを開き、
「利用可能なアプリケーション」→「alexa-skills-kit-nodejs-factskill」を選択します
image.png
これはアレクサスキル作成用のテンプレートのようなものです

アプリケーション名を「KintaiRenraku」とし、「デプロイ」ボタンを押します
image.png
デプロイには数十秒かかりました

デプロイが終了するとLambdaのアプリケーション一覧ページに遷移します
遷移しなければこちらから遷移できます
アプリケーション一覧には先程作成したアプリケーションが「serverlessrepo-KintaiRenraku」という名前で存在しているので名前をクリックします。

すると、このアプリケーション内に「alexaskillskitnodejsfactskill」というLabda関数が作成されていることが確認できます
image.png
関数名をクリックして編集画面に入りましょう

以上でLambda関数の作成ができました!

3.2 Lambda関数とAlexaスキルを接続

この関数がAlexaから呼ばれるためには以下の2つの作業が必要です

  • Labda関数のトリガーにAlexaスキルIDを設定
  • AlexaスキルのエンドポイントにLambda関数のARNを設定

まずは、「Labda関数のトリガーにAlexaスキルIDを設定」から行います
関数編集画面上部の「Alexa Skill Kit」トリガーをクリックします
image.png

すると、トリガー一覧画面に入ります
既に追加済みのAlexa Skill Kitトリガーの説明欄に書いてあるとおり、追加済みのこのトリガーは削除し下さい
そして、「トリガーを追加」ボタンから改めてAlexa Skill Kitを追加します
image.png

Alexa Skill Kitトリガーを追加しようとするとこの様な画面でスキルIDを求められます
image.png

スキルIDは、手順1.1で開いたAlexa Developer Consoleから確認できます
作成したAlexaスキルの編集画面 → エンドポイントタブを開くとスキルIDが書いてあるのでコピーして先程のトリガー追加画面に入力します
image.png

続いて、「AlexaスキルのエンドポイントにLambda関数のARNを設定」を行います
Lambda関数の編集画面上部のボタンからARNをコピーします
image.png

先程開いた、Alexaスキルの編集画面 → エンドポイントタブの「デフォルトの地域」欄にコピーしたARNを入力します
そしてページ上部の「エンドポイントを保存」ボタンを押下すればOKです

これでAlexaからLambda関数が呼び出されるようになりました!

試しにテストしてみましょう
「勤怠連絡を開いて」と話しかけると豆知識を披露してくれました
image.png
今回使用したテンプレートは豆知識を披露してくれるスキルなので、これでAlexaスキルとLambda関数の接続が確認できました

3.3 パッケージ追加

SlackAPIを叩くには色々な方法がありますが、今回は簡単にコードを書くために、@slack/web-apiパッケージを使用します

以下の手順はローカル環境にnpmがインストールされていることが前提です

npmパッケージをインストールするためにはLambda関数の実行環境を一旦、ローカルに落としてくる必要があります
作成したLambda関数の編集ページ上部の「アクション」プルダウン → 関数をエクスポート → デプロイパッケージをエクスポートをします
image.png

するとzipファイルがDLされるので、任意のフォルダに解凍します
次に、コマンドプロンプトを開いて解凍したフォルダに移動します
そしてnpm install @slack/web-apiコマンドを実行します

解凍したフォルダ → 「node_modules」フォルダ内に@slack\web-apiというディレクトリができていれば成功です

アップロードするために、解凍したフォルダ内にあるファイルを全て選択してzip圧縮します

Lambda関数の編集ページを開き、コードソース → 「アップロード元」プルダウン → .zipファイルを選択し、先程作成したzipファイルをアップロードします
image.png

アップロード後にコードソース欄の「Enviroment」タブがこの様になっていれば@slack/web-apiパッケージのインストールは完了です
image.png

3.4 コーディング

今回は以下の様なコードを作成しました。
channelId_Kintai変数には勤怠連絡先のチャンネルIDを指定して下さい
詳細説明は割愛します

index.js
'use strict';constAlexa=require('ask-sdk-core');const{WebClient}=require('@slack/web-api');// 喋る内容constHELP_MESSAGE="Slackの勤怠管理チャンネルで打刻します。";constHELP_REPROMPT='どうしますか?';constSTOP_MESSAGE="お疲れさまです";// 打刻用の情報constchannelId_Kintai="hoge";constkanjiConversion={"しゅっきん":"しゅっきん","出勤":"しゅっきん","きゅうけいかいし":"きゅうけいかいし","休憩開始":"きゅうけいかいし","きゅうけいしゅうりょう":"きゅうけいしゅうりょう","休憩終了":"きゅうけいしゅうりょう","たいきん":"たいきん","退勤":"たいきん",};constcommandInfos={"しゅっきん":{"command":"in","msg":"今日もいちにちがんばるぞい",},"きゅうけいかいし":{"command":"bi","msg":"ゆっくりやすんでくださいね",},"きゅうけいしゅうりょう":{"command":"bo","msg":"残りもがんばりましょう",},"たいきん":{"command":"out","msg":"今日も一日お疲れさまでした",},};constKintaiHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.request;returnrequest.type==='IntentRequest'&&request.intent.name==='KintaiIntent';},asynchandle(handlerInput){// 勤怠チャンネルに打刻メッセージを送るconstintent=handlerInput.requestEnvelope.request.intent;varcommandInfo=commandInfos[kanjiConversion[intent.slots.kintaiType.value]];if(!commandInfo){returnhandlerInput.responseBuilder.speak(intent.slots.kintaiType.value+"は存在しないコマンドです。使えるコマンドは「出勤」、「休憩開始」、「休憩終了」、「退勤」の4つです").getResponse();}constclient=newWebClient(handlerInput.requestEnvelope.context.System.user.accessToken)varparams={channel:channelId_Kintai,as_user:true,text:commandInfo["command"],};varmsg;try{console.log("りくえすと(chat.postMessage): "+JSON.stringify(params));constresponse=awaitclient.chat.postMessage(params);console.log("れすぽんす(chat.postMessage): "+JSON.stringify(response));msg=response.ok?"打刻しました。"+commandInfo["msg"]:"打刻に失敗しました";}catch(e){console.log("エラー: "+e);msg="打刻に失敗しました";}finally{returnhandlerInput.responseBuilder.speak(msg).getResponse();}},};constHelpHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.request;returnrequest.type==='LaunchRequest'||(request.type==='IntentRequest'&&request.intent.name==='AMAZON.HelpIntent');},handle(handlerInput){letmsg="Slackの勤怠管理チャンネルで打刻します。使えるコマンドは「出勤」、「休憩開始」、「休憩終了」、「退勤」の4つです。「Alexa、勤怠で出勤して」と言ってみて下さい。";returnhandlerInput.responseBuilder.speak(msg).reprompt("コマンドを発言して下さい").getResponse();},};constExitHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.request;returnrequest.type==='IntentRequest'&&(request.intent.name==='AMAZON.CancelIntent'||request.intent.name==='AMAZON.StopIntent');},handle(handlerInput){returnhandlerInput.responseBuilder.speak("お疲れさまです").getResponse();},};constSessionEndedRequestHandler={canHandle(handlerInput){constrequest=handlerInput.requestEnvelope.request;returnrequest.type==='SessionEndedRequest';},handle(handlerInput){console.log(`セッションが以下の理由で終了しました: ${handlerInput.requestEnvelope.request.reason}`);returnhandlerInput.responseBuilder.getResponse();},};constskillBuilder=Alexa.SkillBuilders.custom();exports.handler=skillBuilder.addRequestHandlers(KintaiHandler,HelpHandler,ExitHandler,SessionEndedRequestHandler,).withCustomUserAgent('sample/basic-fact/v2').lambda();

3.5 関数のテスト

リクエストが正しく処理されるかテストしてみましょう
Alexaに話しかけるか、Alexa Developer Consoleの「テスト」タブから発話をしてみます
無事、以下の様な返答が行われました
image.png
また、Slackへのメッセージ送信も行われています
image.png

以上でAlexaスキルの作成は完了です!

4. 配布

折角なので、作成したスキルを同僚の皆さんに使ってもらいましょう

Alexa Developer Consoleの「公開」タブ → 「公開範囲」タブを開きます
ベータテスト機能で配布するためには以下の作業を行う必要があるので全て済ませておきます
image.png

必要な作業を全て済ませてから改めて「公開範囲」タブの「ベータテスト」欄を開くと、テスターのEメールアドレスを追加できるようになっています
ここに、アプリを配布したい方のメアドを追加すればその方へメールが飛び、今回作成したスキルを配布することができます

所感

便利なスキルを作れて楽しかったです。自分で毎日使っています
作り方をまとめるのが意外に大変で驚きました。書き始め当初は、コードの説明も細かく書く予定でしたが後半は疲れたので止めました
Alexaスキルの作り方の記事は古いものが多く、現在の開発環境の参考になる記事が見つけにくかった為、この様な記事を書きました
この記事を読んでAlexaスキルを作られた方も作り方をまとめて見られては如何でしょうか?

参考にさせて頂いたページ

Firestore: Reference型をJSONにするときの挙動を変える

$
0
0

前置き

例えば以下のようなデータをFunctionsでFirestoreのドキュメントusers/12345に入れたとする.

{"display_name":String,..."userDetail":Reference}

Functionsでこのデータを取ってきてJSONでクライアントに返すAPIを作る(error()とかsuccess()は別で作ってあるって言う体で...).

app.get("/users/:id",async(req,res,next)=>{constusers=awaitdb.doc(`accounts/${req.params.id}`).get();if(!users.exists){error(res,404,"users_created","You've not created first user.");return;}success(res,users.data());return;});

するとこんな感じのデータが返ってくる

{"display_name":"Bony_Chops",...."userDetail":{"_firestore":{"_settings":{"projectId":"nicha-nnct","firebaseVersion":"9.5.0","libName":"gccl","libVersion":"4.9.4 fire/9.5.0","ssl":false,....}....}...}}

Reference型がめっちゃでかい上ヤバそうな値を返してくる.これはまずい.

失敗例

Reference型の正体であるfirebase.firestore.DocumentReferenceクラスのtoJSONprototypeで上書きします(何故か補完でfirebase.default.firestore.DocumentReferenceだったのでそちらで...).とりあえず,そのReferenceが指すpathを返却するようにしてみます.

constfirebase=require("firebase");firebase.default.firestore.DocumentReference.prototype.toJSON=function(){returnthis.path;}

でもさっきの結果は変わりませんでした.なんで???

結論

Firestoreへのアクセスをdb = admin.firestore();で行っていたのでadmin.firestore.DocumentReferenceを使うのが適切だったみたいです.

constadmin=require('firebase-admin');admin.firestore.DocumentReference.prototype.toJSON=function(){returnthis.path;}

結果

{"display_name":"Bony_Chops",..."userDetail":"users_detail/12345"}

これでいい感じ.

VSCodeでnodeメソッドの補完が効いてない問題 (デフォルトの設定のままでは補完候補が出ない)

$
0
0

解決方法

型情報も入れましょう
typesyncで自動的に解決!

環境

Windows10
VSCode v1.54.3
node v14.16.0
補完の候補はVSCodeの設定で大きく変わります。

確認用のファイルを作る

test.js

test.js
constmessage="hello!"message.consthttp=require('http')http.constexpress=require('express')constapp=express();app.

動作確認

それぞれメソッドのところで補完キーを押してそれぞれのメソッドが候補として出てくるかどうか?
※「.」の後ろ(カーソルをアンダーバーの所に持っていく)で補完キーを押してみる。
※expressはインストール済みとする

message._
http._
app._

発端

大変優秀なVSCodeの拡張機能
「Visual Studio IntelliCode」
これさえインストールしておけば後は全部自動で補完してくれると思っていました・・・が補完候補を出してくれない場合もあったので調べてみました。

※Visual Studio IntelliCodeはVSCodeの拡張機能からインストールできます。

実際の調査手順

pnpm インストール
pnpm install -g pnpm

※pnpmは小さなプログラムやプロジェクトを何個も作る場合、2度目からはインストールが早くなるnpmの代替パッケージマネージャです。
node入門者が小さなサンプルを沢山試す時に、ライブラリがローカルに保存してあるので何度もダウンロードをせず時間短縮になります。

プロジェクトの初期化

pnpm init
package.jsonが作られる

JavaScript標準のCoreライブラリの場合

Coreライブラリなので問題なく補完候補が出ています。
1-no.PNG

test01.js
constmessage="Hello!"message.

message.のところでは補完は効いています。

問題はここから

Node.jsの標準ライブラリの場合

比較してみる

補完が効いてない状態
2-no.PNG
補完が効いている状態
2-ok.PNG

test02.js
consthttp=require('http')http.

http.のところで補完が効かない

理由
nodeの型情報がないから

解決方法
型情報をインストールする

pnpm install --save-dev @types/node

http.のところで補完が効くようになった

Node.jsの外部ライブラリの場合


Express.jsの場合

比較してみる

補完が効いてない状態
3-no.PNG
補完が効いている状態
3-ok.PNG

ライブラリをインストール
pnpm install express

test03.js
constexpress=require('express')constapp=express();app.

補完が効いてない、でもexpressの型情報はどこにあるの?
それに、他のライブラリも一つ一つ探さなきゃ駄目?

補完を自動で探してくれる便利ツール

pnpm install -g typesync

typesyncをインストール

typesyncはpackage.jsonをみて足りない型情報を自動で検索してインストールしてくれます。
(特にTypeScriptと合わせて使うと便利です。)

インストール
pnpm install -g typesync

使い方
VSCodeのターミナルから実行します。
typesync

※package.jsonの "scripts"に登録しておくと便利です。

それぞれのライブラリの手動インストールの方法(※非推奨 一つ一つ型情報を探す手間が面倒なので)
型情報を探してきてライブラリの型定義ファイルをインストールします。
npm install --save-dev @types/express

おまけ 私のVSCodeの設定

settings.json(サジェスト設定部分のみ)
  // サジェスト機能
  // 下記の3つは衝突する、どちらかの機能が効かなくなる
  // カーソルに近い順に候補が並び替えされます。
  // "editor.suggest.localityBonus": true,
  // トリガー文字の入力時に候補が自動的に表示されるようにするかどうかを制御します。
  "editor.suggestOnTriggerCharacters": true,
  // Suggestionsの表示ディレイ
  "editor.quickSuggestionsDelay": 50,
  // ドキュメント内の単語に基づいて入力候補を計算するかどうか
  "editor.wordBasedSuggestions": true,
  //
  // "top"//他の候補の上にスニペットを優先して表示
  // "none"//スニペットの候補を表示しない
  // "bottom"//スニペットの候補は一番下
  // "inline"//ファイル内の関数名とかが優先される。(既定値)
  // editor.suggest.localityBonusと衝突、無効にする。
  "editor.snippetSuggestions": "inline",
  //
  // first 一番上の項目が常に選択されている
  // recentlyUsed 以前に使用された項目が選択されている(デフォルト)
  // recentlyUsedByPrefix 以前に選択した時のプレフィクスに基づいて使用された項目が選択されている
  // recentlyUsedByPrefixだとインテリセンスの候補が下に下がってしまう。
  // 例えば過去にcoと打ってconsoleを選択し、
  // conと打ってconstを選択したことがある場合、
  // coまで打つとconsoleがデフォルト選択され、
  // さらにnを打つとconstがデフォルト選択される。
  "editor.suggestSelection": "first",
  // サジェスト一覧の初期表示項目設定
  "vsintellicode.modify.editor.suggestSelection": "choseToUpdateConfiguration",
  //
  // 入力中に補完候補をだすかどうか
  // 以下の設定でコメントの中でもどこでも
  // 補完候補を出せるようになる
  "editor.quickSuggestions": {
    "other": true,
    "comments": true,
    "strings": true
  },
  // スニペットのタブストップ中にも入力候補を補完します。
  "editor.suggest.snippetsPreventQuickSuggestions": false,


Windows10環境下でElectronのsqlite3をインストール

$
0
0

Electronのsqlite3をインストールで躓く

Electronのsqlite3のインストールでつまづきました。
ほとんどMACやLinuxで書かれた解決方法が多かったので泣きそうになりました。
自分の備忘録として残しておきます。

解決法はこちら

手順2まではElectronではじめるデスクトップアプリケーション開発を参考にさせていただきました。

その前にnode-gypのインストールで躓いている場合はこちらを参考にしてください。
Windowsでnpm installしてnode-gypでつまずいた時対処方法

手順1.普通にsqlite3をnpmインストール

npm install sqlite3

手順2.プロジェクト配下のフォルダにelectronをインストール 

npm install --save-dev electron

手順3.Electronを再構築

npm install --save-dev electron-rebuild

手順3.Electronのバージョンを確認

electron -v

手順4.「--target」の部分を上記で確認したバージョンと同じようにする。これでインストール完了。詳細はnpm sqlit3公式サイトで

sqlite3 --build-from-source --runtime=electron --target=1.7.6 --dist-url=https://electronjs.org/headers

Node.jsのCLIでローディングを実装する

$
0
0

はじめに

とある情報をエクスポートするプログラムを作成していた時に、情報量が多くエクスポートに時間が少しかかるとプログラムが動作しているのかどうか不安になるときがありました。
そんなとき、よく見る、ローディング表示をすれば動作しているのかハングしてしまっているのかがわかるので、実装したいと思いました。

環境

  • Node v10.16.3
  • Typescript 4.0.2
  • npm 6.14.9
  • git version 2.23.0.windows.1
  • Windows 10 Pro

参考サイト

【Node.js】CLIでロード中にクルクル回るアレ(スピナー)を作る

コード

  • Loading.tsローディングスタート及びエンドの関数を提供
Loading.ts
importrlfrom'readline'constspin_char=["","","","","","","","","",""]letspin_count=0;constspin=(message:string)=>{process.stdout.write('\x1B[?25l')//カーソルを消すrl.clearLine(process.stdout,0)//行をすべて削除rl.moveCursor(process.stdout,-9999,0)//一番左側に戻るprocess.stdout.write(`${spin_char[spin_count]}${message}`)//spin_charの配列で描画spin_count++//要素番号計算spin_count>=spin_char.length?spin_count=0:null//要素番号のリセット}exportconstloading=(msg:string)=>setInterval(()=>{spin(msg);},200);exportconstendloading=(loading:NodeJS.Timeout,msg:string)=>{clearInterval(loading)rl.clearLine(process.stdout,0)// 行をすべて削除process.stdout.write(`\n${msg}`)//end Messageprocess.stderr.write('\x1B[?25h')//ローディングで消したカーソルを戻す}

使用例

loading('ローディング中に表示したい文字列')
endLoading(loadingの返り値 ,'終了時に表示したい文字列')
上記をそれぞれローディングスタート、ローディングエンドのタイミングで呼ぶ

QueryCollection.ts
//start Loadingconstloadingstart=loading('Query a collection')//Discovery queryconstresquery=awaitqueryCollection(url,apikey,environmentid,collectionid,version)//end Loadingendloading(loadingstart,'Query a collection Finished!!')

使用イメージ

cliLoading (1).gif

最後に

Loading.tsのspin_charや表示速度等をカスタマイズしていけば自分好みのローディングアニメーションを作ることもできますので是非活用ください。

Azure Communication Services ことはじめ (3) : Microsoft Teams 会議に音声参加ができるまで

$
0
0

Azure Communication Services (以下 ACS) は、リアルタイム コミュニケーション基盤となるサービスで、テキスト | 音声 | ビデオ によるコミュニケーションのハブとなり、接続やコントロールを行うアプリやサービスを SDK などを用いて容易に開発できます。

今回は、2021 年 3 月 から ACS に追加された Microsoft Teams 会議参加ができる機能を使って、Teams 会議に音声通話で参加できるまでの手順を追って確認し、Node.js の Web アプリを作成します。

開発環境

0. 事前準備

Azure Communication Services サービス

Azure Communication Services ことはじめ (1) : チャットができるまで0.事前準備と同様に、Azure Portal で ACS のサービスを作成し、接続文字列とエンドポイントを取得しておきます。

Microsoft Teams tenant interoperability (相互運用) 有効化

Microsoft Teams テナントと ACS の接続を有効にする必要があります。2021 年 3 月現在 プレビュー機能のため、こちらのフォームから申請が必要です。

Request to enable/disable the federation between ACS resources and your Teams tenant

申請者は Microsoft 365 の Global Administrator または Teams Service Administrator の権限が必要になります。(→ Microsoft 365 の 管理者ロール)

1. 音声通話ができるまで

今回は Node.js、Web アプリを念頭に、VoIP 通話 | Teams 会議へ音声参加ができるまでの手順を確認します。

1-0. ライブラリの追加

以下のライブラリを冒頭に追加します。

ライブラリ
azure/communication-commonユーザーの作成、アクセストークン取得
azure/communication-identity (同上) 
azure/communication-calling音声通話のコントロール
client.js
import{AzureCommunicationTokenCredential}from'@azure/communication-common';import{CommunicationIdentityClient}from"@azure/communication-identity";import{CallClient,Features}from"@azure/communication-calling";

1-1. ユーザーを作成し、アクセストークンを取得する

接続文字列を用いて CommunicationIdentityClient を新規作成し、ユーザーの作成とアクセストークンの取得を行います。接続文字列には Azure Communication Services にアクセスするキーが含まれています。アクセストークンの種類は Calling を指定します。(他に Chat, SMS があります)

YOUR_CONNECTION_STRING は 事前準備で取得した 接続文字列に置き換えてください。

client.js
constidentityClient=newCommunicationIdentityClient("YOUR_CONNECTION_STRING");letuserId;letuserToken;identityClient.createUser().then(userResponse=>{userId=userResponse.communicationUserId;identityClient.issueToken(userResponse,["voip"]).then(tokenResponse=>{userToken=tokenResponse.token;}

1-2. 通話クライアントを作成

取得したトークンを用いて、通話をコントロールする CallClient を作成します。
その後作成する CallAgent で VoIP通話 | Teams 会議参加の処理を行います。

client.js
letcallClient;consttokenCredential=newAzureCommunicationUserCredential(userToken);callClient.createCallAgent(tokenCredential).then(agent=>{callAgent=agent;}

1-3. VoIP 通話を開始する | Teams 会議に参加する

VoIP 通話、Teams 会議の参加、いずれも CallAgent から処理することができます。

1-3-1. VoIP 通話を開始する

CallAgent から、通話したい相手を CommunicationUserId で指定して Call します。

client.js
letcallcall=callAgent.call([{communicationUserId:"8:echo123"}],{});

1-3-2. Teams 会議に参加する

CallAgent から、参加したい Teams 会議にアクセスします。Teams 会議 URL を引数として与えるだけで OK です。

client.js
letcallcall=callAgent.join({meetingLink:"TEAMS_MEETING_URL"},{});

Teams 会議 URL は以下のような URL をエンコードしたものになります。
https://teams.microsoft.com/l/meetup-join/19:meeting_xxx...xxx@thread.v2/0?context={"tid":"xxx...xxx","oid":"xxx...xxx"}

Teams 会議への参加ステータス (リクエスト、許可、参加など) を取得したり、録音録画の有無を確認したりすることが可能です。

client.js
varmessage="";call.on('stateChanged',()=>{message=call.state;});call.api(Features.Recording).on('isRecordingActiveChanged',()=>{if(call.api(Features.Recording).isRecordingActive){message="This call is being recorded";};});

2. Web アプリの開発

以上の手順を踏まえて、Visual Studio Code で Node.js Web アプリを作成します。

2-1. 新規 Node.js アプリの作成

Node.js アプリを作成するフォルダーを作成し、Visual Studio Code で開き、npm initコマンドで package.json を作成します。

npm init -y

2-2. Azure Communication Services のライブラリのインストール

npm installコマンドで Azure Communication Services の必要なライブラリ (パッケージ) をインストールします。

npm install @azure/communication-common --save
npm install @azure/communication-identity --save
npm install @azure/communication-calling --save

2-3. Webpack のインストール

今回は Webpack を利用して、JavaScript のモジュールバンドル & ローカルでの実行確認 を行います。
npm installコマンドで、webpack、webpack-cli、webpack-dev-server をインストールします。

npm install webpack@4.42.0 webpack-cli@3.3.11 webpack-dev-server@3.10.3 --save-dev

今回は Azure Communication Services ドキュメント推奨バージョンを指定してインストールしています。

2-4. コーディング

画面 (index.html)

ユーザーからの操作および音声入出力、各種情報を表示するため、index.html という名前でファイルを作成し、以下のような UI を作成します。

ACS_3_01.png

コードは以下になります。

index.html
<!DOCTYPE html><html><head><title>Azure Communication Services - VoIP Calling | Teams Interop</title></head><body><h1>Azure Communication Services</h1><h2>VoIP Calling | Teams Meeting Join Sample App</h2><div><label>Your UserId: </label><inputid="caller-id-output"type="text"placeholder="Your id will be shown here"style="margin-bottom:1em; width: 600px; vertical-align: text-top;"disabled="true"/></div><div><label>Your Token: </label><textareaid="caller-token-output"type="text"placeholder="Your token will be shown here"style="margin-bottom:1em; width: 600px; height: 130px; vertical-align: text-top;"disabled="true"></textarea></div><div><inputid="callee-id-input"type="text"placeholder="Who would you like to call?"style="margin-bottom:1em; width: 300px;"/><buttonid="call-button"type="button"disabled="true"style="margin-bottom:1em">
        Start Call
      </button></div><div><inputid="meeting-url-input"type="text"placeholder="Copy and paste MSTeams meeting link"style="margin-bottom:1em; width: 300px;"/><buttonid="join-meeting-button"type="button"disabled="true"style="margin-bottom:1em">
        Join Teams Meeting
      </button></div><div><buttonid="hang-up-button"type="button"disabled="true"style="margin-bottom:1em">
        Hang Up
      </button></div><div><labelid="message-output"style="margin-bottom:1em; width: 1000px; height: 50px;"></label></div><script src="./index.js"></script></body></html>

加筆を行って整えたコードは以下になります。
ACSTeamsCallWeb202103/index.html

通話機能 (client.js)

client.js という名前でファイルを作成し、VoIP 通話 | Teams 会話 をコントロールする機能を記述します。

後ほど client.js を index.js にビルドして index.html で読み込みます。

利用ライブラリー

今回は、これらのライブラリーを利用します。

client.js
import{AzureCommunicationTokenCredential}from'@azure/communication-common';import{CommunicationIdentityClient}from"@azure/communication-identity";import{CallClient,Features}from"@azure/communication-calling";

接続文字列

client.js に connectionString を記載しておきます。

YOUR_CONNECTION_STRING は 事前準備で取得した 接続文字列に置き換えてください。

セキュリティの観点から別の設定ファイルなどに記載して読み出すのが一般的ですが、今回は動作を確認するのみのアプリなので本体に記載しています。

client.js
letconnectionString="YOUR_CONNECTION_STRING";

UI 画面の入出力

呼び出しを行う相手先の UserId または Teams 会議リンク(URL) の入力を取得できるようにします。
また、VoIP通話 の開始 | Teams 会話 の参加、終了のボダンのクリックを取得できるようにします。
出力は、UserId、UserToken、動作に対するメッセージを表示するようにしておきます。

client.js
// 入力constcalleeInput=document.getElementById("callee-id-input");// 通話先 UserIdconstcallButton=document.getElementById("call-button");// 通話開始ボタンconstmeetingUrlInput=document.getElementById("meeting-url-input");// Teams会議URLconstjoinMeetingButton=document.getElementById("join-meeting-button");// Teams会議会話参加ボタンconsthangUpButton=document.getElementById("hang-up-button");//通話終了ボタン// 出力constcallerIdOutput=document.getElementById("caller-id-output");// ACS UserIdconstcallerTokenOutput=document.getElementById("caller-token-output");// ACS UserTokenconstmessageOutput=document.getElementById("message-output");// 状態メッセージ

CommunicationUser の新規作成、Token の取得、CallAgent の生成

接続文字列から User を新規作成して Token を取得します。Token を利用して、通話のコントロールを行う CallAgent を生成します。CAllAgent が無事生成出来たら callButton (VoIP通話開始ボタン) | joinMeetingButton (Teams会議参加ボタン) をクリック可能(disabled = false)にします。

client.js
constidentityClient=newCommunicationIdentityClient(connectionString);constcallClient=newCallClient();letcallAgent;letcall;identityClient.createUser().then(identityResponse=>{callerIdOutput.value=identityResponse.communicationUserId;// ACS User Id 画面出力identityClient.getToken(identityResponse,["voip"]).then(tokenResponse=>{constuserToken=tokenResponse.token;callerTokenOutput.value=userToken;// ACS User Token 画面出力messageOutput.innerText+="Got user token.";// 状態メッセージ画面出力consttokenCredential=newAzureCommunicationTokenCredential(userToken);callClient.createCallAgent(tokenCredential,{displayName:'ACS user'}).then(agent=>{callAgent=agent;callButton.disabled=false;// VoIP通話開始ボタンをクリック可能にjoinMeetingButton.disabled=false;// Teams会議参加ボタンをクリック可能にmessageOutput.innerText+="\nReady to call | join MSTeam's Meeting.";// 状態メッセージ画面出力});});});

VoIP 通話の開始

callButton (通話開始ボタン) がクリックされたら、callAgent から相手の Communication Id (callee-id-input TextBox から取得) の呼び出しを行います。

client.js
callButton.addEventListener("click",()=>{// 通話開始call=callAgent.startCall([{id:calleeInput.value}],{});// 各ボタンのステータス変更、状態メッセージ表示hangUpButton.disabled=false;// 通話停止ボタンをクリック可能にcallButton.disabled=true;// VoIP通話開始ボタンをクリック不可にjoinMeetingButton.disabled=true;// Teams会議参加ボタンをクリック不可にmessageOutput.innerText+="\nCall: started.";// 状態メッセージ画面出力    });

Teams 会議への参加

joinMeetingButton (Teams会議参加ボタン) がクリックされたら、callAgent から Teams 会議 (meeting-url-input TextBox から取得) への参加をリクエストします。
call のステータスを確認して、会議参加リクエストの承認、会議参加、録音録画有無を表示します。

client.js
joinMeetingButton.addEventListener("click",()=>{// 会議参加リクエストcall=callAgent.join({meetingLink:meetingUrlInput.value},{});// 会議参加ステータスのチェックcall.on('stateChanged',()=>{messageOutput.innerText+="\nMeeting:"+call.state;})// 会議の録音録画のステータス表示call.api(Features.Recording).on('isRecordingActiveChanged',()=>{if(call.api(Features.Recording).isRecordingActive){messageOutput.innerText+="\nThis call is being recorded";};});// 各ボタンのステータス変更、状態メッセージ表示hangUpButton.disabled=false;// 通話停止ボタンをクリック可能にcallButton.disabled=true;// VoIP通話開始ボタンをクリック不可にjoinMeetingButton.disabled=true;// Teams会議参加ボタンをクリック不可に});

通話の終了

hangupButton (通話終了ボタン) がクリックされたら、callAgent の通話を切断します。

client.js
hangUpButton.addEventListener("click",()=>{// 通話 | 会議参加 の終了call.hangUp({forEveryone:true});// 各ボタンのステータス変更、状態メッセージ表示hangUpButton.disabled=true;// 通話停止ボタンをクリック不可にcallButton.disabled=false;// VoIP通話開始ボタンをクリック可能にjoinMeetingButton.disabled=false;// Teams会議参加ボタンをクリック可能にmessageOutput.innerText+="\nNow hanged up.";});

最終的なコードはこちらになります。
ACSTeamsCallWeb202103/client.js

3. 音声通話 | Teams 会議通話参加 を試してみる

今回は Webpack を利用しているので、client.js を index.js にビルドして起動します。

npx webpack-dev-server --entry ./client.js --output index.js --debug --devtool inline-source-map

起動したら、ブラウザーから http://localhost:8080にアクセスします。
User Id と Token が取得できると [Start Call]のボタンがアクティブになります。

音声会話のチェック

音声通話テスト用のユーザーである 8:echo123を入力して [Start Call]をクリックします。
"Hello, welcome to Azure Communication Services audio testing system..." と音声が聞こえれば OK です。音声を録音して再生することで、こちらの音声が取得できていることも確認できます。
[Hang Up]をクリックすると会話は終了します。

Teams 会議通話参加のチェック

Teams 会議を作成し、参加 URL を取得します。
Teams 会議参加 URL を入力て Join Team's Meetingをクリックします。
[Hang Up]をクリックすると会議から退出します。

ACS_3_02.png
ACS_3_03.png

Node.jsのStreamを使ってみる

$
0
0

はじめに

Node.jsを使っていて,Streamに触れる機会が多々あったため,改めて調べてみました。
この記事はその備忘録になります。

Streamとは

Node.jsに存在しているオブジェクトの一つで,ストリーミングデータを扱うためのオブジェクト。(公式ドキュメント

Streamを用いることで,下記のようなメリットがあります。

  • 非同期処理であるため,他の処理と並行して実行することができる。
  • データを一括で処理するのではなく,一定量ずつ処理を行うため,メモリを圧迫しない。

以下でStreamを使ったサンプルコードを紹介します。

実行ファイル作成

実行ファイルの完成形

100KBのcsvファイルを読み込み,それをそのまま別ファイルに書き出します。
今回は,File System モジュールで用意されているfs.ReadStreamクラスと,fs.WriteStreamクラスを利用します。

sample.js
// 必要なモジュールを読み込むconstfs=require('fs');// 読み込み用のStreamを作成constreadStream=fs.createReadStream('100KB.csv');// 書き込み用のStreamを作成constwriteStream=fs.createWriteStream('output.csv');// 読み込み用のStreamにイベントリスナーを追加// dataイベント : データが読み込まれる度に発火するreadStream.on('data',chunk=>{console.log(`chunk length = ${chunk.length} (bytes)`);returnwriteStream.write(chunk)});// endイベント : データの読み込みが全て完了したら発火するreadStream.on('end',()=>{returnwriteStream.end()});

この後一つ一つについて詳細に説明します。

読み込み用のStreamを作成

sample.js(一部)
// 読み込み用のStreamを作成constreadStream=fs.createReadStream('100KB.csv');

100KBのファイルを用意し,それを読み込むためのStreamを作成します。(fs.createReadStreamメソッド

書き込み用のStreamを作成

sample.js(一部)
constwriteStream=fs.createWriteStream('output.csv');

読み込んだファイルを書き込むファイルを用意し,書き込み用のStreamを作成(fs.createWriteStreamメソッド

読み込んだファイルの内容を,別ファイルに書き込む

sample.js(一部)
// 読み込み用のStreamにイベントリスナーを追加// dataイベント : データが読み込まれる度に発火するreadStream.on('data',chunk=>{console.log(`chunk length = ${chunk.length} (bytes)`);returnwriteStream.write(chunk)});

dataイベントは,一定量のデータが読み込まれる度に発火します。
dataイベントのコールバック関数には,読み込んだデータが渡されます。(デフォルトではBufferオブジェクトが渡される)
今回は,渡されたデータをそのまま別ファイルに書き込みます。(writeable.writeメソッド

書き込みを終了する

sample.js(一部)
// endイベント : データの読み込みが全て完了したら発火するreadStream.on('end',()=>{returnwriteStream.end()});

endイベントは,読み込むデータがもうない場合に発火します。
endイベントが発火したら,ファイルへの書き込みを終了させます。(writable.endメソッド

実行

$node sample.js
chunk length = 65536 (bytes)
chunk length = 36864 (bytes)

fs.createReadStreamメソッドで作成された読み込み用のStreamが一度に読み込むデータは,デフォルトでは64KBなので,一度目のdataイベント発火時には100KB中の64KBが読み込まれ,2度目のdataイベント発火時に残りの36KBが読み込まれている。

参考

Node.js 公式

その他

Expressが久しぶりで、忘れたときに見るやつ

$
0
0

「Express久しぶりに使うな〜、あれ?」

「どう書くのか忘れたー!」というときに見るものです

環境を作る

ディレクトリを作って、ターミナルでそこに移動して
npmを初期化します

npm init -y

次にExpressをインストールします

npm install--save express

これで起動できます

node .

お好みで開発に便利なnodemon

index.jsを用意します

index.js
constexpress=require("express")constapp=express()/* 書いてあるコードはここに書いてね */app.listen(3000,()=>{console.log("listening at http://localhost:3000")})

準備完了

リクエストを捌く

GETやらPOSTやらをうけてレスポンス返そう

GET

これでブラウザで http://localhost:3000にアクセスすればHello World!と返ってきます

app.get("/",(req,res)=>{res.send("Hello World!")})

POST

Postmanやcurlなどを使って試しましょう
これで http://localhost:3000にPOSTするとHello World!と返ってきます

app.post("/",(req,res)=>{res.send("Hello World!")})

場所を変える

/5000trillionとかにアクセスしたときのリクエストを捌こう

app.get("/5000trillion",(req,res)=>{res.send("5000兆円\n欲しい!")})

簡単なデータの処理

これで http://localhost:3000/5000trillion?mode=deflationにアクセスすると
デフレが起きて物価が下がり、50万円で満足します
50万円欲しい!と返ってきます

ちなみに?mode=deflationクエリ文字列といいます
URLにちょっとデータを含めることができます

app.get("/5000trillion",(req,res)=>{if(req.query.mode==="deflation"){res.send("50万円\n欲しい!")}else{res.send("5000兆円\n欲しい!")}})

静的な場所

publicフォルダーの下を/assetsにアクセスしたときに返したいとき
こんな感じになります
http://localhost:3000/assets/logo.png<-> ./public/logo.png

app.use("/assets",express.static("./public"))

ファイル分割

ディレクトリ別に.jsファイルを分けることもできます
route.get()のパスを"/5000trillion"にすると、/5000trillion/5000trillionになるので気をつけましょう

5000t.js
constexpress=require("express")constroute=express.Router()route.get("/",(req,res)=>{if(req.query.mode==="deflation"){res.send("50万円\n欲しい!")}else{res.send("5000兆円\n欲しい!")}})module.exports=route
index.js
constroute=require("./5000t")app.use("/5000trillion",route)

ミドルウェア

get(...)などの前に認証などの処理をすることができます
ログを出してみます
最後に必ずnext()を呼び忘れないようにしましょう
そうしないと、続きが実行されません

app.use((req,res,next)=>{console.log(`[${Date.now()}] ${req.ip}${req.url}`)next()})// `/5000trillion`の下だけapp.use("/5000trillion",(req,res,next)=>{res.setHeader("powered-by","5000 trillion yen!")// ヘッダーを追加 CORSなんかもここでする ライブラリ使ったほうがいいconsole.log(`[${Date.now()}] ${req.ip}${req.url}`)next()})

おわり

「せや!Expressこんな感じやったな」というふうになったらいいな

Node.jsライブラリのPuppeteerでスクショしてみた

$
0
0

業務でNode.jsライブラリのPuppeteerが中心的な役割の部分を触っているのでその学習の為に導入して動かしてみようと思いました。

Puppeteerとは?

公式サイト:https://pptr.dev/
パペティアと読みます。Node.jsライブラリで、Google製。
GoogleのWebブラウザ「Chrome」は、UIを持たずコマンドラインやリモートデバッグ機能を通じてWebブラウザを操作できる「Headless Chrome」機能を備えています。Headless Chromeを利用すると人間がWebブラウザをマウスやキーボードで操作することなく、プログラムでHeadless Chromeを起動し、特定のWebページを読み込み、画面キャプチャの取得や、指定された場所をクリックし、値を入力し結果を取得する、といった操作を自動的に行わせることができるようになります。

UIの自動テストとか、スクレイピングなどでも使えて汎用性が高いのですが、この記事ではあくまでもスクショ機能だけを触れていきます。

導入

早速動かしてみましょう、適当に作業ディレクトリを作って、以下のどちらかのコマンドでPuppeteerをインストールします。

npm i puppeteer
or
yarn add puppeteer

まずは公式ドキュメントに載ってる超基本のコードでスクショしてみましょう。
example.jsというファイルに以下のコードを書きます。

constpuppeteer=require('puppeteer');(async()=>{//ヘッドレスブラウザの起動constbrowser=awaitpuppeteer.launch();//タブを開くconstpage=awaitbrowser.newPage();//指定したURLに遷移awaitpage.goto('https://qiita.com/hisashi_matsui');//スクショを撮り、ファイル名example.pngにする。awaitpage.screenshot({path:'example.png'});//終了awaitbrowser.close();})();

実行します

nodeexample.js

するとルート直下にスクショのPNGファイルが保存されます!
スクリーンショット 2021-03-29 23.05.37.png

これだけだと、あくまでも基本中の基本なので、もう少しカスタマイズしていきましょう。

ブラウザのサイズを設定
デフォルトだと800 × 600になります。

setViewPort({width:1900,height:1200})

evaluateメソッド
これを使うと、Nodeではなくブラウザ上(Headless Chrome)でJavaScriptを実行できます。Puppeteerが提供していない高度な処理が必要な場合や、Webページに埋め込まれたJavaScriptを実行する場合などに使用します。

constdocTitle=awaitpage.evaluate(()=>{returndocument.title});console.log(docTitle)//このコンソールは当然コマンド上に出力されます

第一引数内の関数に引数を渡す場合は第二引数に入れる必要があります。

consta=15;constb=5;constsum=awaitpage.evaluate((a,b)=>{returna+b},a,b);console.log(sum)//結果は20

screenshotのオプション
clip: スクショ範囲を細かく指定できます。x,y座標でどこから縦横何pxを撮影するかを設定できます。

path: ファイルの保存先を指定
(Node.js の標準モジュール path が提供している path.join メソッドを使用して、ディレクトリ名あるいはファイル名を結合、この場合あらかじめscreen_shotsという名のフォルダを用意する必要があります)

constpath=require('path');constfilepath=path.join(__dirname,'screen_shots','example.png');awaitpage.screenshot({clip:{x:100,y:100,width:300,height:300},omitBackground:true,path:filepath});

まとめ

このPuppeteerはドキュメントが比較的読みやすかったですし、関連記事も多かったです。
ちなみに、リリース当初はグラデーションとか複雑な描画に弱かったらしいのですが、今は改善されてるみたいです。

Ruby on Railsの環境が整うDockerfile

$
0
0

安易。
MariaDBとかは他コンテナで建てるものとします。

Dockerfile

FROM node:lts-slim AS nodeFROM ruby:3COPY --from=node /opt/ /opt/COPY --from=node /usr/local/lib/ /usr/local/lib/COPY --from=node /usr/local/bin/ /usr/local/bin/RUN apt update && apt upgrade -y&& apt install-y g++

RUN gem install rails

ちょっと説明

マルチステージビルドする

NodeイメージからNodeインタプリタのあるディレクトリをごっそりを持ってくる。
Nodeの特定バージョンを入れようと思うとcurlでインストーラとってきて、スクリプト実行して、必要ならyarnを別で入れるなどちょっとめんどくさい。
でもマルチステージビルドならDocker Official ImageのNodeインタプリタを拾えるし、バージョン指定はイメージのタグを変えればいいだけなのでちょっと楽。

# NodeインタプリタをとってくるイメージFROM node:lts-slim AS node# 最後に書いたFROMがイメージのベースになるFROM ruby:3# インタプリタの含まれるディレクトリをコピーCOPY --from=node /opt/ /opt/COPY --from=node /usr/local/lib/ /usr/local/lib/COPY --from=node /usr/local/bin/ /usr/local/bin/RUN apt update && apt upgrade -y&& apt install-y vim g++ # <- Nodeの駆動にg++が必要

参考: Multi-stage build でNode.jsのインストールをちょっぴり効率化する - アクトインディ開発者ブログ

railsのインストール

グローバルにインストール。
あとはdocker run -it <image name> bashとかしてコンテナ内でrails new appとかすると準備完了。

RUN gem install rails

【AWS S3とNode.jsを連携して画像を持ってくる】

$
0
0

AWS S3のオリジン間リソース共有 (CORS) サポートを利用して、直接Node.jsでfileをダウンロードしてみたのでその時のやり方を載せておく。

この記事では、IAMの利用から、S3にある画像をnode.jsで取得できるようにする流れがわかってもらえればいいなという感じです。


【AWS側の設定】

1,S3にのみアクセスできるIAMグループを作る

今回は、AccessToS3OnlyGroup というグループをS3FullAccess権限のみをもたせて作る。

2,新しくIAMユーザの作成

IAMユーザーを新しく作り、先程作ったAccessToS3OnlyGroupというグループに割り当てます。
ちなみに、AmazonS3FullAccessポリシーの中身はこのようになっています。

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:*","Resource":"*"}]}//Effect → Allow or Deny (許可 or 拒否)//Action → どういった権限を与えるかなどの細かい設定 (GET, POST, など)//Resouce → どのS3を使用しているのかといった設定//Condition → このポリシーが適用される条件

3,IAMユーザでログインする

・rootユーザーでログイン
・IAM→UsersからログインしたいIAMユーザーのsecurity credentials画面へ
・Summaryにあるサインインurlからログインする。
https://~~~.signin.aws.amazon.com/console(~~~の部分がアカウントエイリアスにあたる)
→アカウントIDは、IAMユーザでログイン後、MyAccountの右にある数字の羅列なので、ログイン時にこちらを入力してもログイン可能。

4, 同画面でアクセスキーを作っておく

3番のときと同じsecurity credentials画面で、Access keys から「create access key」から、accessKeyId, secretAccessKeyを作成し、メモしておく。

5, S3に画像をUploadする


【以降はNode.js側の設定になります】

6,app.jsでS3と連携するために必要なモジュールをinstal

npm install cors
npm install aws-sdk
corsのoptionsについて
・origin ... Access-Control-Allow-Origin CORSヘッダーを設定する。どのオリジンにヘッダーを付与するか、しないかなどの設定ができる。
・credentials ... 資格情報系の許可(Cookieなど)を与えるかどうかなどの設定ができる。
今は、cookieを使わないのでfalseにしておく。

constexpress=require('express')constapp=express();constcors=require('cors')constAWS=require('aws-sdk')app.use(cors({origin:true,credentials:false}))

ちなみに、origin : falseにすると以下のようなエラーが出る。

AccesstoXMLHttpRequestat'http://localhost:8080/menu'fromorigin'http://localhost:3000'hasbeenblockedbyCORSpolicy:No'Access-Control-Allow-Origin'headerispresentontherequestedresource.

7, S3からファイルを取得

・S3から特定のバケット内の特定のファイルを取得する。

constparams={'Bucket':'My-app-imgs','Key':'A.jpg'}s3Client.getObject(params,(err,data)=>{if(err){console.log(err)}else{console.log(data)}})

・上記のコードを実装するときに出たエラー(気をつけましょう)
→Keyと指定したつもりだったが、keyになっており、一文字目が大文字になってなかった。

MultipleValidationErrors:Therewere2validationerrors:*MissingRequiredParameter:Missingrequiredkey'Key'inparams*UnexpectedParameter:Unexpectedkey'key'foundinparamsatParamValidator.validate

・S3の特定のバケットからファイル一覧を取得する方法

constparams={'Bucket':'My-app-imgs'}s3Client.listObjects(params,(err,datas)=>{if(err){console.log(err)}else{console.log(datas)}})

・dataの中身
Bodyにバイナリで返されるのでstringに変換する必要がある。

{AcceptRanges:'bytes',LastModified:2021-03-28T07:55:49.000Z,ContentLength:867920,ETag:'"5b6a5a0c6aea626483f3bfbda1c32da3"',ContentType:'image/jpeg',Metadata:{},Body:<Bufferffd8ffe000104a46494600010100000100010000ffdb0084000505050506050607070609090809090d0c0b0b0c0d130e0f0e...867870morebytes>
}

【おまけ imageのバイナリデータを表示する方法】
(これは、Reactのコンポーネント内で使ったときのやつです)

<imgsrc={`data:image/jpeg;base64,${data}`}/>

【Laravel8】npm run dev sh: mix: command not found

$
0
0

経緯

laravelでsassを使うためにnpmをいじっている際にエラーに遭遇しました。

環境

Laravel Framework 8.34.0

エラーの内容

以下がエラーの内容です。(抜粋)

$ npm run dev

> @ dev /folder/foo
> npm run development


> @ development /folder/foo
> mix

sh: mix: command not found
npm ERR! code ELIFECYCLE
npm ERR! syscall spawn
npm ERR! file sh
npm ERR! errno ENOENT
npm ERR! @ development: `mix`
npm ERR! spawn ENOENT
npm ERR! 
npm ERR! Failed at the @ development script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

mixのコマンドがないと言われてしまいました。これは参りました。

検証

以前は動いていたため、とりあえずlaravelのバージョンを7系に下げてもう一度実行。すると、今度はちゃんと動きました!

ということはバージョンが低いのでしょうか。

解決方法

バージョンを上げてみましょう。以下のコマンドを入力して実行します。

npm i --save-dev laravel-mix@latest && 
npm i --save-dev sass-loader@latest && 
npm i --save-dev postcss@latest && 
npm i --save-dev webpack@latest

さて、この状態でもう一度 npm run devを実行します。

$ npm run dev

> @ dev /folder/foo
> npm run development


> @ development /folder/foo
> mix


● Mix █████████████████████████ emitting (95%)  
 emit




● Mix █████████████████████████ done (99%) plugins 
 WebpackBar:done

今度はちゃんと実行できました!

おわりに

バージョンは最新に!そして古い記事には注意!

参考

Laravel, NPM: Command “mix” not found

Docker上のNode.jsサーバーにcurlしたらConnection reset by peerが返ってくる

$
0
0

VirtualBoxでUbuntuを立ててその中でDockerコンテナを使っていて嵌ったので備忘録として書いておく。

結論

Docker上のNode.jsサーバーのhost0.0.0.0に変更

host-machine
$docker exec-it node-container_1 bash
bash-5.0$cd /path/to/project/
bash-5.0$gatsby develop --host=0.0.0.0

状況

  • Vagrant(VirtualBox)でUbuntuのVMを作成している
  • VM上でNode.js(Alpine)のDockerコンテナを立てている

VMのUbuntuにて

Ubuntu(vm)
$curl localhost:8000
curl: (56) Recv failure: Connection reset by peer

Connection resetされる

しかし、docker exec -it node-container_1 bash等でコンテナに入ると

docker
$docker exec-it node-container_1 bash
bash-5.0$curl localhost:8000
<!DOCTYPE html><html><head><meta charSet="utf-8"/>  ...

レスポンスが返ってくる。

環境

Ubuntu

ubuntu-version
$cat /etc/os-release 
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

Docker

docker-version
$docker version 
Client: Docker Engine - Community
 Version:           20.10.5
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        55c4c88
 Built:             Tue Mar  2 20:18:20 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       363e9a8
  Built:            Tue Mar  2 20:16:15 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.4
  GitCommit:        05f951a3781f4f2c1911b05e61c160e9c30eaa8e
 runc:
  Version:          1.0.0-rc93
  GitCommit:        12644e614e25b05da6fd08a38ffa0cfe1903fdec
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker-compose

一般的なNode.jsコンテナです。

version:"3.7"services:node:build:context:./nodeuser:"node"working_dir:/home/node/appenvironment:-NODE_ENV=developmentvolumes:-./node/app:/home/node/apprestart:alwaysexpose:-"8000"ports:-8000:8000stdin_open:true

原因

Dockerコンテナ内で gatsby developすると通常は
localhost:8000をLISTENするようなサーバーが立ち上がる。

コンテナ内では localhost=>127.0.0.1に解決されるが、

ホストマシンから見たコンテナのIPアドレスを確認すると、

ubuntu
$ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:b8ff:feeb:e3a8  prefixlen 64  scopeid 0x20<link>        ether 02:42:b8:eb:e3:a8  txqueuelen 0  (Ethernet)
        RX packets 21  bytes 3292 (3.2 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 27  bytes 12702 (12.7 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

となり、ホストから見たコンテナのIPアドレスは172.17.0.1である。
(これはDockerによって自動的に決まるIPアドレス)

127.0.0.1:8000 =x=> 172.17.0.1:8000
となりConnection Refuseされてしまい、
コンテナとホストでLISTENしているIPアドレスが異なることが原因。

対応

ホストからLISTENされているポートを確認すると、

ubuntu
$docker ps -lCONTAINER ID   IMAGE              COMMAND                  PORTS                                                                                                                NAMES
831fc56b8d87   node-container_1   "docker-entrypoint.s…"   0.0.0.0:8000->8000/tcp

なので、コンテナ内のアプリケーションでLISTENするホストを0.0.0.0(any IPv4-addresses) に変更すれば良さそうなので、

docker
$docker exec-it node-container_1 bash
bash-5.0$cd /path/to/project/
bash-5.0$gatsby develop --host=0.0.0.0

コマンド実行時に --host=0.0.0.0を追加する。

結果

ubuntu
$curl localhost:8000
<!DOCTYPE html><html><head><meta charSet="utf-8"/>...

無事正常なレスポンスが得られました。

参考

Swagger(node.js)でCannot find Module swagger_routerが出たときの対処

$
0
0

エラー(全文)

Error: Cannot find module 'C:\path\api\fittings\swagger_router'
Require stack:
- C:\path\node_modules\bagpipes\lib\fittingTypes\user.js     
- C:\path\node_modules\bagpipes\lib\bagpipes.js
- C:\path\node_modules\bagpipes\lib\index.js
- C:\path\node_modules\swagger-node-runner\index.js
- C:\path\node_modules\swagger-express-mw\lib\index.js       
- C:\path\app.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
    at Function.Module._load (internal/modules/cjs/loader.js:725:27)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at createFitting (C:\path\node_modules\bagpipes\lib\fittingTypes\user.js:18:20)
    at Bagpipes.newFitting (C:\path\node_modules\bagpipes\lib\bagpipes.js:158:17)
    at Bagpipes.createFitting (C:\path\node_modules\bagpipes\lib\bagpipes.js:147:22)
    at Bagpipes.createPipe (C:\path\node_modules\bagpipes\lib\bagpipes.js:111:19)
    at Bagpipes.getPipe (C:\path\node_modules\bagpipes\lib\bagpipes.js:50:38)
    at C:\path\node_modules\bagpipes\lib\bagpipes.js:34:10   

解決策

node_modules/bagpipes/libfittingTypes/user.js24列目、

varsplit=err.message.split(path.sep);

varsplit=err.message.split('\n')[0].split(path.sep);

に変更する

参考

receiptline でレシートプリンター型日めくりカレンダーを作ってみた

$
0
0

さあみなさん、買わなくていいのでちょっと見ていってください。

本日ご紹介する商品は、こちらの日めくりカレンダーです。
どうですこのボディー。レシートプリンターそっくりでしょう?
01.jpg

実はカレンダーを毎朝6時30分に印刷する優れものなんです!
もちろん時刻はお好みに合わせて変更することができます。
02.jpg

カレンダーをめくるのに失敗してがっかりしたことありますよね?
この商品があれば再印刷できるので何度でもやり直しができます!
03.jpg

しかも印刷時にピヨピヨと鳴るメーカーオプションの大音量ブザー付き!
音量は調節できるので、目覚まし時計としてもお使いいただけます!
04.jpg

これだけではありません!今回は特別に Node.js のソースコードもお付けします!
LAN 対応のローカル版、さらに CloudPRNT 対応のクラウド版をご用意いたしました。

ローカル版

local.js
constnet=require('net');constcron=require('node-cron');constreceiptline=require('receiptline');const{convert}=require('convert-svg-to-png');cron.schedule('0 30 6 * * *',()=>{// ReceiptLineconsttext=`\n\n\n\n\n\n\n\n
    \`"^^^~~~~~~~2021年~~~~~~~

    ^令和3年

    April|^^^^"4月|卯月

    "^^^^^^^1

    ^^^木曜日
    Thursday

    -

    \エイプリルフール/

    {b:line}
    |"2021年 3月|"2021年 4月|
    -
    |_日 月 火 水 木 金 土|_日 月 火 水 木 金 土|
    |~~ 1 2 3 4 5 6|~~ ~~ ~~ ~~ 1 2 3|
    |\`\`8 9 10 11 12 13|\`\`5 6 7 8 9 10|
    |\`14\` 15 16 17 18 19 \`20\`|\`11\` 12 13 14 15 16 17|
    |\`21\` 22 23 24 25 26 27|\`18\` 19 20 21 22 23 24|
    |\`28\` 29 30 31 ~~ ~~ ~~|\`25\` 26 27 28 \`29\` 30 ~~|
    {b:space}`;// SVGconstsvg=receiptline.transform(text,{encoding:'cp932'});// PNGconvert(svg).then(png=>{// Printerconstprinter={host:'192.168.1.253',port:9100,gamma:1.0,upsideDown:true,cutting:false,command:'starmbcs'};constsocket=net.connect(printer.port,printer.host,()=>{socket.end(receiptline.transform(`{x:\x07}\n|{i:${png.toString('base64')}}`,printer),'binary');});socket.on('error',err=>{console.log(err.message);});});});

クラウド版

cloud.js
constexpress=require('express');constapp=express();constcron=require('node-cron');constreceiptline=require('receiptline');const{convert}=require('convert-svg-to-png');// ReceiptLinelettext='';cron.schedule('0 30 6 * * *',()=>{// ReceiptLinetext=`\n\n\n\n\n\n\n\n
    \`"^^^~~~~~~~2021年~~~~~~~

    ^令和3年

    April|^^^^"4月|卯月

    "^^^^^^^1

    ^^^木曜日
    Thursday

    -

    \エイプリルフール/

    {b:line}
    |"2021年 3月|"2021年 4月|
    -
    |_日 月 火 水 木 金 土|_日 月 火 水 木 金 土|
    |~~ 1 2 3 4 5 6|~~ ~~ ~~ ~~ 1 2 3|
    |\`\`8 9 10 11 12 13|\`\`5 6 7 8 9 10|
    |\`14\` 15 16 17 18 19 \`20\`|\`11\` 12 13 14 15 16 17|
    |\`21\` 22 23 24 25 26 27|\`18\` 19 20 21 22 23 24|
    |\`28\` 29 30 31 ~~ ~~ ~~|\`25\` 26 27 28 \`29\` 30 ~~|
    {b:space}`;});// Serverapp.post('/calendar',(req,res)=>{// Print Jobif(text.length>0){res.json({jobReady:true,mediaTypes:['application/vnd.star.starprnt']});}else{res.json({jobReady:false});}})app.get('/calendar',(req,res)=>{// SVGconstsvg=receiptline.transform(text,{encoding:'cp932'});// PNGconvert(svg).then(png=>{// Printerconstprinter={gamma:1.0,upsideDown:true,cutting:false,command:'starmbcs'};// Commandconstcommand=Buffer.from(receiptline.transform(`{x:\x07}\n|{i:${png.toString('base64')}}`,printer),'binary');res.status(200).type('application/vnd.star.starprnt').send(command);text='';});});app.listen(8080,()=>{console.log('Server running at http://localhost:8080/');});

さらにさらに!日めくりカレンダーのデザインをカスタマイズできるツールも無料でご提供!
https://receiptline.github.io/designer/

ReceiptLine
`"^^^~~~~~~~2021年~~~~~~~

^令和3年

April|^^^^"4月|卯月

"^^^^^^^1

^^^木曜日
Thursday

-

\エイプリルフール/

{b:line}
|"2021年 3月|"2021年 4月|
-
|_日 月 火 水 木 金 土|_日 月 火 水 木 金 土|
|~~ 1 2 3 4 5 6|~~ ~~ ~~ ~~ 1 2 3|
|`7` 8 9 10 11 12 13|`4` 5 6 7 8 9 10|
|`14` 15 16 17 18 19 `20`|`11` 12 13 14 15 16 17|
|`21` 22 23 24 25 26 27|`18` 19 20 21 22 23 24|
|`28` 29 30 31 ~~ ~~ ~~|`25` 26 27 28 `29` 30 ~~|
{b:space}

05.png
どうですみなさん、すごいでしょう?
・・・エイプリルフールネタにしてみました。

マークダウン言語で紙のレシートや電子レシートを簡単に作れる receiptline。
パッケージに添付されているプログラム例を参考に作りました。
https://github.com/receiptline/receiptline

receiptline で SVG に変換後、さらにプリンターコマンドに変換しています。
4月1日固定なので、当日の日付を印刷するように修正してくださいね!

また何か作ったら投稿します。ではまた!

Viewing all 8880 articles
Browse latest View live