読者です 読者をやめる 読者になる 読者になる

totorajの開発日記

僕がなんかメモる

Web Workersをファイル分けせずに使う

タイトル通りです。

方法

Workerとして実行するスクリプトをBlobを使ってファイルにします。

生成されたファイルのURLを取得してWorkerにするだけです!

ソースコード

createWorkerがWorkerを作るメソッドです。

参考:

// Workerを生成
var worker = createWorker(workerScript);
console.time("prime");
// Workerにメッセージを投げる
worker.postMessage(1000000);
// Workerからメッセージを受け取る
worker.addEventListener("message", function (event) {
    console.timeEnd("prime");
    document.querySelector("body").innerHTML = event.data.join(", ");
});

/**
 * 渡されたFunctionからWorkerを作って返す
 * @param {Function} script - Workerにするスクリプト
 * @return {Worker}
 */
function createWorker(script) {
    // 正規表現を使ってメソッド内のスクリプトのみを抽出
    var re = /^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/,
        body = script.toString().trim().match(re)[1];
    // Blobを使いファイルを生成
    var blob = new Blob([body], {type: "text/javascript"});
    // BlobのURLを取得
    var url = URL.createObjectURL(blob);
    
    // Workerを作って返す
    return new Worker(url);
}

/**
 * Workerとして動かすスクリプト
 */
function workerScript() {
    
    addEventListener("message", function (event) {
        postMessage(prime(event.data));
    });
    
    function prime(limit) {
        var primes = [];
        if (limit < 2) return primes;
        primes.push(2);
        if (limit <= 2) return primes;
        for (var i=3; i <= limit; i++) {
            for (var j=0; j < primes.length; j++) {
                if (i % primes[j] === 0) break;
                if (j+1 >= primes.length) primes.push(i);
            }
        }
        return primes;
    }
}

Web Workersを利用した素数一覧表示テスト - Gist

おわりに

めっちゃ薄っぺらいないようですね・・・。

久々の更新だから仕方ない!

Node.jsでcheerioを使ってXMLを解析する

タイトル通りです。

cheerioというnodeのモジュールを使ってXMLを解析して必要な情報をとりだすときのメモです。

github.com

インストールはnpmでサクッと

npm install cheerio

XMLモードを有効にする

ここで20分ほどはまりました。

loadメソッドを実行するときにオプションで有効にします。

$ = cheerio(xml, { xmlMode: true });

有効にすることでlinkタグなどがちゃんと読み込まれるようになります。

https://github.com/cheeriojs/cheerio/blob/master/Readme.md#loading

セレクタのエスケープ

タグやID,Classに:(コロン)などの特殊文字が含まれている場合は直前に\(バックスラッシュ)を2つでエスケープします。

$("dc\\:creator").text();

おそらくjQueryと同じだと思われます。

参考:

サンプル

ニコニコ動画のマイリストページのRSSから情報を取り出すサンプルです。

json形式で出力します。

使い方

node mylist_rss.js "id" "user_session"

user_sessionは任意です。

非公開の自分のマイリストを取得するときに使います。

例:

node mylist_rss.js 38085032

ソースコード

MITライセンスです。

ニコ動のマイリストをマイリストページのRSSから取得するやつ · GitHub

おわりに

訳あってxml2jsonからcheerioに切り替えたけどなかなか便利ですね。

jQueryっぽく使えるのがすごくいい!

electron-packagerでWindows用の実行ファイルを作る

タイトル通りです。

環境は下記の通りです。

  • OS: Windows10(64bit)
  • node: v4.4.3
  • npm: v2.15.1
  • Electron: v1.0.1

OSがWindows以外の方にはまったく役に立たない記事です!ごめんなさい。

フォルダ構成はこんなかんじ。

demo/
 ├─source/
 │  ├─package.json
 │  ├─main.js
 │  ├─icon.ico
 │  └─index.html
 └─build.js

sourceフォルダがElectronで実行するフォルダです。

手順

コマンドはすべてdemoフォルダ上から実行しています。

electron-packagerをインストール

さくっとインストール

npm install electron-packager

ソースのフォルダにインストールするとこのモジュールまで出力されてしまうので別のフォルダにした方がいいと思います。

build.jsを書く

コマンドをうってもいいんだけど結構長くなるし毎回コマンドうつのは面倒くさいのでjsにしてnodeで実行します。

ソースコード

build.jsのソースコードです。

環境に合わせて書き換えてください。

const packager = require("electron-packager");
// 毎回オプションを書き直すのは面倒くさいのでpackage.jsonから引っ張ってくる
const package = require("./source/package.json");

packager({
    name: package["name"],
    dir: "./source",// ソースフォルダのパス
    out: "./dist",// 出力先フォルダのパス
    icon: "./source/icon.ico",// アイコンのパス
    platform: "win32",
    arch: "x64",
    version: "1.0.1",// Electronのバージョン
    overwrite: true,// 上書き
    asar: false,// asarパッケージ化
    "app-version": package["version"],// アプリバージョン
    "app-copyright": "Copyright (C) 2016 "+package["author"]+".",// コピーライト
    
    "version-string": {// Windowsのみのオプション
        CompanyName: "totoraj.net",
        FileDescription: package["name"],
        OriginalFilename: package["name"]+".exe",
        ProductName: package["name"],
        InternalName: package["name"]
    }
    
}, function (err, appPaths) {// 完了時のコールバック
    if (err) console.log(err);
    console.log("Done: " + appPaths);
});

APIの詳細は下記のelectron-packagerのドキュメントをどうぞ。

https://github.com/electron-userland/electron-packager/blob/master/docs/api.md

build.jsを実行

node build.js

下記のような出力が出れば成功です。

Packaging app for platform win32 x64 using electron v1.0.1
Done: dist\Test-win32-x64

出力先フォルダにexeの入ったフォルダが生成されているはずです。

その他

アイコンについて

アイコンはプラットフォームに応じて適したものにする必要があります。

Windowsならicoファイルです。

icoファイルに関しては調べればいくらでも情報が出てきます。

また、Windows以外の環境でWindows用のexeを出力してもアイコンが適用されないようです。

electron-packagerのドキュメントを参考にしてください。

https://github.com/electron-userland/electron-packager#building-windows-apps-from-non-windows-platforms

JavaScriptの配列についてのメモ

タイトル通りです。

配列のコピーとかをするときに忘れがちなのでメモしておきます。

ソースコードを見ればなんとなくわかるはず。

テストコード

// テストオブジェクト
var TestObject = function (num) {
    this.num = num;
}

// 配列1
var list_1 = [
    new TestObject(1),
    new TestObject(2),
    new TestObject(3),
    4
];
// 配列2
var list_2;

/* ===== 配列2に配列1を代入した場合 ===== */

list_2 = list_1;

list_2[0].num = 10;
console.log(list_1[0].num);// 10
console.log(list_2[0].num);// 10
list_1[0].num = 1;
console.log(list_1[0].num);// 1
console.log(list_2[0].num);// 1

list_2[3] = 40;
console.log(list_1[3])// 40
console.log(list_2[3])// 40
list_1[3] = 4;
console.log(list_1[3])// 4
console.log(list_2[3])// 4

list_2.push(5);
console.log(list_1[4])// 5
console.log(list_2[4])// 5
list_1.pop();
console.log(list_1[4])// undefined;
console.log(list_2[4])// undefined;

// 同じ配列,同じTestObjectが利用されていることがわかる
// また、この場合はプリミティブ型も同じものが利用されている


/* ===== 配列2に配列1の要素を一つずつpushした場合 */

list_2 = [];
for (var i=0; i < list_1.length; i++) {
    list_2.push(list_1[i]);
}

list_2[0].num = 10;
console.log(list_1[0].num);// 10
console.log(list_2[0].num);// 10
list_1[0].num = 1;
console.log(list_1[0].num);// 1
console.log(list_2[0].num);// 1

list_2[3] = 40;
console.log(list_1[3])// 4;
console.log(list_2[3])// 40;

list_2.push(5);
console.log(list_1[4]);// undefined
console.log(list_2[4]);// 5

// 別の配列, 同じTestObjectが利用されていることがわかる
// また、この場合はプリミティブ型は別のものが利用されている

おわりに

なかなか面白い挙動をする。

ElectronでBrowserWindowのCookieを設定する

タイトル通りです。

sessionってのを使います。

下記の公式ドキュメントに詳細があります。

electron/session.md at master · electron/electron · GitHub

ソースコード

簡単なサンプルです。

"use strict";
const electron = require("electron");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

var mainWindow = null;

// アプリの準備ができたらWindowを表示
app.on("ready", function () {
    
    // Windowを生成
    mainWindow = new BrowserWindow({
        width: 600,
        height: 400,
        title: "Cookie test"
    });
    
    // Windowが閉じられたらmainWindowを初期化
    mainWindow.on("close", function () {
        mainWindow = null;
    });
    
    // 適当なHTMLをロード
    mainWindow.loadURL("file://" + __dirname + "/index.html");
    
    /* ===== ここからCookieの設定 ===== */
    // 設定するCookieを定義
    var cookie = {
        url: "http://totoraj.net",// 対象となるURL
        name: "user_name",// Cookieの名前
        value: "totoraj"// Cookieの値
    };
    
    // mainWindowのCookieを設定
    mainWindow.webContents.session.cookies.set(cookie, function (error) {
        if (error) {// エラーが出たら出力
            console.log(error);
        }
    });
    /* ===== ここまでCookieの設定 ===== */
});


// Windowがすべて閉じたらアプリを終了
app.on("window-all-closed", function () {
    if (process.platform != "darwin") {
        app.quit();
    }
});

対象となるURLについて

.nicovideo.jpのようにワイルドカードを指定する場合もhttp://を先頭につけないといけのでhttp://.nicovideo.jpとなる。

.nicovideo.jpを設定してみるとfailedというエラーが出たのでそもそもhttp://https://を付けないとエラーが出る?かもしれない。

おわりに

わけあってBrowserWindowに任意のCookieを設定したかったんだけどなかなかそれらしい記事とかがなかったのでメモ。

参考になれば幸いです。

SVGについてのメモ

現在制作しているプレイヤーのボタンを作るときにSVGについて調べたので忘れそうなことをメモしておく。

文法についてのメモ

SVGのpath要素のd属性のはなし

Mコマンドの直後のLコマンドは省略できる。

連続して同じコマンドを使う場合もコマンドは省略できる。

<!-- 下記は同じ -->
<path d="M 0,0 L 10,0 L 10,10" />
<path d="M 0,0 10,0 10,10" />

区切りの,(コンマ)と(スペース)はどちらでもいい

コマンドの直後は区切る必要はない

<!-- 下記は同じ -->
<path d="M 0,0 10,10" />
<path d="M0 0 10 10" />

僕は見やすさを考慮して座標のxとyはコンマで区切って、その他はスペースにしています。

SVGをアニメーションさせる

ライブラリを使うのが手っ取り早い。

Snap.svgというのがよさげ。

Adobeが開発しているオープンソース(Apache2 license)のライブラリ。

めっちゃしっかり作られている。

アニメーションのコツ

pathのd属性を指定したd属性までアニメーションさせるときに少し癖がある。

きれいに動くpathを書くコツ?みたいなのがあるので少しメモしておく。

Bad

f:id:totorachan:20160429153436g:plain

Mコマンドとzコマンドの数がそろっていないので変なアニメーションになる。

<!-- Before -->
<path d="M 3,2 13,8 3,14 z" />

<!-- After -->
<path d="M 3,2 6.4,2 6.4,14 3,14 z
         M 9.6,2 13,2 13,14 9.6,14 z" />

Good

f:id:totorachan:20160429153513g:plain

BeforeのMコマンドとzコマンドの数をAfterに合わせるために分割した。

さらにMコマンドの座標を1つ余分に追加してAfterと合わせている。

見た目が変わらないようにコマンドと座標を合わせるのは結構難しいけど、これだときれいなアニメーションになる。

<!-- Before -->
<path d="M 3,2 8,5 8,11 3,14 z
         M 8,5 13,8 13,8 8,11 z" />

<!-- After -->
<path d="M 3,2 6.4,2 6.4,14 3,14 z
         M 9.6,2 13,2 13,14 9.6,14 z" />

おわりに

なにもツールを使わずにSVG手書きしたのは初めてだったけど無駄がない構造にできるのでオススメ。

ただ、シンプルなアイコンだと普通に書けるけど複雑なものだと少し厳しいものがある。

ニコ動のAPI関連のメモ(1)

自分用のメモです。

間違った情報があるかもしれません。

マイリストをRSSで取得

RSS->JSONなどに変換してから処理をすると楽です。

node.jsだったら「xml2json」っていうモジュールがおすすめ!

RSSのURL

http://www.nicovideo.jp/mylist/<id>?rss=2.0

基本的な階層

xmljsonっぽく書くとこうなる

rss: {
  channel: {
    title: "マイリストタイトル"
    description: "マイリスト説明(改行含む)"
    putDate: "更新時間?"
    "dc:ccreator": "所有者"

    item: [
      {
        title: "動画タイトル"
        link: "動画URL"
        pubDate: "マイリスト登録時間"
        description: "「サムネ,動画説明文,再生時間,投稿時間,マイリストコメント」が含まれている"
      }
      ...
    ]
  }
}

切り出しが必要なところ

マイリストタイトル

/^.+?\s(.+)\u2010[^\u2010]+?$/

rss.channel.titleから切り出す。

サムネURL

/http:\/\/.+\.smilevideo\.jp\/smile\?i=[0-9]+/

rss.item[n].descriptionから切り出す。

マイリストコメント

/<p class="nico-memo">([\s\S]+?)<\/p><p class="nico-thumbnail">/

rss.item[n].descriptionから切り出す。

動画再生時間

/<strong class="nico-info-length">([0-9|:]+?)<\/strong>/

rss.item[n].descriptionから切り出す。

動画投稿時間

/<strong class="nico-info-date">(.+?)<\/strong>/

rss.item[n].descriptionから切り出す。

よくわからん表記方法なので適当に数字を切り出してそろえる。

動画取得関連

FLV or MP4

1. 動画のURLを取得

http://flapi.nicovideo.jp/api/getflv?v=<id>

上記のURLから動画のURLを取得

2. Cookieを取得

http://www.nicovideo.jp/watch/<id>

上記のURLからレスポンスヘッダのCookie「nicohistory」を取得

3. 動画を取得

1で取得したURLから動画を取得する。

リクエストヘッダに2で取得したnicohistoryを含めておかないと403を返される。

スマホ用のMP4

1. watchAuthKeyを取得するAPIのURLを取得

http://sp.nicovide.jp/watch/<id>

上記のURLから
#jsDataContainerの属性[data-watch_api_url]に設定されているURLを取得する。

取得する例(JavaScript)

// responseには文字列として再生ページのhtmlが入っている
response.match(/data-watch_api_url="(.+?)"/)[1].replace("&amp;","&");

2. watchAuthKeyを取得

1で取得したURLにアクセスしてwatchAuthKeyを取得。

帰ってくるJSONは下記を参考にしてください。

レスポンス(JSON)

{"watchAuthKey":"(ここにめっちゃ長いキー)","status":"ok","status_code":200}

3. Cookieと動画のURLを取得

http://flapi.nicovideo.jp/api/getflv?v=<id>
&device=iphone3
&watch_auth_key=<2で取得したkey>

上記のURLからレスポンスヘッダのCookienicohistory」と動画のURLを取得。

4. 動画を取得

3で取得したURLにリクエストを送るときにヘッダに3で取得したnicohistoryを含めておくと動画を取得できる