20190417のNode.jsに関する記事は6件です。

koaでサーバ開発クイックスタート

Node.js の lightweight web framework の一つに koa というフレームワークがあります。
koa でサーバを開発するときの(私がよく使う)主要なライブラリと使い方を軽くまとめました。

目次

  • koa
  • koa-router
  • koa-static
  • @koa/cors
  • koa-bodyparser

koa

koa 本体です。執筆時点でバージョン 2.7.0 です。

Install

npm i koa

Usage

const Koa = require('koa');
const app = new Koa();
app.use(await ctx => {
  ctx.body = 'hello';
});
app.listen(3000); // port 3000 でリッスン開始

koa-router

URL のパスによって処理を分岐させるミドルウェアです。

Install

npm i koa-router

Usage

const Koa = require('koa');
const Router = require('koa-router');
const router = new Router();
router.get('/a', await (ctx, next) => {
  ctx.body = '/a にアクセスされたよ';
});
router.get('/b', await (ctx, next) => {
  ctx.body = '/b にアクセスされたよ';
});
router.get('/b/:id', await (ctx, next) => {
  ctx.body = `/b/${ctx.params.id} にアクセスされたよ`;
});
const app = new Koa();
app
  .use(router.routes())
  .use(router.allowedMethods());
app.listen(3000);

koa-static

静的ファイルをホストするミドルウェアです。

Install

npm i koa-static

Usage

const Koa = require('koa');
const serve = require('koa-static');
const app = new Koa();
app.use(serve('./public')); // 公開したいディレクトリを指定
app.listen(3000);

./public/nice.png というファイルがあれば http://localhost:3000/nine.png で取得できる。

@koa/cors

CORS (Cross-Origin Resource Sharing) のためのミドルウェアです。

Install

npm i @koa/cors@2

Usage

const Koa = require('koa');
const cors = require('@koa/cors');
const app = new Koa();
app.use(cors());
app.use(await ctx => {
  ctx.body = 'hello';
});
app.listen(3000);

koa-bodyparser

HTTP リクエストの body をパースするミドルウェアです。
ざっくりいうと、 POST したときのデータを読むために使います。

Install

npm i koa-bodyparser

Usage

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser());
app.use(await ctx => {
  const body = ctx.request.body; // パースされたものがここに入る
  ctx.body = body;
});
app.listen(3000);

おわり

増えたら適宜加筆します。

よき koa ライフを。

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

Node.jsを使って本田とじゃんけんする

本田とじゃんけんとは

ペプシジャパンが急に始めた企画である。特定のツイートをすると本田とじゃんけんでき、勝つとペプシコーラがもらえるらしい。

https://www.pepsi.co.jp/campaign/cvs/

本田の勝率

は?

本田とじゃんけんする方法

当初はペプシ公式のツイートにあるボタンを押さなければいけないと思っていたのだが、どうやらペプシジャパンへのリプライにしてハッシュタグが入っていれば反応するようだ。

というわけで、結論としては以下のツイートを投稿すれば本田とじゃんけんできることになる。

@pepsi_jpn #本田とじゃんけん #本田に〇〇で勝つ

Node.jsで本田とじゃんけんするには?

Twitter APIでもいいですが、APIキーがないと使えないので今回はpuppeteerを使って本田とじゃんけんをしていきたいと思います。

puppeteerはヘッドレスブラウザでChromeを弄れるすごいやつです。

ググると色々情報が出てくるので、詳しい情報は他の方の記事で見てみてください。

ソースコード

リポジトリ

const puppeteer = require('puppeteer');
require('dotenv').config();

async function main() {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  try {
    await login(page);
    await tweetPepsi(page)
  } catch (error) {
    console.log(error);
  } finally {
    browser.close();
  }
}

async function login(page) {
  const URL = 'https://twitter.com/login';

  await page.goto(URL, {waitUntil: "domcontentloaded"});

  // ログイン情報入力、ログインボタン押下
  await page.evaluate((id, password) => {
    document.querySelector('.js-username-field').value = id;
    document.querySelector('.js-password-field').value = password;
    document.querySelector('.js-signin').submit();
  }, process.env.ACCOUNT_ID, process.env.ACCOUNT_PASSWORD);

  await page.waitForNavigation();

  console.log('✨  ログイン成功');
}

async function tweetPepsi(page) {
  const tweetText = '@pepsi_jpn #本田とじゃんけん #本田にパーで勝つ';

  // ツイートモーダル開く
  await page.evaluate(({}) => {
    document.querySelector('#global-new-tweet-button').click();
  },{});

  await page.waitFor(500);

  // ツイート文入力
  await page.evaluate((tweetText) => {
    document.querySelector('#Tweetstorm-tweet-box-0 > div.tweet-box-content > div.tweet-content > div.RichEditor.RichEditor--emojiPicker.is-fakeFocus > div.RichEditor-container.u-borderRadiusInherit > div.RichEditor-scrollContainer.u-borderRadiusInherit > div.tweet-box.rich-editor.is-showPlaceholder > div').textContent = tweetText;
  }, tweetText);

  // ツイート
  await page.evaluate(({}) => {
    document.querySelector('.SendTweetsButton').click();
  },{});

  console.log('?  ツイート完了');
}

main();

使用ライブラリ

  • dotenv
    • Twitter IDとパスワードの秘匿用
  • puppeteer
    • ヘッドレスブラウザ

処理解説

処理としては
- ヘッドレスブラウザを立てる
- Twitterのログインページに遷移
- querySelectorでログイン情報を入れる
- querySelectorでクリックイベント起こしてログイン
- ツイートモーダル開く
- ツイート文章入れる
- ツイートボタン押下

これだけです。

今後の課題

  • FirebaseなりLambdaなりGASなり使って定期実行トリガーを設定したい
  • リプライを確認してバッジがついたらペプシジャパンからの返信ツイートを確認したい
    • リプライの文章から勝ち負け判定

なお

本田とじゃんけんの期間は4/19までみたいです。

今から定期実行トリガー作っても2日しか試せなそうで残念です(泣)

というか勝てる気がしないので、ドンキでコカ・コーラを買ってきたいと思います。

ありがとうございました

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

述語論理をやろうず

概要

プログラミング言語上で 1階述語論理の言語を実装したい。具体的には「論理式を 冠頭標準形 に変形し スコーレム標準形 を求め、節形式にする手順を実装に落とし込みたい」。そのために必要な 各種検討項目をまとめた。要するに 本稿は 論理プログラミング一歩手前までの数理論理学をプログラムに落とし込むための設計ドキュメントだといってよい。コードは未記載(分量が多いのでQiitaじゃないところに公開するかも)。

想定動作環境: Node上でのJavaScript で動作。依存パッケージは moo(字句解析), nearley (構文解析) のみ。npm i --save moo nearley で環境構築完了。

0. 表記 ~言語の記号~

  • 変数は以下が使える: x, y, z, w, x1, x2, ...x99
  • 定数は以下が使える: a, b, c, a1, a2, ... a99
  • 関数は以下が使える: f, g, h, f1, f2, ....f99
  • 述語は以下が使える: F, G, H, F1, F2, ..., F99
  • 括弧とカンマが使える: (, ), ,
  • 論理記号は以下が使える:
    • ¬として !, ⋀として &, ⋁として |, →として ->, ↔として ::
    • ∀xとして Ax., ∃xとして Ex.
    • = として =

論理式の例をいくつか挙げる

  • 原子論理式(コードでは prime と呼ぶ)
    • F(a), G(a13), F(a,x), G(f(a),b)
    • a=b, x=f(a,g(b)), f(a1,b)=g(a,b,c)
  • 複合論理式(コードでは compound と呼ぶ)
    • F(a)->G(b)->F(a), F(a)&G(f(b,c)), !F(a)&G(f(b)),
    • Ax.Ey.(x=y -> f(x)=f(y))

1. 内部表現

  • 構文木は S式を模した Array で表現する
    • f(a, b, c)[f, a, b, c]
    • a=b->F(a)[->, [=, a, b], [F, a]]
  • 言語の記号は文字列ではなく Object で表現する
    • x{ type: 'variable', value: 'x' }
    • a{ type: 'constant', value: 'a' }
    • f{ type: 'function', value: 'f' }
    • F{ type: 'predicate', value: 'F' }
    • ={ type: 'predicate', value: '=', infix: true }
    • !{ type: 'connective', value: 'not' }
    • &{ type: 'connective', value: 'and', infix: true }
    • |{ type: 'connective', value: 'or', infix: true }
    • ->{ type: 'connective', value: 'then', infix: true }
    • Ax.{ type: 'connective', value: 'forAll', boundID: 'x' }
    • Ex.{ type: 'connective', value: 'exists', boundID: 'x' }

幾つかの特記事項:

  • infix属性で中間記法であることを明示する。構文木を文字列にする際にのみ必要なので内部表現として保持しないという選択肢もあるが、この方式の方が論理記号の追加に対して対応しやすい(ファクトリ関数の修正だけで済む)。
  • 量化子は、束縛する変数名を文字列で持つ(boundID)。Objectで持たないということを明示するために属性名にIDを付けている。
  • 言語に追加論理記号を足す(例えば +, -, <, e, 0, ...)のは簡単。どうすればよいかを考えてみるとよい。

よって以下を実装すること:

  • toObj: 文字列を受け取り上述のオブジェクトを返す関数

2. 統語的な基本操作

一番最初に必要な 統語的な操作を記載:

  • parse: 文字列を構文木に変更する。moo, nearley, 文法ファイル, toObj を組み合わせて作る
  • repr: (人間のために)構文木を文字列にする
  • createNode: 構文木を入力として少し変形した構文木を作成する(例: 論理式の否定を作りたい, 二つの論理式の ⋀ をとった論理式を作りたい)。色々な場面で必要な操作なので まとめておくと読みやすいしコードの重複削減に貢献する。

加えて記号, 変数についての操作が必要

  • occurenceOf: 論理式の集合を渡し, 利用されている変数・定数・関数・述語の一覧を取得する。一覧が欲しいというよりも 未使用の記号が知りたいというニーズに応えることが多い。束縛変数をリネームしたい時や スコーレム関数に利用する記号を選択するなど。
  • freeOf, bndOf: 論理式における自由変数と束縛変数の一覧を求める。
  • rearrange: (人間のために) 論理式に登場する非自由な変数を機械的に一括して変更する。(例: Ax12.Ez.x12=f(x23,a, z)Ax.Ey.x=f(x23,a,y) に変更)。純統語的な操作ではないが正当化できる。自由変数をリネームしないのは、他の論理式との兼ね合いがあるため。注: 後述の束縛変数のリネームとは本質的に異なる操作 (例: F(z)->Az.F(z)を考えてみよ)

再帰・帰納の時によく使う操作:

  • isPrimeFormula: 与えられた論理式が原子論理式であることを判定。再帰の終了条件の判定に頻出。
  • isOpenFormula: 与えられた論理式に量化子が入っていないことを判定。再帰の終了条件の判定に頻出(特にPNF, SNFまわり)。

3. 統語的な応用操作

統語的な操作ではあるが、その定義に意味論的・証明論的なバックグラウンドを持つ各種操作をまとめる。

  • boundRename: 指定した束縛変数名を変更する操作。α同値性がバックグラウンドになっている(例: F(z) -> Az.F(z)F(z) -> Ax.F(x))。
  • substitiute: 代入操作。t/x と表記され 1つの変数x を1つの項t に置き換える帰納的操作。帰納的に定義されるが (∀xφ)[t/x] = ∀xφ, (∀yφ)[t/x] = ∀y(φ[t/x]) となることには注意。代入は 完全に統語的な操作であるが 無制限に代入操作をすることは(統語論的ではなく)意味論的・証明論的に許されない (例: y/x を ∃y¬(x=y) に施すと ∃y¬(y=y) になる。統語的には正しい挙動であるが、意味論的・証明論的には許容できない)。代入はスコーレム化の際に使う。
  • isCollisionFree: 上述のように 代入結果が変にならないための判定関数。「変になる」とは完全に意味論的・証明論的な概念だが、その判定自身は統語的に定義可能である。isCollisionFree(formula, term, variableName) というシグネチャを持つ。「論理式φが代入t/xに対してcollision-freeな場合t/xを行うと・・・・」というような意味論的・証明論的な規則を作るために利用。

4. 意味論的操作

意味論的同値関係を用いて論理式を変形する操作をまとめる

  • toPNF: 冠頭標準形への変形。意味論的同値性を再帰的に適用することで、量化子が論理式の先頭にのみ存在するように変形できる。同値変形の際に衝突判定を回避する変数を選ぶ必要があるが、もっと緩く未使用の変数を採用すればよい。利用する同値性:
    • ¬∀xφ = ∃x¬φ
    • 未使用の変数y で ∀xφ⋀ψ = ∀y(φ[y/x]⋀ψ), ∀xφ⋀ψ = ∀y(φ[y/x]⋀ψ)
    • 未使用の変数y で ∀xφ⋁ψ = ∀y(φ[y/x]⋁ψ), ∃xφ⋁ψ = ∃y(φ[y/x]⋁ψ)
    • 未使用の変数y で ∀xφ→ψ = ∃y(φ[y/x]→ψ), ∃xφ→ψ = ∀y(φ[y/x]→ψ)
    • 未使用の変数y で φ→∀xψ = ∀y(φ→ψ[y/x]), φ→∃xψ = ∃y(φ→ψ[y/x])
  • toSNF: スコーレム標準形への変形。意味論的同値性ではなく充足的同値性による式変形により、∀論理式に変形する。内部的には一度 toPNF を呼び出して冠頭標準形にしたのち、 occurenceOf で未使用の定数や変数の一覧を取得して帰納的に変形する。∀式にしたら、それを節形式にする(つまりCNFにする)のだが、ここまでくれば命題論理の方法が使える。

5. 最短ルート

目的が「任意の論理式を節形式にする」のであれば、前述したいくつかの操作は不要で、最低限必要な以下の操作を実装すればOK。parse, occurenceOf, substitiute, toPNF, toSNF。補助的に createNode, isPrimeFormula, isOpenFormula。あと確認用に repr, rearrange。何が言いたいかというと、自由変数/束縛変数/非衝突判定/束縛変数のリネームといった数理論理学で必要な概念は節形式にする上では不要だということ。言語で使用していない記号の一覧が occurenceOf で得られるので、衝突回避な代入は簡単に求まるという事情による。

最後に

議論・コメント・要望・指摘なんでも歓迎です。

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

oclifとpuppeteerを使って、シュッとウェブサイトのコンテンツをJSON化できるコマンドを作った

できたもの

https://github.com/rike422/kirinuki-cli

サンプル: qiitaの新着ページをスクレイピングしてjsonとして取得するコマンドです。

joでjsonを作成してjqで整形してます。

jo -B topics="$(jo -B title="$(jo -a .tr-Item_title text)" link=.tr-Item_title _unfold=true )" | xargs -0 kirinuki scrape -p https://qiita.com/ | jq .

qiita.gif

xargs -0 kirinuki scrape -p https://qiita.com/
部分が作ったコマンドになります。

できること

cssSelectorを値として書いたjsonで、データの加工処理なしにjsonを取得することができます。

記法について書いた過去記事

ocilif

過去記事がありました。
https://qiita.com/mokamoto/items/f70eaff72f795f1d7322

良さ

今回は使わなかった良さ

commanderとかつかってリッチなフィードバックを作ろうとすると、外部ライブラリ使うことになったりするので、さっと作りたい時最高だと思います。

puppetter

みんな大好きHeadless Chrome Node API

puppetterそのものを通常の依存関係にすると巨大なchromiumの実行ファイルをDLすることになるので、puppetter-coreを依存関係に追加するのみにしました。
オプションでchormeの実行パスを渡せるようにし、インストール済みchromeを使えるようにしています。
グローバルなどにインストールされたpuppetterがある場合そちらを参照しに行きます。

まとめ

定期的に監視したいページとか、jsonで取りたいページがあるときに使ってください。

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

今更Electron環境構築めも

今更Electron

マルチプラットフォームなら使える!と思って勉強する。

前提

nodeJsインストール済。

インストール

mkdir electron-test && cd $_
npm init -y

# --save-dev オプションが何を意味するかわからんけど、これじゃないとダメ
npm install electron-prebuilt --save-dev

# 実行はこれ。よくわかんないけど
node_modules/electron-prebuilt/cli.js
electron-packagerインストール
### install electron-packager.
sudo npm i electron-packager -g

## なんかwin32なアプリの為には、wine32が必要らしい.
https://github.com/electron-userland/electron-packager/issues/654

## ↓の通りにwineをbuild & installする (かなり時間かかるよ)
https://ameblo.jp/kousakusya/entry-12333298836.html
asar実行
# asar pack {アプリケーションディレクトリ} {出力ファイル名}.asar
asar pack ./sample ./sample.asar
electron-packager実行
### make package.
electron-packager ./sample sample --platform=all --arch=x64 --electronVersion=1.4.13

結論

まだわからんけど、Windows用バイナリはwine頼りってことはわかった。
win32は、ソースからビルドするしかなく、buildで異様に時間がかかる。

後、自分の環境(CentOs7.6 + GNome)だと、ウィンドウの透明化はできなかった。
Windowsなら簡単にできた。

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

Node.js,Expressメモ④

アイコンをうちの猫にしてみました。こんにちは。
rete.jsを使いたいためにExpressのチュートリアルを進めています。
前回はmongoDBに初めて触れてうきうきです。
新しい知識を実践投入したいところもありますが、今はDBがメインではないので頭の片隅においときます。

今回はこちらのPart4をやります。
https://developer.mozilla.org/ja/docs/Learn/Server-side/Express_Nodejs/routes

Part3の後半からすっかり翻訳がされなくなってしまっています。
俺の英語力よ目覚めよ。
(google翻訳した時は2画面になって左右に並んでくれたら最高なのに。)

こんかいすること

・どんな情報をページに表示するか設定して適切なURLを返すこと
・リクエストに応じてモデルからデータを取得してHTMLページに表示する

(定番の図ではありますが)こういうイメージだそうです。
image.png

Routesとは

ルートは適切なHTTP(GET, POST, PUT, DELETE, etc.)URL パス、パターン、そしてそれらを操作する関数が含まれます。
それらの方法はいくつかあるけど、チュートリアルではexpress.Routerを使用しています。

まず個別のルートを作る?

wiki.jsという独立したルートを作るようです。
なぜ急にwikiが!と思いますがとりあえず先生の教えに従います。

wiki.jsはapp.jsと同じ階層に置きます。(先生の教え)

get メソッド

router.get("/about", function(req, res){
res.send("About this wiki");
})

・req(HTTP request)
・res(HTTP response)
・next(上のケースではres.send()を使っているからnextは指定していないらしいです)

res.sendのほかにjsonを返すres.json(), ファイルを返すres.sendFile()もあるようですね。

URLの設定

rooter.get("/url名", function(req, res){処理}
でいくらでもルート以下の階層を定義できるってことなんですね。なるほど。
またこのurl名は正規表現?もいけるとのこと。記憶。

app.get(/.*fish$/, function (req, res) {}

また以下のパターンで動的にパラメーターを受け渡せる。
Bookの例で書いてあるのはこんな感じ。
http://localhost:3000/users/34/books/8989/users/:userId/books/:bookId
というパターンにするとuserIdが34, :bookIdが8989になるそうな。
これはわかりやすい。

そしてそれらはjson形式で保持されているようです。
req.params = {"userId": 34, "bookId": 8989}

ちなみに、、bookを追加するとき用に/book/createを作りたいとしたときに/book/:userIdとパターンを設定すると
{userID: create}になることがあります。
上から順番にマッチさせるようなので順番には気を付けてください。とのこと。

ページの追加

基本的にjsを追加して関連付けるみたいですね。

// routes/wiki.js
var express = require("express");
var router = express.Router();

router.get("/", function(req, res){
    res.send("About this wiki");
})

module.exports = router;

※コードの書き方はじめてわかった!

最後の一行がとても大事なようです。この一文が入っていなくて数時間無駄にしました。。

app.jsに以下を追加
var wiki = require("./wiki");

app.set("/wiki", wiki);

シンプルに追加するにはこれだけでいいようです。
これでにアクセスできるようになります。

http://localhost:3000/wiki
ほー。

controller

MVCのCではありますが、いつももやっとします。
wikipediaによると
ユーザからの入力(通常イベントとして通知される)をモデルへのメッセージへと変換してモデルに伝える要素である。すなわち、UIからの入力を担当する。モデルに変更を引き起こす場合もあるが、直接に描画を行ったり、モデルの内部データを直接操作したりはしない。
と書かれています。

要はUIで入力したものをモデルに渡す部分の独立性を保てばいいのかな、、

bookControler.js
var Book = require("../models/book");

exports.index = function(req, res){
    res.send("NOT IMPLEMENTED: site Home Page");
}

exports.book_create_get = function(req, res){
    res.send("NOT IMPLEMENTED: book create GET");
};

exports.book_create_post = function(req, res){
    res.send("NOT IMPLEMENTED: book create POST");
};
省略

最初にbookのモデルを読み込んでいるがチュートリアルに記述はないです。
進めていくうちに使われるのかなと楽しみにしつつ写経を続けます。

routes/catalog.js

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

var book_controller = require("../controllers/bookController");

router.get("/", book_controller.index);
router.get("/book/create", book_controller.book_create_get);
router.post("/book/create", book_controller.book_create_post);
省略

catalogにこういうurlが来たらこういう処理をするよーという処理を関連付けます。
よくよくみたらこのチュートリアルを通して違う書き方もできそうですが、これから記述が増えて
分ける意味がでてくるんかなと思います。

app.js
var catalogRouter = require("./routes/catalog");
app.use("/catalog", catalogRouter);

最後にappに追加してcatarogのurlを呼び出せるようにします。
なるほど!

これでダミー構造がだいたいできたのかなと思います。
次回はモデルの追加、更新、削除あたりが出てきそうな予感です。

つづく。

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