20210912のNode.jsに関する記事は9件です。

ウマ娘で初めてランクAができたのでLINEボットにスクリーンショットを投稿してみた

タイトルの通りですが、複数の処理を連携させていまして、結局やることは、 ①ウマ娘プリティーダービーで、ウマ娘のプロファイルを画面キャプチャし、それをLINEメッセージとして投稿 ②取得した画像から、Google Cloud Vision APIでOCR(文字認識)して、ステータス値を判別 ③ステータス値をLINEメッセージのレスポンスとして返す 大事なのは②です。すなわち、以下の2つです。 ・LINEメッセージから画像ファイルを取得 ・Google Cloud Vision APIで文字認識 ソースコードもろもろは以下に上げておきます。 poruruba/CloudVision_Test LINEメッセージから画像ファイルを取得 以下の部分です。 api/controllers/tsundete/index.js const { streamToBuffer } = require('@jorgeferrero/stream-to-buffer'); var stream = await client.getMessageContent(event.message.id); var buffer = await streamToBuffer(stream); 通知されるメッセージには直接は画像ファイルは含まれず、IDが知らされるので、client.getMessageContent を呼び出して取得します。取得するとStream形式で取得されるため、有志のnpmモジュールを使ってバッファに変換しています。 上記のclientは以下で作ったものです。 const config = { channelAccessToken: '【LINEチャネルアクセストークン】', channelSecret: '【LINEチャネルシークレット】', }; const line = require('@line/bot-sdk'); client = new line.Client(config); LINEのnpmモジュールを使っています。 line/line-bot-sdk-nodejs steamToBufferは、以下のnpmモジュールを使わせていただきました。 @jorgeferrero/stream-to-buffer LINEに関する処理はユーティリティ「line-utils.js」にまとめてあります。また、LINEボットの設定やシークレット情報の取得も必要です。 詳細は以下が参考になります。  LINEボットを立ち上げるまで。LINEビーコンも。 Google Cloud Vision APIで文字認識 「textdetection.js」に処理をまとめておきました。 やっていることは以下の3つです。 あらかじめ、手動で画像から抽出したい文字列の場所(X,Y座標と幅と高さ)のリストを決めておき、テンプレートとします。 ①ベースとなる文字列を探して、Y方向の位置を特定します。 ②文字列の範囲を抽出するためのマスクを生成します。 ③アスペクト比を保ったまま幅をテンプレートに合わせます。 ④Google Cloud Vision APIで文字認識して文字列を抽出します。 ⑤抽出した文字列を、テンプレートの場所とマッピングします。 ①は、スマホによっては、ノッチの有無によって上側に黒い帯が含まれる場合があるためです。 ②は、LINEでアップロードすると、画像サイズが縮小されるためです。 ③は、そのまま文字認識すると、不要な文字がたくさん抽出されるし、時間もかかるためです。 ⑤抽出した文字が、どの範囲から抽出された文字列なのかを特定します。 api/controllers/tsundete/textdetection.js 'use strict'; const vision = require('@google-cloud/vision'); const sharp = require('sharp'); //const fs = require('fs').promises; class TextDetection{ constructor(){ this.client = new vision.ImageAnnotatorClient(); } async detection(target_buffer, template){ try { // ①ベースとなる文字列を探して、Y方向の位置を特定します。 var sample = await sharp(target_buffer); var metadata = await sample.metadata(); console.log(metadata); var ratio = metadata.width / template.image_width; console.log("ratio=" + ratio); const positionMask = Buffer.from( `<svg width="${metadata.width}" height="${metadata.height}" > <rect x="${Math.round(template.base_range.x * ratio)}" y="0" width="${Math.round(template.base_range.width * ratio)}" height="${metadata.height}" /> </svg>` ); var sample_buffer = await sample .sharpen(20) .composite([{ input: positionMask, blend: 'dest-in', top: 0, left: 0 }]) .png() .toBuffer(); const [position_result] = await this.client.textDetection(sample_buffer); var position_allResult = parse_paragraph(position_result.fullTextAnnotation, template); var base_position = position_allResult.find(item => item.str == template.base_range.value); if (!base_position) { console.log('not found'); return []; } var offset = Math.round((base_position.max_y + base_position.min_y) / 2 / ratio - (template.base_range.y + template.base_range.height / 2)); console.log("offset=" + offset); // ②文字列の範囲を抽出するためのマスクを生成します。 const targetMask = make_mask(template.ranges, offset, template); // var range = await sharp(targetMask).png().toBuffer(); // fs.writeFile("./range.png", range); var masked_buffer = await sharp(target_buffer) .resize(template.image_width) // ③アスペクト比を保ったまま幅をテンプレートに合わせます。 .sharpen(20) .composite([{ input: targetMask, blend: 'dest-in', top: 0, left: 0 }]) .png() .toBuffer(); // fs.writeFile("./result.png", masked_buffer); // ④Google Cloud Vision APIで文字認識して文字列を抽出します。 const [result] = await this.client.textDetection(masked_buffer); var allResult = parse_paragraph(result.fullTextAnnotation, template); // ⑤抽出した文字が、どの範囲から抽出された文字列なのかを特定します。 allResult.forEach(item => { var found = template.ranges.find(range => { var center_x = Math.round((item.min_x + item.max_x) / 2); var center_y = Math.round((item.min_y + item.max_y) / 2); return (center_x >= range.x && center_x <= (range.x + range.width)) && (center_y >= (range.y + offset) && center_y <= (range.y + offset + range.height)) }); if (found) item.range = found }); console.log("allResult=", JSON.stringify(allResult)); return allResult; } catch (error) { console.error(error); throw error; } } } function make_mask(ranges, offset, template) { var svg = `<svg width="${template.image_width}" height="${template.image_height}">`; for (var range of ranges) { svg += `<rect x="${range.x}" y="${range.y + offset}" width="${range.width}" height="${range.height}" />` } svg += `</svg>`; return Buffer.from(svg); } function parse_paragraph(annotation, template) { if (!annotation || !annotation.pages || annotation.pages.length < 1) return []; var result = []; var blocks = annotation.pages[0].blocks; for (var block of blocks) { for (var para of block.paragraphs) { var str = ""; var min_x = template.image_width, max_x = -1, min_y = template.image_height, max_y = -1; for (var word of para.words) { for (var symbol of word.symbols) { str += symbol.text; for (var vertices of symbol.boundingBox.vertices) { if (vertices.x < min_x) min_x = vertices.x; if (vertices.x > max_x) max_x = vertices.x; if (vertices.y < min_y) min_y = vertices.y; if (vertices.y > max_y) max_y = vertices.y; } } } if (str != "") result.push({ str, min_x, min_y, max_x, max_y }); } } return result; } module.exports = new TextDetection(); Google Cloud Vision APIの呼び出しには、課金の有効化とAPIの有効化をし、さらにサービスアカウントのクレデンシャルの作成が必要です。環境変数または.envファイルに以下の記載が必要です。 .env GOOGLE_APPLICATION_CREDENTIALS="【Google Cloud Vision APIのクレデンシャルファイル】" 詳細は以下を参照してください。  https://cloud.google.com/vision/docs/setup?hl=ja 以下のnpmモジュールを使っています。 @google-cloud/vision lovell/sharp ちなみに、文字認識は、ロゴっぽいアルファベット(芝のランクなど)はうまく検出できませんでした。 投稿の様子 LINEアプリから投稿するとこんな感じです。 その他 LINEメッセージとして、画像ではなくメッセージを受信したら、OK Googleと同様の応答を返すようにしています。 「googleassistant.js」に実装をまとめてあります。 おまけで、美琴妹の語尾もくっつけています。以下、ご参考まで。  ツンデレのGoogleアシスタントをLINEボットにする 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウマ娘で初めてランクAがでたのでLINEボットにスクリーンショットを投稿してみた

タイトルの通りですが、複数の処理を連携させていまして、結局やってることは、 ①ウマ娘プリティーダービーで、ウマ娘のプロファイルを画面キャプチャし、それをLINEメッセージとして投稿 ②取得した画像から、Google Cloud Vision APIでOCR(文字認識)して、ステータス値を判別 ③ステータス値をLINEメッセージのレスポンスとして返す 大事なのは②です。すなわち、以下の2つです。 ・LINEメッセージから画像ファイルを取得 ・Google Cloud Vision APIで文字認識 ソースコードもろもろは以下に上げておきます。 poruruba/CloudVision_Test LINEメッセージから画像ファイルを取得 以下の部分です。 api/controllers/tsundete/index.js const { streamToBuffer } = require('@jorgeferrero/stream-to-buffer'); var stream = await client.getMessageContent(event.message.id); var buffer = await streamToBuffer(stream); 通知されるメッセージには直接は画像ファイルは含まれず、IDが知らされるので、client.getMessageContent を呼び出して取得します。取得するとStream形式で取得されるため、有志のnpmモジュールを使ってバッファに変換しています。 上記のclientは以下で作ったものです。 const config = { channelAccessToken: '【LINEチャネルアクセストークン】', channelSecret: '【LINEチャネルシークレット】', }; const line = require('@line/bot-sdk'); client = new line.Client(config); LINEのnpmモジュールを使っています。 line/line-bot-sdk-nodejs steamToBufferは、以下のnpmモジュールを使わせていただきました。 @jorgeferrero/stream-to-buffer LINEに関する処理はユーティリティ「line-utils.js」にまとめてあります。また、LINEボットの設定やシークレット情報の取得も必要です。 詳細は以下が参考になります。  LINEボットを立ち上げるまで。LINEビーコンも。 Google Cloud Vision APIで文字認識 「textdetection.js」に処理をまとめておきました。 あらかじめ、手動で画像から抽出したい文字列の場所(X,Y座標と幅と高さ)のリストを決めておき、テンプレートとします。 テンプレートというのはこんな感じの情報です。 const template = { name: "ウマ娘", image_width: 1080, image_height: 2220, base_range: { x: 417, y: 219, width: 251, height: 55, name: "タイトル", value: "ウマ娘詳細" }, ranges: [ { x: 521, y: 406, width: 529, height: 42, name: "馬名" }, { x: 129, y: 546, width: 176, height: 36, name: "評価" }, { x: 115, y: 665, width: 117, height: 40, name: "スピード" }, { x: 316, y: 665, width: 117, height: 40, name: "スタミナ" }, { x: 516, y: 665, width: 117, height: 40, name: "パワー" }, { x: 716, y: 665, width: 117, height: 40, name: "根性" }, { x: 916, y: 665, width: 117, height: 40, name: "賢さ" }, ], }; ①ベースとなる文字列(base_range.value)を探して、Y方向の位置を特定します。 ②文字列の範囲(ranges)を抽出するためのマスクを生成します。 ③アスペクト比を保ったまま幅をテンプレートに合わせます。 ④Google Cloud Vision APIで文字認識して文字列を抽出します。 ⑤抽出した文字列を、テンプレートの場所とマッピングします。 ①は、スマホによっては、ノッチの有無によって上側に黒い帯が含まれる場合があるためです。 ②は、LINEでアップロードすると、画像サイズが縮小されるためです。 ③は、そのまま文字認識すると、不要な文字がたくさん抽出されるし、時間もかかるためです。 ⑤抽出した文字が、どの範囲から抽出された文字列なのかを特定します。 api/controllers/tsundete/textdetection.js 'use strict'; const vision = require('@google-cloud/vision'); const sharp = require('sharp'); //const fs = require('fs').promises; class TextDetection{ constructor(){ this.client = new vision.ImageAnnotatorClient(); } async detection(target_buffer, template){ try { // ①ベースとなる文字列を探して、Y方向の位置を特定します。 var sample = await sharp(target_buffer); var metadata = await sample.metadata(); console.log(metadata); var ratio = metadata.width / template.image_width; console.log("ratio=" + ratio); const positionMask = Buffer.from( `<svg width="${metadata.width}" height="${metadata.height}" > <rect x="${Math.round(template.base_range.x * ratio)}" y="0" width="${Math.round(template.base_range.width * ratio)}" height="${metadata.height}" /> </svg>` ); var sample_buffer = await sample .sharpen(20) .composite([{ input: positionMask, blend: 'dest-in', top: 0, left: 0 }]) .png() .toBuffer(); const [position_result] = await this.client.textDetection(sample_buffer); var position_allResult = parse_paragraph(position_result.fullTextAnnotation); var base_position = position_allResult.find(item => item.str == template.base_range.value); if (!base_position) { console.log('not found'); return []; } var offset = Math.round((base_position.max_y + base_position.min_y) / 2 / ratio - (template.base_range.y + template.base_range.height / 2)); console.log("offset=" + offset); // ②文字列の範囲を抽出するためのマスクを生成します。 const targetMask = make_mask(template.ranges, offset, template); // var range = await sharp(targetMask).png().toBuffer(); // fs.writeFile("./range.png", range); var masked_buffer = await sharp(target_buffer) .resize(template.image_width) // ③アスペクト比を保ったまま幅をテンプレートに合わせます。 .sharpen(20) .composite([{ input: targetMask, blend: 'dest-in', top: 0, left: 0 }]) .png() .toBuffer(); // fs.writeFile("./result.png", masked_buffer); // ④Google Cloud Vision APIで文字認識して文字列を抽出します。 const [result] = await this.client.textDetection(masked_buffer); var allResult = parse_paragraph(result.fullTextAnnotation); // ⑤抽出した文字が、どの範囲から抽出された文字列なのかを特定します。 allResult.forEach(item => { var center_x = Math.round((item.min_x + item.max_x) / 2); var center_y = Math.round((item.min_y + item.max_y) / 2); var found = template.ranges.find(range => { return (center_x >= range.x && center_x <= (range.x + range.width)) && (center_y >= (range.y + offset) && center_y <= (range.y + offset + range.height)) }); if (found) item.range = found }); console.log("allResult=", JSON.stringify(allResult)); return allResult; } catch (error) { console.error(error); throw error; } } } function make_mask(ranges, offset, template) { var svg = `<svg width="${template.image_width}" height="${template.image_height}">`; for (var range of ranges) { svg += `<rect x="${range.x}" y="${range.y + offset}" width="${range.width}" height="${range.height}" />` } svg += `</svg>`; return Buffer.from(svg); } function parse_paragraph(annotation) { if (!annotation || !annotation.pages || annotation.pages.length < 1) return []; var result = []; var blocks = annotation.pages[0].blocks; for (var block of blocks) { for (var para of block.paragraphs) { var str = ""; var min_x = Number.MAX_SAFE_INTEGER, max_x = -1, min_y = Number.MAX_SAFE_INTEGER, max_y = -1; for (var word of para.words) { for (var symbol of word.symbols) { str += symbol.text; for (var vertices of symbol.boundingBox.vertices) { if (vertices.x < min_x) min_x = vertices.x; if (vertices.x > max_x) max_x = vertices.x; if (vertices.y < min_y) min_y = vertices.y; if (vertices.y > max_y) max_y = vertices.y; } } } if (str != "") result.push({ str, min_x, min_y, max_x, max_y }); } } return result; } module.exports = new TextDetection(); Google Cloud Vision APIの呼び出しには、課金の有効化とAPIの有効化をし、さらにサービスアカウントのクレデンシャルの作成が必要です。環境変数または.envファイルに以下の記載が必要です。 .env GOOGLE_APPLICATION_CREDENTIALS="【Google Cloud Vision APIのクレデンシャルファイル】" 詳細は以下を参照してください。  https://cloud.google.com/vision/docs/setup?hl=ja 以下のnpmモジュールを使っています。 @google-cloud/vision lovell/sharp ちなみに、文字認識は、ロゴっぽいアルファベット(芝のランクなど)はうまく検出できませんでした。 投稿の様子 LINEアプリから投稿するとこんな感じです。 その他 LINEメッセージとして、画像ではなくメッセージを受信したら、OK Googleと同様の応答を返すようにしています。 「googleassistant.js」に実装をまとめてあります。 おまけで、美琴妹の語尾もくっつけています。以下、ご参考まで。  ツンデレのGoogleアシスタントをLINEボットにする 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js with TypeScript】propsでObjectを渡すときの型の指定方法

概要 propsで親componentから子componentにObjectを渡すときの型を、interfaceで指定する方法。 方法 type: Object as () => SampleType, のようにinterfaceを返すメソッドを指定する。 実装例 <template> <div id="sample-button"> <button class="m-3 btn btn-sm btn-primary" @click="clickChildComponent"> SampleButton </button> </div> </template> <script lang="ts"> import Vue from "vue"; import SampleType from "@/types/SampleType"; export default Vue.extend({ name: "sample", props: { sample: { // SampleTypeというinterfaceをメソッドにして指定する type: Object as () => SampleType, required: true, } }, methods: { clickChildComponent(): void { console.log(this.sample); } }, }); </script> 参考 Using a Typescript interface or type as a prop type in VueJS Vue.jsでObject型のPropsにTypeScriptの型を割り当てる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LambdaでVPC内にあるEC2にmysqldumpを実行しS3にアップロードする

やりたいこと AWSのLambdaを使って、EC2上のMySQLのdumpを取得後、S3にアップロードする。 ポイント VPC内にあるEC2とVPCの外にあるS3のこの両方にアクセスが必要 というのがハマりポイントでした。 (これまでLambdaを使ったことなかったのであまりわかってなく、簡単にいけるだろうと思って、何も考えずにやってそれ以外の箇所でも結構ハマりました) 構成図 最終的にはこのようになります。 手順 1.Lambda Functionを作る コンソールからLambdaを選択肢、Create Functionで作る。 - Runtimeは、今回Node.jsを選択します。(ここはお好みで) - Roleは今回新しくLambda用に作成する形にしています。 2.Lambda スクリプトを実装する 2-1.スクリプト実装 index.js 'use strict'; const aws = require('aws-sdk'); const mysqldump = require('mysqldump'); const fs = require('fs'); const s3 = new aws.S3({ apiVersion: '2006-03-01' }); require("date-utils"); var date = new Date(); var timeStamp = date.toFormat('YYYYMMDDHH24MISS'); const dumpFileName = 'dump_' + timeStamp + '.sql'; const dumpPath = '/tmp/' + dumpFileName; exports.handler = async (event, context) => { console.log("start mysqldump"); console.log('dumpFileName:' + dumpFileName); const resultDump = await mysqldump({ connection: { host: process.env.DBHOST, user: process.env.DBUSER, password: process.env.DBPASS, database: process.env.DBNAME, }, dumpToFile: dumpPath, // Lambda上のサーバにdumpファイルを一時的に置く場所 }); console.log("start s3 upload"); var params = { Bucket: process.env.BUCKETNAME, Key: process.env.BUCKETKEY + dumpFileName, Body: fs.readFileSync(dumpPath) }; var resultPutS3 = await s3.putObject(params).promise(); console.log(resultPutS3); } 2-2.環境変数設定 上記スクリプト内で process.env.xxxとなっていた箇所です。 Configuration → Enviroment variablesから設定できます。 ※Dateのタイムゾーンは日本にしておきたいため、TZでセットしています。(ここは自身の環境に合わせてTZをセットすると良いと思います) 実行してみる Codeから、Deployを実施したのち、Testを選択すると実行できます。 結果 Runtime.ImportModuleError が発生します 原因 awsのライブラリ以外は、ローカルからアップロードする必要があるため (Lambda初心者の私はこれを知らなかったため、最初はなぜエラーなのかわかりませんでした。。) 2-3. スクリプトに必要なライブラリをアップロード 2-3-1.ローカルでスクリプトとライブラリをZIPで固めたものを作成する Lambda上のファイルのコピーをlocalに作成します。 -rw-rw-r--@ 1 yukiyoshimura admin 1090 9 12 14:19 index.js [yukiyoshimura sample]$ aws以外のライブラリをnpmでダウンロードします。 npm install mysqldump npm install date-utils zipで固める [yukiyoshimura sample]$ zip -r ../sample.zip ./ 2-3-2.lambdaへアップロードする 以下からアップロードします アップロード後 node_modulesディレクトリに必要なライブラリが入った状態でlambdaコンソール上で確認できればOK⬇︎ 再び実行してみる Time out するけど、 Runtime.ImportModuleError が解消されていれば一旦OKです。 3.LambdaがEC2にアクセスできるようにする 3-1. Lambdaに紐づけているroleに権限を付与する Configuration → permissions 以下をセットしておきます。 AWSLambdaVPCAccessExecutionRole AmazonS3FullAccess 3-2. lambdaをVPCに登録する Configuration → VPCを選択 VPCは、アクセスしたいEC2のVPCと同じものをセットします。 Subnetは、Lambda用にPrivate Subnetを作ったのでそれをセットしました。 Security groupsは、デフォルトのものをそのままセットします。(インバウンドなし、アウトバウンドAll) 4.EC2に対してmysqldumpができるようにする EC2に関連付けしているSecurity groupのインバウンドに以下を追加する。 Sourceのところは、今回、LambdaでセットしたSecurity Groupと同じものをセットしました。 5.VPCに登録したlambdaからS3にアクセスできるようにする S3アクセス用のVPC Endpointの作成 VPC → Endpoints → Create Endpoint S3のエンドポイントを作成します。今回は、Gatewayで作成しています。 VPCは、EC2と同じもの。 routetablesは、Lambdaでセットしたsubnetが紐づいているものを指定する。 https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/vpce-gateway.html endpointを作成すると、指定したroutetablesにS3へのendpointが追加されます。 その他 lambdaのタイムアウトはデフォルト3秒なので10秒くらいにしておくと良い ネットワークのエラーでタイムアウトなのか、処理時間でのタイムアウトなのか見分けがつかない時がありました。 今回のdumpしてからS3へのアップロードだと3秒では終わらなかったので10秒にしました。 Configuration → General configuraton から変更できます。 MySQLに外から繋ぐため、外部からの接続を許可しておく必要があります この辺りを参考に。 自分はlambda接続用のユーザを作成しました。 https://qiita.com/yoshiokaCB/items/df4ae185be7cbc4f03ac まとめ VPC内へのアクセスと外のアクセスが必要だったため、色々設定が必要でしたが、何とかできました。 VPC Endopointを利用する以外にもNATゲートウェイを使う方法もあるようです。 参考にしたページ https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html https://devlog.arksystems.co.jp/2018/04/04/4807/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

廃棄PCのantix上にrubyonrails環境構築テスト

rubyonrails-test 家の廃棄予定のノートPC(Pentium)にantixをいれて、rubyonrails+node.js環境構築のテスト rubyは新しいものがいれられなかったため、2.6.3をビルドしている 結論:nodeがうまくいれられずに断念 環境構築 mac VSCode拡張 Ruby HTML Snippets Rubyのインストール # update brew update # rbenv brew install rbenv ruby-build echo 'eval "$(rbenv init -)"' >> ~/.bash_profile source ~/.bash_profile # readline brew install readline brew link readline --force # ruby RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)" # install ruby rbenv install 2.6.3 # use ruby rbenv global 2.6.3 rbenv rehash # check ruby version ruby -v 環境構築 antiX antiX install 基本デフォルトでインストール CDにやいてCDからブートcli-installer 参考 https://antixlinux.com/forum-archive/install-using-the-cli-installer-t3041.html 時刻設定だけ東京に変更 設定終わってログインしたらsudo apt update && sudo apt upgrade grubの更新がされているので全てのディスクにインストールをする(BIOSありの場合) 必要あればネットワークとかの設定を行うantiX-cli-cc 環境構築 antiX rbenv ruby gem bundler sudo apt update sudo apt install -y gcc make bzip2 git libssl-dev libreadline-dev zlib1g-dev sudo apt update git clone https://github.com/rbenv/rbenv.git ~/.rbenv echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(rbenv init -)"' >> ~/.bashrc # reload bash source ~/.bashrc mkdir -p "$(rbenv root)/plugins" # rbenv プラグインをインストール git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)/plugins/ruby-build" # rbenv install 後に ~/.rbenv/default-gems に記載の gem をインストールしてくれる git clone https://github.com/rbenv/rbenv-default-gems.git "$(rbenv root)/plugins/rbenv-default-gems" echo bundler > "$(rbenv root)/default-gems" # $ rbenv update すると、~/.rbenv/plugins 以下を全部 git pull してくれる ruby-build も対象となる git clone https://github.com/rkh/rbenv-update.git "$(rbenv root)/plugins/rbenv-update" sudo apt update rbenv install 2.6.3 rbenv global 2.6.3 rbenv rehash # check version ruby -v gem -v bundler -v node.js npm # get setup.sh # execute setup.sh curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash source ~/.profile curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update && sudo apt-get install yarn
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】指定のビット位置にデータを設定する方法

はじめに 特定のビット位置にデータを設定したいと思った時に、どのようにコードを書けば良いでしょうか? 例えば、1バイトデータの中で6ビット目に1という値を入れたいというケースです(以下にデータを記載) 00010000 ↑5ビット目に1を設定する ※Big Endian このように特定のビット位置にデータを設定したい場合の方法を試行錯誤したので、その記録を残したいと思います。 特定のビット位置にデータを設定する方法 特定のビット位置にデータを設定する方法として挙げられるのが、 シフト演算子(<<)を利用する方法 です。 例えば、5ビット目に1を設定したい場合は、0x01データに対して4ビット分だけ左シフトすれば良いです。以下に実際のコードを載せています。 // 5bit目に1を設定したいので、4ビット(5-1)分だけ左シフトする const bit = 0x01 << (5 - 1); // bit: 16(=0b00010000) 複数のビット位置にデータを設定する方法 次に、複数のビット位置にデータを設定する方法を考えます。 例えば、以下のように5ビット目と7ビット目にそれぞれ1を設定する場合です。 01010000 ↑ ↑ 5と7ビット目に1を設定したい 複数のビット位置にデータを設定する場合は、シフト演算子(<<)に加えて、 ビット論理和(|) を利用すれば実現できます。以下に実際のコードを載せています。 let bit = 0x00; // 5ビット目に1を設定したデータと、論理和を計算する bit = bit | 0x01 << (5 - 1); // 7ビット目に1を設定したデータと、論理和を計算する bit = bit | 0x01 << (7 - 1); // > bit: 80(=0b01010000) ビット論理和は、前後のデータの各ビット位置を比較し、どちらか一方のビットが1である場合に1、それ以外は0を返す演算子です。 今回の計算を図で表すと以下の通りです。 5ビット位置に1を設定する bit = bit | 0x01 << (5 - 1) 0b00000000 // bit 0b00010000 // 0x01 << (5 - 1) ----------- 0b00010000 // ビット論理和の結果(bit | 0x01 << (5 - 1)) 7ビット位置に1を設定する bit = bit | 0x01 << (7 - 1) 0b00010000 // bit (=上述で5ビット目に1を設定した結果) 0b01000000 // 0x01 << (7 - 1) ----------- 0b01010000 // ビット論理和の結果(bit | 0x01 << (7 - 1)) ビット論理和を用いることで前の計算結果を利用できるため、複数のビット位置に値を設定することができるようになります。 参考文献 JavaScript | ビット演算子/シフト演算子 | Let'sプログラミング
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EJS

Node.jsの学習の備忘録と振り返りの記事です。 何かの参考になれば幸いです。 付け足しや訂正などある場合ご教授いただけると 大変嬉しく思います!! EJSとは HTMLとJavaScriptのコード両方を記述できるNode.jsのパッケージ インストール npmからインストールnpm install ejs JavaScript利用 <% %>または<%= %>でかこう <% %>:ブラウザに何も表示されない(変数の定義などに使用) <%= %>:ブラウザに表示される <%const items = [ {id:1,name:'じゃがいも'}, {id:2,name:'にんじん'}, {id:3,name:'玉ねぎ'}, ];%> <ul> <% items.forEach((item) => {%> <li> <span><% = item.id %></span> <span><% = item.name %></span> </li> <%]);%> </ul>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.js実践編(Markdown導入編)

準備 以下のコマンドで必要なパッケージをインストールします。 npm install markdown-it 1から作る場合は以下のパッケージをインストールします。 npm install express-session npm install express-validator npm install sqlite3 npm install knex npm install bookshelf npm install markdown-it モデル作成 以下のコマンドでモデルを作成します。 npx sequelize-cli model:generate --name Markdata --attributes userId:integer,title:string,content:text モデルファイルを修正、追記します。 models/markdata.js 'use strict'; module.exports = (sequelize, DataTypes) => { const Markdata = sequelize.define('Markdata', { userId: { type: DataTypes.INTEGER, validate: { notEmpty: { msg: "利用者は必須です。" } } }, title: { type: DataTypes.STRING, validate: { notEmpty: { msg: "タイトルは必須です。" } } }, content: { // 長さの制限無し type: DataTypes.TEXT, validate: { notEmpty: { msg: "コンテンツは必須です。" } } } }, {}); Markdata.associate = function(models) { Markdata.belongsTo(models.User); }; return Markdata; }; models/users.js User.associate = function(models) { // 主モデル User.hasMany(models.Board); // 以下を追記 User.hasMany(models.Markdata); }; 以下のコマンドでマイグレーションを行います。 npx sequelize-cli db:migrate ルーティング作成 以下のファイルを作成します。 routes/marks.js const express = require('express'); const router = express.Router(); const db = require('../models/index'); const { Op } = require("sequelize"); const MarkdownIt = require('markdown-it'); const markdown = new MarkdownIt(); const pnum = 10; // ログインのチェック function check(req, res) { if (req.session.login == null) { // ログイン後に戻る値 req.session.back = '/md'; res.redirect('/users/login'); return true; } else { return false; } } // トップページ router.get('/', (req, res, next) => { if (check(req, res)){ return }; db.Markdata.findAll({ where:{userId: req.session.login.id}, limit:pnum, order: [ ['createdAt', 'DESC'] ] }).then(mds => { var data = { title: 'Markdown Search', login: req.session.login, message: '※最近の投稿データ', form: {find:''}, content: mds }; res.render('md/index', data); }); }); // 検索フォームの送信処理 router.post('/', (req, res, next) => { if (check(req, res)){ return }; db.Markdata.findAll({ where:{ userId: req.session.login.id, content: {[Op.like]:'%' + req.body.find + '%'}, }, order: [ ['createdAt', 'DESC'] ] }).then( mds => { var data = { title: 'Markdown Search', login: req.session.login, message: '※"' + req.body.find + '" で検索された最近の投稿データ', form: req.body, content:mds }; res.render('md/index', data); }); }); // 新規作成ページの表示 router.get('/add', (req, res, next) => { if (check(req, res)){ return }; res.render('md/add', { title: 'Markdown/Add' }); }); // 新規フォームの送信処理 router.post('/add', (req, res, next) => { if (check(req, res)){ return }; db.sequelize.sync() .then(() => db.Markdata.create({ userId: req.session.login.id, title: req.body.title, content: req.body.content, }) .then(model => { res.redirect('/md'); }) ); }); // '/mark'へアクセスした際のリダイレクト router.get('/mark', (req, res, next) => { res.redirect('/md'); return; }); // 指定IDのMarkdata表示 router.get('/mark/:id', (req,res, next) => { if (check(req, res)){ return }; db.Markdata.findOne({ where: { id: req.params.id, userId: req.session.login.id }, }) .then((model) => { makepage(req, res, model, true); }); }); // Markdataの更新処理 router.post('/mark/:id', (req, res, next) => { if (check(req, res)){ return }; db.Markdata.findByPk(req.params.id) .then(md => { md.content = req.body.source; md.save().then((model) => { makepage(req, res, model, false); }); }) }); // 指定IDのMarkdaraの表示ページ作成 function makepage(req, res, model, flg) { var footer; if (flg){ var d1 = new Date(model.createdAt); var dstr1 = d1.getFullYear() + '-' + (d1.getMonth() + 1) + '-' + d1.getDate(); var d2 = new Date(model.updatedAt); var dstr2 = d2.getFullYear() + '-' + (d2.getMonth() + 1) + '-' + d2.getDate(); footer = '(created: ' + dstr1 + ', updated: ' + dstr2 + ')'; } else { footer = 'UPdating date and time information...' } var data = { title: 'Markdown', id: req.params.id, head: model.title, footer: footer, content: markdown.render(model.content), source: model.content }; res.render('md/mark', data); } module.exports = router; app.jsに登録します。 app.js var marksRouter = require('./routes/marks'); app.use('/md', marksRouter); テンプレート作成 以下のファイルを作成します。 views/md/index.ejs <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"> <title><%= title %></title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body class="container"> <header> <h1 class="display-4"> <%= title %> </h1> </header> <div role="main"> <p class="h5 my-4">Hi, <span><%= login.name %></span>!<br> Welcome to <%= title %>. </p> <form action="/md" method="POST"> <div class="form-group"> <label for="find">FIND</label> <input type="text" name="find" id="find" value="<%= form.find %>" class="form-control"> </div> <input type="submit" value="検索" class="btn btn-primary"> </form> <p class="my-4 h5"><%= message %></p> <table class="table"> <% for (var i in content) { %> <% var ob = content[i]; %> <tr> <td> <a href="/md/mark/<%= ob.id %>" class="text-dark"> <%= ob.title %> </a> </td> </tr> <% } %> </table> <p> </p> <p><a href="/md/add">※データを登録</a></p> </div> </body> </html> views/md/add.ejs <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"> <title><%= title %></title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body class="container"> <header> <h1 class="display-4 text-primary"> <%= title %> </h1> </header> <div role="main"> <form action="/md/add" method="post"> <div class="form-group"> <label>TITLE</label> <input type="text" name="title" id="title" class="form-control"> </div> <div class="form-group"> <label>CONTENT</label> <textarea name="content" id="content" rows="10" class="form-control"></textarea> </div> <input type="submit" value="送信" class="btn btn-primary"> </form> <p class="mt-4"><a href="/md">&lt;&lt; Top へ戻る</a></p> </div> </body> </html> views/md/mark.ejs <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"> <title><%= title %></title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body class="container"> <header> <h1 class="display-4"> <%= title %> </h1> </header> <div role="main"> <p class="h5"><%= head %></p> <form action="/md/mark/<%=id %>" method="post"> <div class="form-group"> <label for="source">SOURCE</label> <textarea name="source" id="source" rows="5" class="form-control"><%= source %></textarea> </div> <input type="submit" value="更新" class="btn btn-primary"> </form> <div class="card mt-4"> <div class="card-header text-center h5"> Preview </div> <div class="card-body"> <%- content %> </div> <div class="card-footer text-muted text-right"> <%= footer %> </div> </div> <p class="mt-4"><a href="/md">&lt;&lt; Top へ戻る</a></p> </div> </body> </html> 結果 ログイン画面 Markdown一覧 Markdown追加 Markdown更新
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nest.jsでGraphQL + Passportを使って認証処理を作る。

Nest.jsで認証のライブラリであるPassportを使用する方法は公式ドキュメントに載っており、GraphQLでの実装も参考程度に乗っています。 しかし、GraphQLでの実装はあくまでも参考程度なので、これだけでは動作せることができません。 今回は実際にGraphQLでusernameとpasswordでの認証ができるところまで実装してみます。 1. ベースプロジェクトの作成 下記コマンドでNest.jsのプロジェクトを作成します。 $ npm i -g @nestjs/cli $ nest new passport-sample $ cd passport-sample プロジェクト作成後、Authentication requirementsの手順に沿って、必要なライブラリをインストールします。 2. モジュールの作成 Implementing Passport strategies に沿って、passportに必要なサンプルのモジュールの作成と各種ロジックをサンプル実装をします。 ここでは下記のファイルが作成されます。 users/users.service.ts users/users.module.ts auth/auth.service.ts auth/auth.module.ts 3. LocalStrategyの作成 認証のためのバリデーションを実行するLocalStrategyをImplementing Passport localに沿って実装します。 LocalStrategyは下記の様になります。 auth/local.strategy.ts import { Strategy } from 'passport-local'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { AuthService } from './auth.service'; @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(private authService: AuthService) { super(); } async validate(username: string, password: string): Promise<any> { const user = await this.authService.validateUser(username, password); if (!user) { throw new UnauthorizedException(); } return user; } } 手順に沿ってAuthModuleも更新します。 4. LocalAuthGuardの作成 公式ドキュメントでは、LocalAuthGuardは下記の様になっています。 auth/local-auth.guard.ts import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class LocalAuthGuard extends AuthGuard('local') {} しかし、この実装だとGraphQLから渡されるパラメーターを正しくLocalStrategyに渡せていないため、認証に失敗してしまいます。 そのため、GraphQLでこのLocalAuthGuardを実装するには、以下のようにgetRequestメソッドでパラメーターをマッピングしてあげる必要があります。 auth/local-auth.guard.ts import { Injectable, ExecutionContext } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { GqlExecutionContext } from '@nestjs/graphql'; @Injectable() export class LocalAuthGuard extends AuthGuard('local') { getRequest(context: ExecutionContext) { const ctx = GqlExecutionContext.create(context); const gqlReq = ctx.getContext().req; if (gqlReq) { gqlReq.body = ctx.getArgs(); return gqlReq; } return context.switchToHttp().getRequest(); } } 5. MutaionにLocalAuthGuardをセットする まずはUser情報を返却するためのModelを定義します。 users/models/user.model.ts import { Field, Int, ObjectType } from '@nestjs/graphql'; @ObjectType() export class User { @Field(type => Int) userId: number; @Field() username: string; } app.resolver.tsにloginのMutationを作成し、LocalAuthGuardをセットします。 app.resolver.ts import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Resolver, Mutation, Args } from '@nestjs/graphql'; import { LocalAuthGuard } from './auth/local-auth.guard'; import { User } from './users/models/user.model'; @Resolver() export class AppResolver { @UseGuards(LocalAuthGuard) @Mutation(() => User) async login( @Request() req, @Args({ name: 'username', type: () => Int }) _: number, @Args({ name: 'password' }) _: string ): Promise<User> { return req.user } } これで、GraphQlでPassportを使った認証処理が動く様になります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む