Node.jsからTwitterAPIを使う

Node.jsからTwitterAPIを使ってみたのでまとめました

やりたいこと

  • TwitterAPIを使ってツイート内容と画像をあげる
  • WebブラウザからのアクセスとUIWebViewを使ったアプリから使われるのでPin認証を使う

手順

  1. Twitter Application Management からアプリ作成する。Settingsタブの"Callback URL"は空欄にする。いつの間にか携帯電話番号が必要になっていた
  2. npm install oauth twitter --save でライブラリをインストール
  3. 軽く検証するときは、アプリの設定画面のKey and Access TokensタブのYour Access TokenでIDを発行する

あとはGistのような感じで。 コードの補足 L45でTwitterオブジェクトをnew するときに _.merge(clientConfig,...をしているのは、テストを書くときにnockを使ってテストをしたいため。テスト時にはconfig部分を変更している。

処理の流れとしては

  1. getOAuthTokenでトークンとPinを表示するためのURLを取得
  2. クライアント側でTwitterのPinの画面からコピーしたpin番号を渡してもらってauthCallbackを呼び出す。
  3. uploadMediaをアップしたい画像の数分呼び出す
  4. tweet処理でツイート内容とuploadMediaのcallbackで取得した media_id_string のカンマ区切りをわたす

感想

ドキュメントしっかりしてるのと、npmにライブラリが揃ってるから簡単に実装できた(小並感)

2015-07-07 画像周りの追記

ツイート内容に決まってる画像を埋め込む場合は、いちいちアップするのは通信の無駄なので、

  1. 鍵がかかってないアカウントに画像をUPする
  2. UPしたツイートのリンクから画像のURLをとる(例えば:pic.twitter.com/wldYWc3CXn)
  3. 画像のURLを本文に埋め込む

参考

コード

S3とCloud Storageのアップロード時間が全然違った話

S3とCloud Storageのファイル転送速度どのぐらいちがうのかなーと思って計測してみました。 2KBぐらいの5000枚の画像ファイルを作ってそれをインスタンス→S3、cloud StorageとS3(cloud Storage)→S3(cloud Storage)で計測してみました。 多分、ネットワークとか色々変動したりすると思うので、導入時に検証したほうがよいかと思うけど、参考までに。

#!/bin/sh
set -eu
mkdir $HOME/image
for j in `seq 1 100`
do
cd $HOME/image
mkdir $j && cd $j
for i in `seq 1 50`
do
  convert -size 1024x640 xc:yellow -pointsize 20 -draw "text 0,20 test_$i" test_$i.png
done
done

AWS

使ったコマンド

# ローカルからバケット
 date; aws s3 sync . s3://<foo>/`date +"%Y%m%d%H%S"` --exclude '*' --include 'image/*' --quiet; date
# バケットからバケット
date; aws s3 sync s3://<foo>/201506070403 s3://<foo>/`date +"%Y%m%d%H%S"` --exclude '*' --include 'image/*' --quiet; date
# インスタンス→S3にかかった時間
2015年  6月  7日 日曜日 12:20:31 UTC
2015年  6月  7日 日曜日 12:21:07 UTC

# S3→S3にかかった時間
2015年  6月  7日 日曜日 12:21:36 UTC
2015年  6月  7日 日曜日 12:22:21 UTC

GCP

使ったコマンド

date; gsutil -m rsync -d -R image gs://<foo>/`date +"%Y%m%d%H%M"`/image ; date
date; gsutil -m rsync -d -R gs://<foo>/201506070403/image gs://<foo>/`date +"%Y%m%d%H%M"`/image ; date
# インスタンス→Cloud Storageにかかった時間
2015年  6月  7日 日曜日 12:02:09 UTC
2015年  6月  7日 日曜日 12:05:05 UTC

# Cloud Storage→Cloud Storageにかかった時間
2015年  6月  7日 日曜日 12:13:37 UTC
2015年  6月  7日 日曜日 12:21:18 UTC

Sinon.JSのuseFakeTimersを教えてもらった

ということがあって、HipChat上に投げたら、sinon.useFakeTimersつかってないの?というツッコミがあったので、そんなのあるんだと。ちょっと使ってみました。 あら便利

http://sinonjs.org/ を見てみると JavaScript test spies, stubs and mocks.らしい。ちなみにバージョンは1.14.1で動かしてます。

var sinon = require('sinon');
describe('test', function() {
  describe('sub-test', function() {
    var sandbox;
    beforeEach(function(done) {
      sandbox = sinon.sandbox.create(); // sandbox でまとめて管理
      sandbox.useFakeTimers(new Date(2000, 0, 1).getTime());
      done();
    });

    it('call new Date', function(done) {
      console.log(new Date());
      done();
    });

    it('call new Date2', function(done) {
      console.log(new Date());
      done();
    });

    afterEach(function(done) {
      sandbox.restore();
      console.log('afterEach', new Date());
      done();
    });
  });
});

で動かすと、

  test
    sub-test
Sat Jan 01 2000 00:00:00 GMT+0900 (JST)
      ✓ call new Date
afterEach Thu May 21 2015 23:02:23 GMT+0900 (JST)
Sat Jan 01 2000 00:00:00 GMT+0900 (JST)
      ✓ call new Date2
afterEach Thu May 21 2015 23:02:23 GMT+0900 (JST)


  2 passing (14ms)

TypeScriptでNodeのWebアプリを書き換え中

今年頭からNode.jsを本格的に初めてみて、JavaScriptの柔軟性の便利さと怖さに四苦八苦してしてます。と言っても、1/3ぐらいは負荷試験してたり、細々と色々なことしてたので、そこまで書いてたわけではないけど。

オブジェクトにどんどんプロパティを追加して柔軟にできたり、ちょこっと関数追加できたりと嬉しい恩恵もあるのですが、その一方でこの関数の引数/戻り値の型がわからなかったりして、TypeErrorを連発してしまって、Java1.4時代のClassCastExeceptionの時代に戻ってしまった思いをしてます。 自分はIDEを使ってるのですが、その恩恵が100%得られてないというお金的に損した気分。

で、まぁ自分のベースはJavaなので型がある言語だと嬉しいよね。ということで、ちょっと前からTypeScriptの勉強を始めてます。型以外にもES6ぽい書き方もできるし、TypeScriptが出力するJSコードを見つつ、JSの勉強もするという色々なことに使ってます。

GW中にガッツリ書き書き換えて久しぶりにコード書いて徹夜して、自分でもまだまだいけるんだと自己満足に浸っております

通称わかめ本を読みつつ、以前Nodeを勉強するときに作ったWebアプリをTypeScriptで書き換え中。大規模開発になってるので実際の新規プロダクションで入れられたらいいなー

今はまだ書き換えてる途中なので、所々お作法に沿ってなかったりします。 [TypeScript版] https://github.com/hachi-eiji/79school-2/tree/typescript [通常のJavaScript版] https://github.com/hachi-eiji/79school-2/tree/master

まぁ一番間違っているのは二重コールバックのコードをよく書いてしまってることだけどw

TypeScriptリファレンス Ver.1.0対応

TypeScriptリファレンス Ver.1.0対応

自分用のメモにもslackがいいのかもしれない

仕事用のメモで何かいいのないのかなーと色々試している途中

ユースケース

  • メモはほぼ毎日書く
  • 自分用のミーティング議事メモや、軽いプログラム(集計SQLとかコピペで実行するシェルとか)、コード書いてて気づいたことを書く。
  • 小さなタスクも書く
  • ホワイトボードをカメラで撮ることはある

必須要件

  • プロジェクト情報とか入るので、自分以外には見せたくない
  • マシンが複数台(会社と家に障害対応用マシン)あるので共有はしたい
  • メモの書き出しには日付入れてわかりやすくしたい

あったら嬉しい

  • 過去に書いたものを検索する
  • 可能な限りテキストベースでコピーとかしたい
  • vimコマンドでショートカットできると嬉しい
  • インターネットにつながってなくても使えると嬉しい
  • markdown でかけると助かる

やらなくていいこと

  • スマホからのアクセス
  • 自分用のメモなので細かいバージョン管理
試したもの 感想
テキストファイ DropboxとかGitを使って同期できる。
数年使っていたが、月ごとにファイル分けるのがだんだんめんどくさくなってくる。
Dropboxには他の大きなファイルがあるのでスペックが低い障害対応用マシンで同期するのはディスクサイズを食うので、GitHubを使ったが毎日pushするのめんどくさい
Evernote PC、スマホ用のクライアントアプリある
コピーするとHTMLタグ(?)を自動で設定するのですごい不便。
プロジェクトの仕様とかをガッツリまとめるには便利。1週間ぐらい使って止めた。
Kobito QiitaのMac版クライアントアプリ。有料プランにするとDropbox同期ができる。
markdownのリアルタイムプレビューもあるので、有料でもいいかなと思ってたが、1ヶ月(実際は20日営業日前後)書いてるとクライアントアプリが明らかに重くなった
wri.pe ノートを取るシンプルなWebサービス。markdownで記載できて、リアルタイムプレビューができる。auto saveもしてくれるのは嬉しい。
時系列に書くので、最新のものを一番上に書いているが、テキストエリア内の最初のフォーカスが一番下に来てるのがすごい惜しい。もし、フォーカスが一番下に来るのであれば、markdownプレビューも一番下に来てほしい。あとmarkdownのプレビュー文字が小さいのでもう少し大きくしてほしい。
Slack テキスト形式ではないものの投稿したら自動で時系列になる。コードスニペット書けるし、画像もアップできる。
タスクはstarで管理しておく。今のところあまり不満はない。
ちなみにChatWorkでもできるかなと思ったがSlackのほうが機能多かった。ただ、タスク機能は恐ろしく便利。社内のチャットツールがChatWorkだったら全部まとめてた。個人的にはHipChatよりChatWorkのほうが好き(期待値も込めて)

ということで、今のところSlackに落ち着いてる。もうすこし使ってみて、使い方あってなかったらwri.peにするかChatWorkにするかも。いいものがあれば教えてくれるとすごい嬉しいです

gulpを書くときのメモ

背景

今のプロジェクトでgulpの中身を見ることが何回かあって、そのたびに「こうしたらいいのになー」とか思うので、軽くまとめておく。見るたびに直したい衝動にかられるが、動いているのには理由があるし、今はそのフェーズでないのでグッと我慢する。もちろんgulpを書いてる人から見たら、全部gulpを見ればわかるとか、ファイル分割されているとか、いいところはあるんだろうけど。 当たり前のことを書いてるが、自分が忘れやすいのでメモってるだけ

今回のユースケース

実際のプロジェクトではもっとたくさんのことをやってるが今回は下記に絞る

  • ローカル環境もしくはJenkins上から呼び出す。
  • JavaScript(CSS)ファイルをminifyしたり、画像をリサイズする。

改善点メモ

  • 本番環境,開発環境でminifyする/しないをわけない。タスクをまとめた親タスクを作るか、呼び出し側で制御する。親タスク側ではminimistとかつかって、外部パラメータを渡す。開発環境は何台でも増えるし、いままでminifyしてなかった環境でminifyしたくなることもある
  • gulp-helpを使ってタスクの説明を書く。3日後の自分は他人 gulp-help
  • ファイルを分割しすぎない。gulpfile.js → ファイルA.js → ファイルB.js→ファイルA.jsに戻るみたいなことがあった。若干冗長でも2つ目ぐらいで止めておきたい

なんかこんな感じ

Sample GulpFile

$ gulp help
[00:52:46] Using gulpfile ~/development/gulp-test/gulpfile.js
[00:52:46] Starting 'help'...

Usage
  gulp [task]

Available tasks
  help       Display this help text.
  minify-js  JavaScriptをMinifyします

[00:52:46] Finished 'help' after 1.5 ms

UNIXという考え方の本はこういうときも役に立つ

UNIXという考え方―その設計思想と哲学

UNIXという考え方―その設計思想と哲学

MongoDBのindex

indexの作成

db.collection_name.ensureIndexで作成できる。この時に第2引数のoptionsにbackgroundを入れること。デフォルトは background:falseなので、index作成完了までデータベースロックがかかるため
ちなみにバックグラウンドを有効にした場合でも、シェルセッションの応答まではバックグラウンドにならない.
なお、性能はバックグラウンドで作成したほうが遅い。
バックグラウンドで作成するときは,primary終了後にsecondaryへindex作成が行われるので、primary -> secondaryの順番で作成する。
secondaryで大きなindexを作成するときは、一度secondaryを外したのちに単独でindexを作成する.再度レプリカセットに入れるときに他のセカンダリにキャッチアップできるようにしてから、stepDownでプライマリを切り替える。Build Indexes on Replica Sets — MongoDB Manual 3.0.1に書いてあったけど、変更したReplica SetのステータスがRECOVERINGで止まった。(別セッションでinsertしてるからどんどんprimaryとずれていくのはわかるけど...)
この辺は詳しい方に教えていただきたい

複合index

{ "item": 1, "location": 1, "stock": 1 } というindexを貼った場合は、検索条件でindexを使わない場合がある(基本MySQLと同じかな)

検索条件 index使う?
item o
item, location o
location, item o
item, location, stock o
item, stock o (ただし、item,stockでindexを貼ったほうが効果はある)
location x
stock x
location, stock x

sortでindexを使う場合

{a:1, b:1}というindexを貼った場合はクエリの条件で { a: 1, b: 1 },{ a: -1, b: -1 }とした場合にはindexは見るが、{b:1, a:1},{a:-1, b:1}の場合は見ない。検索条件とsortを同時に使用する場合は、作成したindexの並び順通りになる。(MySQLとかと同じだとおもう)

Index Intersection

1クエリで複数indexを使うようになった.複合indexとの性能の違いはあまり見つけられず...

Sparse Indexes

指定されたプロパティが存在しない時に無視されるindex。今のところ通常indexと混在すると色々面倒そうな気がするので、あまり使い道が出てこない

mongos> db.scores.save({userid:"newbie"});
WriteResult({ "nInserted" : 1 })
mongos> db.scores.save({userid:"abby", score: 82});
WriteResult({ "nInserted" : 1 })
mongos> db.scores.save({userid:"nina", score: 90});
WriteResult({ "nInserted" : 1 })
mongos> db.scores.ensureIndex({score :1}, {sparse:true});
{
	"raw" : {
		"ip-10-0-0-16.ec2.internal:27017" : {
			"createdCollectionAutomatically" : false,
			"numIndexesBefore" : 1,
			"numIndexesAfter" : 2,
			"ok" : 1
		}
	},
	"ok" : 1
}
mongos> db.scores.find( { score: { $lt: 90 } } )
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }
mongos> db.scores.find().sort( { score: -1 } )
{ "_id" : ObjectId("54a798995a355747582911e2"), "userid" : "nina", "score" : 90 }
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("54a798805a355747582911e0"), "userid" : "newbie" }
mongos> db.scores.find().sort( { score: 1 } )
{ "_id" : ObjectId("54a798805a355747582911e0"), "userid" : "newbie" }
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("54a798995a355747582911e2"), "userid" : "nina", "score" : 90 }
mongos> db.scores.find().sort( { score: -1 } ).hint( { score: 1 } )
{ "_id" : ObjectId("54a798995a355747582911e2"), "userid" : "nina", "score" : 90 }
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }

mongos> db.scores.ensureIndex({score :1});
{
	"raw" : {
		"ip-10-0-0-16.ec2.internal:27017" : {
			"createdCollectionAutomatically" : false,
			"numIndexesBefore" : 1,
			"numIndexesAfter" : 2,
			"ok" : 1
		}
	},
	"ok" : 1
}
mongos> db.scores.find( { score: { $lt: 90 } } )
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }
mongos> db.scores.find().sort( { score: -1 } )
{ "_id" : ObjectId("54a798995a355747582911e2"), "userid" : "nina", "score" : 90 }
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("54a798805a355747582911e0"), "userid" : "newbie" }
mongos> db.scores.find().sort( { score: 1 } )
{ "_id" : ObjectId("54a798805a355747582911e0"), "userid" : "newbie" }
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("54a798995a355747582911e2"), "userid" : "nina", "score" : 90 }
mongos> db.scores.find().sort( { score: -1 } ).hint( { score: 1 } )
{ "_id" : ObjectId("54a798995a355747582911e2"), "userid" : "nina", "score" : 90 }
{ "_id" : ObjectId("54a7988e5a355747582911e1"), "userid" : "abby", "score" : 82 }
{ "_id" : ObjectId("54a798805a355747582911e0"), "userid" : "newbie" }