Category: プログラミング

BF4の各月のプレイ時間取得するスクリプト書いた

このエントリーをはてなブックマークに追加
はてなブックマーク - BF4の各月のプレイ時間取得するスクリプト書いた


いよいよ来月がBattlefield Hardline(BFH)の発売日。
今月くらいはBF4休んでうんぬんかんぬん


EMAIL・PASSWORD・USER_IDに自分の値入れておいて実行するだけ。CSVで出力されるからあとは適当に。
USER_IDはバトルログの自分のステータスページの9桁くらい(適当)の数値。

感覚的に先月やり過ぎたかなって思ってて、
実際に1月が一番プレイ時間長くて草。かなり忙しかったはずなのに。



外資系やらIT系やらの経団連に入っていない企業の本採用がすでに始まってて、1月は面接やらで大分メンタル削られてた。
そのストレスのはけ口としてBF4やってしまった感はある


とりあえずこの時期の就活は一段落で開放感あるけど
2月,3月と論文の締め切りあるゆえん2月は研究に全力出す。

さすがに全くやらないとストレスのはけ口がなくなっちゃうから
2月は28時間以下目標で。

今月はがんばるぞい!なんとしても在学中にもう一度海外行くぞい!



バトルフィールド ハードライン [オンラインコード] [ダウンロード]
エレクトロニック・アーツ (2015-03-19)
売り上げランキング: 231

このエントリーをはてなブックマークに追加
はてなブックマーク - BF4の各月のプレイ時間取得するスクリプト書いた

Arduinoとリードスイッチで部屋の扉の開閉監視

このエントリーをはてなブックマークに追加
はてなブックマーク - Arduinoとリードスイッチで部屋の扉の開閉監視

概要

Arduinoとリードスイッチで部屋の扉の開閉状況のモニタリング始めた。
部屋の扉が開いたらPushbulletでスマホなりMacのChromeなりに通知が来る。

外出中に親が勝手に部屋の掃除するのが嫌で、
何度もやらなくていいとお願いしたけれど
一向にやめてくれないし、明らかにやった形跡あるのにやってないやってないと言うので
それなら開閉モニタリングしておこう、という中学生みたいな動機。


予算:6000円くらい
作業時間:半日


用意するもの

Arudino本体リードスイッチと適当な長い導線さえあれば出来る。
ソースコードはgist


Arduinoをはじめようキット
スイッチサイエンス
売り上げランキング: 644
ProjectBox for Arduino (ブルーエッジ)
スイッチサイエンス
売り上げランキング: 20,842


Arduino上で回路を組む

Arduinoにリードスイッチをつけて、オンオフの値を読み取れるようにする。
作る回路は以下。リードスイッチの両端を2番ポートとGNDにつなげるだけ。




Arduino IDEでコーディング

リードスイッチは磁石が近づくと断線状態となる。プルアップ抵抗がうんたらかんたら。
とりあえず以下のコードで動くので()細かいことは気にしないことにする。


RubyでArduinoとシリアル通信して値を読み込む&通知を送信

RubyでArduinoとシリアル通信して値を読み込む。

通知にはPushbulletを使う。AndroidやPC/MacのChromeに手軽に通知を送れて便利。
自分で開けたのに通知送られても困るので平日の昼間に限定。別途物理的なスイッチをArduino側に追加してもいいけどとりあえず。
環境変数PUSHBULLET_API_KEYに自身のAccessTokenをセットしておく。
#どちらか
$ echo 'PUSHBULLET_API_KEY="MY_ACCESS_TOKEN"'>>~/.zshenv
$ echo 'PUSHBULLET_API_KEY="MY_ACCESS_TOKEN"'>>~/.bashrc




設置

扉が空いたらリードスイッチと磁石が離れるようにうまく設置する。



完成

誰かが扉を開けるとスマホに通知が来る。





終わり

PCが警察に押収されたら(PC周辺の状況が変化したら)任意のコマンドを実行しようとかいうSWATdなるプログラムもあるらしい。

なお扉の開閉監視してまで護りたい物はなかった模様

このエントリーをはてなブックマークに追加
はてなブックマーク - Arduinoとリードスイッチで部屋の扉の開閉監視

socket.io-clientを用いてExpress+Socket.IOで作ったサービスへ同時接続テストを行う

このエントリーをはてなブックマークに追加
はてなブックマーク - socket.io-clientを用いてExpress+Socket.IOで作ったサービスへ同時接続テストを行う

Express3.x(connect2.3+)とSocket.IOでのセッション管理mochaとsuperagentでexpressを使ったサービスのログイン周りのテストの続き。

今回はsocket.ioの同時接続のテストについて。今回は

  • 1000人同時接続が出来るか
  • 正しくメッセージのやりとりが出来るか(チャットなので)
  • ログイン済みユーザーのみが接続できるか
のテストを行なってみました。

socket.io-clientを用いれば、コンソールから同時接続のテストが簡単に出来ます。
ただし、環境によってはOS自体のファイルディスクリプタの最大数を引き上げないと1000人同時接続は難しいです。


ソースコード

今回は今までのを全て含めたものをgithubに上げています。
pxsta / express-socket.io-chat-test


今回の環境

  • Mac OSX 10.7.4 Lion
  • socket.io@0.9.10
  • socket.io-client@0.9.10
  • express@3.0.0rc3


やったこと

ファイルディスクリプタの最大数の変更(環境によっては不要)

1000人同時接続のテストを行う際にはOS自体のファイルディスクリプタの最大数を引き上げる必要があります。
少なくともMacでは256などというとても低い値に設定されているのでそのままでは無理です。

Mac OSX 10.7.4 Lionの環境で1000人接続のテストを行った所、117人あたりで接続を受け付けなくなるか、以下のようなエラーで止まってしまいまいした。
[ERROR] console - Error: EBADF, Bad file descriptor
/node_modules/express/node_modules/connect/lib/middleware/errorHandler.js:68
                .replace('{style}', style)
                 ^
TypeError: Cannot call method 'replace' of undefined
    at /node_modules/express/node_modules/connect/lib/middleware/errorHandler.js:68:18
    at [object Object]. (fs.js:80:5)
    at [object Object].emit (events.js:64:17)
    at fs.js:820:12

Macの場合は
$ ulimit -n
256
$ sysctl kern.maxfilesperproc
kern.maxfilesperproc: 10240
$ ulimit -n  10240
$ ulimit -n  
10240
のようにして変更可能です。ulimitにkern.maxfilesperprocの値を設定すれば最大値まで引き上げられます。


socket.io-clientによる同時接続のテスト(1000人くらい)

socket.io-clientを用いれば同時接続のテストをコンソールから行うことが出来ます。
以下のように、接続自体は今までクライアント側で書いていたものをサーバ側でそのまま使うことが出来ます。
var io = require('socket.io-client');
var options = {
    'force new connection' : true,  //別々のコネクションとして認識させるために必要
    port : 3000
};

//接続
var socket = io.connect("",options);
socket.on('connect', function(data) {
    done("wrong behaviour");
});

//メッセージの送信
socket.emit("chat","send message test");

そのため、複数人接続のテストはmochaと組み合わせて以下のようにして簡単に出来ます。
var should = require('should')
  , expect = require('expect.js');
var options = {
    'force new connection' : true,  //別々のコネクションとして認識させるために必要
    port : 3000,
};

//socket.ioの接続テスト
describe('socket.io test', function() {
    it('1000件接続出来るかどうか。ファイルディスクリプタの最大値を大きくしないと一度に接続できない', function(done) {
        this.timeout(600000);
        var maxCount = 100;
        var clientCount = 0;
        var clients = [];
        
        //全てが接続し終わったら全て切断してから終了
        var end = function() {
            for (var k=0;k<clients.length;k++) {
                clients[k].disconnect();
            }
            done();
        }; 
        
        //maxCount個のクライアントを接続する
        for (var i = 0; i < maxCount; i++) {
            var client = helper.login({
                auth : {
                    userID : 'test' + i,
                    password : 'test'
                },
                server : {
                    host : "",
                    details : options
                }
            }, function(err, client) {
                clients.push(client);
                
                //接続出来たクライアントがmaxCount個に達するまで待つ
                client.on('connect', function(data) {
                    if ((++clientCount) == maxCount) {
                        end();
                    }
                });
            });
        }
    });
});
接続する際のパラメータを
'force new connection':true
と設定しておかないと全てが1つのコネクションとして扱われてしまいます。
パラメータさえ指定しておけば後は繋ぎたいだけforで回して接続すれば出来てしまいます。簡単!


socket.ioの認証まわりのテスト

socket.io-clientではcookieをセットすることができません。

socket.ioの接続認証はexpressのセッションIDをcookieより取得、さらにそのセッションIDを元にsessionStoreからセッションデータを取得、正しいセッションデータがあれば接続を認める、という流れになっています。

そのため、cookieが無いとsocket.ioの認証をそもそも受けられないのですが、
expressやsocket.ioのテストはこんな感じで書いてます、というお話 – アルパカDiary
という素晴らしいエントリーが合ったので参考にさせていただきました。

socket.io-client/lib/socket.js内のio.Socket.prototype.handshakeをsupport/helper.js内でオーバーライドしてしまい、
cookieをx-set-cookieというキーでヘッダに格納しているようです。

support/helper.jsに補助的なメソッドを追加&テスト用にcookieをセットする、という手法を使わせて頂き、
socket.ioの認証テストは以下のように。

var should = require('should')
  , expect = require('expect.js')
  , helper = require('./support/helper')
  , app = require('../app.js')

var options = {
    transports : ['websocket'],
    'force new connection' : true,  //別々のコネクションとして認識させるために必要
    port : app.get('port'),
};


//socket.ioの接続テスト
describe('socket.io test', function() {
    before(function(done) {
        helper.initDataStore(done);
    });
    
    it('ログイン前はsocket.ioで接続できないはず', function(done) {
        this.timeout(10000);
        helper.login({
            auth : {
                userID : 'test',
                password : 'wrong password'
            },
            server : {
                host : "",
                details : options
            }
        }, function(err, client) {
            //接続できてはいけない
            client.on('connect', function(data) {
                done("wrong behaviour");
            });
            
            //エラーが起こるはず
            client.on('error', function(data) {
                //ハンドシェイクのエラーのはず
                should.equal(data.toString(),"handshake error");
                done();
            });
        });
    });
    it('ログインした後はsocket.ioで接続できるはず', function(done) {
        this.timeout(10000);
        helper.login({
            auth : {
                userID : 'test',
                password : 'test'
            },
            server : {
                host : "",
                details : options
            }
        }, function(err, client) {
            client.on('connect', function(data) {
                //接続完了するはず
                done();
            });
        });
    });
});
helper.loginではログインしてexpressのセッションの発行を受けたのち、コールバック関数でsocket.io-clientのsocketインスタンスを返しています。

socket.ioの認証でエラーが起きるとerrorイベントが発火するためclient.on(‘error’, function(data) {})の用にしてそのエラーをキャッチするようにしたいます。
testUserはログイン済みのため接続できてconnectionイベントが発火しますが、invalidUserはログインしていないため接続できずにerrorイベントが発火します。


メッセージ交換が出来るかどうかのテストも以下のように簡単に行うことが出来ます。
    it('メッセージの送受信がclient0とclient1でできるかどうか', function(done) {
        this.timeout(10000);
        var maxCount = 2;
        var clientCount = 0;
        var clients = [];
        
        var connectCompleat = function() {
            clients[0].on("message",function(message){
                //client0が受信したメッセージはclient1が送信したメッセージなはず
                should.equal(message, clients[1].userID + ' ' + "message");
                
                //テスト終了前に切断する
                clients[0].disconnect();
                clients[1].disconnect();
                done();
            });
            clients[1].emit("chat","message");
        }; 
        
        for (var i = 0; i < maxCount; i++) {
            (function(userID) {
                var client = helper.login({
                    auth : {
                        userID : userID,
                        password : 'test'
                    },
                    server : {
                        host : "",
                        details : options
                    }
                }, function(err, client) {
                    client.userID = userID;
                    clients.push(client);
                    
                    //client0とclient1が接続するまで待つ
                    client.on('connect', function(data) {
                        if ((++clientCount) == maxCount) {
                            connectCompleat();
                        }
                    });
                });
            })('test' + i);
        }
    });

1000人同時接続のテストの際にファイルディスクリプタ最大数が原因だと気づかずにはまってしまいましたが、
それさえ忘れなければ普段クライアントで利用しているコードがサーバ側でも使えるのは便利ですね。


前々回・前回・今回のサンプルコードを全て含めたものはpxsta / express-socket.io-chat-testです。

このエントリーをはてなブックマークに追加
はてなブックマーク - socket.io-clientを用いてExpress+Socket.IOで作ったサービスへ同時接続テストを行う

mochaとsuperagentでexpressを使ったサービスのログイン周りのテスト

このエントリーをはてなブックマークに追加
はてなブックマーク - mochaとsuperagentでexpressを使ったサービスのログイン周りのテスト

前回のexpress+soket.ioでのセッション管理に続いて、今回はexpressのセッション管理のテストについて。

mochasuperagentを使えば

  • ページの表示確認(ステータスコード・ページbody本文)
  • ログインページヘのpostで正しくログイン出来るか
  • ログイン状態管理(セッションでの振り分けが出来ているか・リダイレクトが正しく行われているか)
などのログイン周りのテストが簡単に出来るようなので試してみました。

ソースコードとサンプル

コード
mochaとsuperagentを用いてexpressのアプリのログイン周りのテスト — Gist
.
├── app.js
├── chat.html
├── index.html
├── node_modules
│   ├── connect-redis
│   ├── cookie
│   ├── ejs
│   ├── express
│   ├── should
│   ├── socket.io
│   └── superagent
├── package.json
└── test
     ├── app.test.js
     └── mocha.opts


テスト対象の動作サンプル
Socket.io-Express3.0.0rc3 session sample
簡単なチャット。passwordがtestなら何でもログイン可能。やけに時間がかかるのはherokuだから?


やったこと

インストール

npm install mocha
npm install superagent
npm install should
shouldもとりあえず。
mochaについてはテストフレームワーク mocha – hokaccha.hamalog v2がとても参考になりました。

app.js側の準備

基本的に前回とほとんど同じですが、expressの設定とsessionStoreをテストコードから参照するために2行ほど書き換えます。
var app = module.exports = express();
app.configure(function() {
    //テスト用
    app.set("sessionStore",sessionStore);
});


ログインが必要なページのテスト

まず、/chatにアクセスするためにはログインが必要。
ログイン前に直接アクセスすると/にリダイレクトされるはずです。そのテストを行うコードは次のようになります。
var url = require('url')
  , should = require('should')
  , assert = require('assert')
  , superagent = require('superagent');

describe('HTTP Server Test', function() {
    //テストに使用するユーザーを作成
    var testUser = superagent.agent();
    
    it('ログイン前にチャットページにアクセスしたらログインページにリダイレクトされるはず', function(done) {
        testUser.get('http://localhost:3000/chat').end(function(err, res) {
            should.not.exist(err);
            res.redirects.should.not.be.empty;
            should.equal(url.parse(res.redirects[0], false, true).pathname, "/");
            done();
        });
    });
});
superagent.agent()でユーザーを作成して、
getでHTTP GETの要求を/chatにした後リダイレクト先が/であることを確かめています。

testディレクトリにapp.test.jsなどという名前で保存してmochaを実行すれば
$ mocha
   info  - socket.io started

  ․

  ✔ 1 test complete (17ms)
などと表示され無事にテストが通ったことが分かります。

ログイン処理のテスト

次に、/ページではログインを行います。
ログインフォームは
<form action="/user/login" method="post">
    ID: <input type="text"  name="userID" value="test"/> <br/>
    PASS: <input type="text"  name="password" value ="test"/>
    <input type="submit" value="login" />
</form>
postを受け付ける部分は
//ログイン処理
app.post('/user/login', function(req, res) {
    var postData = {
        userID : req.body.userID,
        password : req.body.password
    };
    //passがtestならログイン成功させる
    if ( typeof postData.userID !== 'undefined' && typeof postData.password !== 'undefined' && postData.password.toString() === 'test') {
        //sessionにユーザーID保存
        req.session.userID = postData.userID.toString();
        res.redirect('/chat');
    }
    else {
        console.log('login failed');
        res.redirect('/');
    }
});
のようになっていて、/user/loginにHTTP POSTでuserID, passwordを送るようになっています。

superagentではユーザーごとのcookieの管理が出来るため、セッション管理のテストもできます。
このログイン処理のテストを行うコードをmocha+superagentで書くと以下のように。
describe('HTTP Server Test', function() {
    //テストに使用するユーザーを作成
    var testUser = superagent.agent();  //正規にログインするユーザー
    var invalidUser = superagent.agent(); //ログインしないユーザー

    it('ログインに成功したら/chatにリダイレクトされるはず', function(done) {
        //ログインページに対してのPOST送信でログイン処理を行う
        testUser.post('http://localhost:3000/user/login').send({
            userID : 'test',
            password : 'test'
        }).end(function(err, res) {
            should.not.exist(err);
            res.redirects.should.not.be.empty;
            should.equal(url.parse(res.redirects[0], false, true).pathname, "/chat"); 
            done();
        });
    });

    it('ログイン済みのtestUserは/chatに直接アクセスすると/chatにアクセスできるはず', function(done) {
        testUser.get('http://localhost:3000/chat').end(function(err, res) {
            should.not.exist(err);

            //ページが表示され、それは/chatなはず
            should.equal(res.statusCode, 200);
            should.equal(res.req.path, "/chat");
            done();
        });
    });

    it('ログインしてないinvalidUserは/chatに直接にしても/にリダイレクトされるはず', function(done) {
        invalidUser.get('http://localhost:3000/chat').end(function(err, res) {
            should.not.exist(err);

            //リダイレクト先が存在し、/にリダイレクトされるはず
            res.redirects.should.not.be.empty;
            should.equal(url.parse(res.redirects[0], false, true).pathname, "/");
            done();
        });
    });
});

testUserとinvalidUserの2ユーザーを作り、testUserは.post(‘user/login’).send({userID : ‘test’,password : ‘test’})でログイン処理を。
testUserはログイン済みのため/chatにアクセスできますが、invalidUserは未ログインなため/にリダイレクトされます。

セッションストアにデータが収められているかのテスト

最後に、sessionStoreに正しくセッション情報が保存されているかどうかのテスト。
ヘッダのcookieからセッションIDを取り出して、それを元にセッションストアからセッションデータを取り出してユーザー名を確かめます。
これはsocket.ioの認証時にも同じ事をしています。

var url = require('url')
  , connect = require("express/node_modules/connect")
  , should = require('should')
  , assert = require('assert')
  , superagent = require('superagent')
  , app = require('../app.js');


describe('HTTP Server Test', function() {
    //テストに使用するユーザーを作成
    var testUser = superagent.agent();

    it('ログインに成功したらセッションストレージにユーザー名が保存されているはず', function(done) {
        testUser.post('http://localhost:3000/user/login').send({
            userID : 'test',
            password : 'test'
        }).end(function(err, res) {
            should.not.exist(err);
            
            //sessionStoreにsessionデータが保存されているはず
            var header_cookie = res.req._headers.cookie;
            var cookie = require('cookie').parse(decodeURIComponent(header_cookie));
            cookie = connect.utils.parseSignedCookies(cookie, app.get('secretKey'));
            var sessionID = cookie[app.get('cookieSessionKey')];
            
            app.get("sessionStore").get(sessionID,function(err,session){
                 should.not.exist(err);
                 
                 //userIDがログイン時のものと一致するはず
                 should.equal(session.userID,"test");
                 done();
            });
        });
    });
});
sessionStoreにセッション情報が保存されていることを確認。

と、いうことで、mocha+superagentを使えばログイン処理まわりのテストも簡単に出来そうです!
superagentを使えば複数人の接続のテストも出来るかと!

今回のサンプルコードはここにまとめて置いておきました。
https://gist.github.com/3968061

このエントリーをはてなブックマークに追加
はてなブックマーク - mochaとsuperagentでexpressを使ったサービスのログイン周りのテスト

Express3.x(connect2.3+)とSocket.ioでのセッション管理

このエントリーをはてなブックマークに追加
はてなブックマーク - Express3.x(connect2.3+)とSocket.ioでのセッション管理

久しぶりにnode.jsを触ったらexpressがいつの間にか2.xから3.0.3rcになっていて微妙に挙動が違う!
そしてconnect@2.3.2からはparseCookieが無くなってて昔のままだとセッションの共有が出来ない!


そして一番ハマったのはセッションIDが署名されているにも関わらずcookieからparseCookieで取り出した値をそのままセッションIDとして使っていたために、
req.sessionIDとcookieから取り出したsessionIDが別のものになっていて
sessionStoreから正しくセッションデータを取り出せなかった、ってこと。
署名したセッションID入りのcookieはconnect.utils.parseSignedCookies(cookies:object, secret:string)で元に戻しましょう。

と、いうことでexpress3.xとsocket.ioを使った簡単なチャットを新たに作ってみました。

今回の環境

  • node.js v0.8.7
  • express@3.0.0rc3
  • connect@2.4.3
  • socket.io@0.9.10

ソースコードとサンプル

コード
Socket.io-Express3.x-session — Gist

サンプル
Socket.io-Express3.0.0rc3 session sample
簡単なチャット。passwordがtestなら何でもログイン可能。やけに時間がかかるのはherokuだから?
(herokuってこんなに遅いんですか?)

やったこと

connect2.3以降はparseCookieがconnect内に無いため、変わりにcookieモジュールを使う。npmから普通に入る。
npm install cookie

express3.xからはexpress()がhttp.Server型のオブジェクトを返さなくなったため、http.createServerでhttp.Server型のインスタンスを生成してからsocket.ioに渡す。
var express = require('express')
  , app = express()
  , http = require('http')
  , io = require('socket.io').listen(http.createServer(app).listen(3000));

また、express.sessionミドルウェアがsecretパラメータを使用しなくなり、変わりにexpress.cookieParserがsecretパラメータを受け付けるようになった。
secretパラメータの値を元に、expressのセッションが署名される。
//メモリストアかRedisのどちらかでセッションを保存
//var sessionStore = new express.session.MemoryStore()
var RedisStore = require('connect-redis')(express)
  , sessionStore = new RedisStore();

app.configure(function() {
    app.set('secretKey', 'mySecret');
    app.set('cookieSessionKey', 'sid');

    //expressでセッション管理を行う
    app.use(express.cookieParser(app.get('secretKey')));     //セッションの署名に使われるキーを設定
    app.use(express.session({
        key : app.get('cookieSessionKey'),     //cookieにexpressのsessionIDを保存する際のキーを設定
        store : sessionStore
    }));
});
express.cookieParserに渡した値でセッションが署名される。
また、express.sessionのkeyパラメータはブラウザにセッションIDが保存される際のキーとなる。
storeに指定したストレージにセッション情報が保存される。今回はRedis。


そして肝心のsocket.ioの認証部分。ここでsocket.ioとexpressのセッション情報を紐つける。


今までのようにconnectのparseCookieが使えないため、cookieモジュールのparseCookieを用いる。
また、cookie中のセッションIDは署名されているため、単にparseCookieに掛けただけではreq.sessionIDと異なるセッションIDしか取れない。sessionStoreにはreq.sessionIDと同じ値がキーとなって保存されているためそれは不都合。

res.cookie(name, val, options)にてcookieをセットする際にutils.sign(val, secret)にて署名が行われ、セッションIDは
s:' + セッションID + '.' + secretのハッシュ値
という形に変換されている。
そのためpaeseCookieだけでは「s:本来のセッションID.ハッシュ値」という値しか取れない。

さらに、handshakeData.headers.cookieはURLエンコードされているので、
s%3A:' + URLエンコードされたセッションID + '.' + secretのハッシュ値
というように頭の「s:」 が「s%3A」となって格納されている。

そこで、decodeURIComponentでURLデコードしてたのち同じくutils.js内のutil.parseSignedCookiesを用いて本来のセッションIDを取得する。


ということでsocket.ioの認証部分。parseSignedCookiesにcookieのオブジェクトと署名に用いた値を渡すだけ。
//socket.ioのコネクション認証時にexpressのセッションIDを元にログイン済みか確認する
io.set('authorization', function(handshakeData, callback) {
    if (handshakeData.headers.cookie) {
        //cookieを取得
        var cookie = require('cookie').parse(decodeURIComponent(handshakeData.headers.cookie));
        //cookie中の署名済みの値を元に戻す
        cookie = connect.utils.parseSignedCookies(cookie, app.get('secretKey'));
        //cookieからexpressのセッションIDを取得する
        var sessionID = cookie[app.get('cookieSessionKey')];

        // セッションデータをストレージから取得
        sessionStore.get(sessionID, function(err, session) {
            if (err) {
                //セッションが取得できなかったら
                console.dir(err);
                callback(err.message, false);
            }
            else if (!session) {
                console.log('session not found');
                callback('session not found', false);
            }
            else {
                console.log("authorization success");

                // socket.ioからもセッションを参照できるようにする
                handshakeData.cookie = cookie;
                handshakeData.sessionID = sessionID;
                handshakeData.sessionStore = sessionStore;
                handshakeData.session = new Session(handshakeData, session);

                callback(null, true);
            }
        });
    }
    else {
        //cookieが見つからなかった時
        return callback('cookie not found', false);
    }
});


だらだらと書いてきましたが、結局のところ
app.config(function(){
    app.use(express.cookieParser("署名に使うキー"));
    app.use(express.session({
        key : "expressのセッションIDがcookieに保存される際のキー(デフォルトはconnect.sid)",
        store : sessionStore
    }));
});
のように署名に使うキー、cookieに保存される際のキー、ストレージを設定して、
socket.ioの認証部分で
//ヘッダーからcookie取得
var cookie = require('cookie').parse(decodeURIComponent(handshakeData.headers.cookie));
//cookie中の署名済みの値を上で設定した'署名に使うキー'を元にして戻して
cookie = connect.utils.parseSignedCookies('署名に使うキー');
var sessionID = cookie[app.get('cookieSessionKey')];
署名されたcookieからconnect.utils.parseSignedCookiesで元のsessionIDを取得しましょう、ただそれだけです。
cookieからparseCookieして取得したsessionIDとreq.sessionIDが違うせいでsessionStoreからsessionデータを取得できない、エラーはないけどundefinedになる、というのに中々気づけず結構ハマったので。

このエントリーをはてなブックマークに追加
はてなブックマーク - Express3.x(connect2.3+)とSocket.ioでのセッション管理

Canvas+node.js+socket.ioで簡易オンラインゲーム作ってみた

このエントリーをはてなブックマークに追加
はてなブックマーク - Canvas+node.js+socket.ioで簡易オンラインゲーム作ってみた

Canvas+node.js+socket.ioで簡易オンラインゲーム作ってみました!
ほんとに簡易!


もともとはサークルの交流会のLTデモのためにちょちょっと作ったものなのでゲーム性はほとんどないです。
作成時間は半日くらい。その後の細かな手直しに4時間くらい。

js暦2ヶ月、node.js暦1ヶ月という短さですが、node.jsとsocket.ioの手軽さには本当に感服です!

サンプル

とりあえずブラウザでアクセス
Chrome16、Firefox7で動作確認。表示だけならAndroid2.3でも可。
※Avastなどのウイルス対策ソフトが動いてるとサーバーと接続できないみたいです

2chのプログラミングスレで晒してもなんとか動いてたので、たぶん、普通に動くはずです。

操作方法

矢印キーで移動、スペースキーで加速
物理をすっかり忘れてしまって未知なる力で動かしてるので、加速回りは怪しいかも

CPUもいますが勝手に自殺しちゃうので、
Chromeのタブ+シークレットウィンドウ使うか、Firefox+Chromeでアクセスすれば自分対自分ができます。

開発環境

node.js 0.6.0
socket.io 0.8.7
express 2.5.1

ソース

最近git使い始めたので、なんとなくgithubにもあげてみました。
https://github.com/pxsta/OnlineBallAtack-socketio

仕様

サーバ側でゲームのメインループまわして移動量計算・あたり判定、
クライアント側は定期的にボールの位置情報を受信してCanvasに描写してるだけです。

ユーザーの識別にはSocket.ioのセッションIDを使用。
再接続/リロード時のためにExpressのセッションIDとSocket.ioのセッションIDを紐つけています。
ExpressのセッションIDはサーバのみが保持して、クライアント側には他クライアントのセッションIDは知らせていません。

サーバ(メインループ部のみ)

src/server.js
//メインループ
var run = function()
{
    setInterval(function(){
           update();
           sync();
        },1000.0/MyApp.config.FPS);
};

//ゲームデータの更新
var update = function()
{
    var ballArray = MyApp.ballList.toArray();
    for(var i=0;i<ballArray.length;i++){
        //ここであたり判定とかボールが落ちてないかの確認とか
    }
    
    //各ボールを現在のスピード等に基づいて移動させる
    MyApp.ballList.update(param);
};

//クライアントに同期させる
var sync = function(){
    var ballArray = MyApp.ballList.toArray();
    var sendMessage = {ballList:MyApp.ballList.toJSON()};
    
    for(var i=0;i<ballArray.length;i++){
        //各クライアントにすべてのボールの位置情報を送る
        var socketID = ballArray[i].getSocketID();
        io.sockets.socket(socketID).volatile.emit("updateBallInfo",JSON.stringify(sendMessage));
    }
}

サーバ側で、updateとdrawを定期的に実行というありがちなメインループをまわす。
今回はdrawをsyncに変えて、描写の代わりにクライアントにゲームの状態を送信。

クライアント

src/client/client.js
//サーバからボールの情報を受信したときのイベントハンドラ
connection.on('updateBallInfo', function (msg) {
    var messageJson = JSON.parse(msg);
    
    //ボールの状態を更新する
    MyApp.ballList.get(messageJson.ballList[i].socketID).updateValue(messageJson.ballList[i]);    
});

//メインループ
var run = function()
{    
    MyApp.mainLoopID=setInterval(function()
    {
        update();
        draw();
    },1000.0/MyApp.config.FPS);
};

var update = function(){
    //落ちたボールをリストから削除したり
    //自分に落ちてるフラグがセットされてたら画面メインループ停止させたり
    //サーバから受信した各ボールの情報をもとにゲームオーバー判定とかするだけ。
};

//canvasに描写する
var draw = function()
{
    var ctx = MyApp.context;
    var offset = MyApp.getOffset();
    
    //Canvasをゼロクリアする
    ctx.clearRect(0,0,MyApp.canvasSize.width,MyApp.canvasSize.height);


    //マップを描写
    MyApp.map.draw(ctx,MyApp.getOffset());
    
    // ボールを描写
    MyApp.ballList.draw(ctx,MyApp.getOffset());
     
};
“updateBallInfo”としてサーバから定期的に送られてくるボール情報を受信して、ローカルに各ボールの状態を更新・保存、
その状態を元にボールを描写。

ボールの移動

src/client/client.js
$(window).keydown(function(e){
    //サーバにキー押下情報を送る
    connection.emit("keydown",code);
}
ボールの移動は、
クライアント側はキーイベントを監視して押されたボタンの情報をサーバに送るのみ。

src/server.js

//クライアントがキーを押した時
connection.on('keydown',function(code){
    var moveVector={x:0,y:0,mode:"normal"};
    if(37<=code&&code<=40||code==32){
        if(code==37){
            moveVector.x+=(-1);
        }
        else if(code==39){
            moveVector.x+=1;
        }
        else if(code==38){
            moveVector.y+=(-1);
        }
        else if(code==40){
            moveVector.y+=1;
        }
        else if(code==32){
            //ターボ
            moveVector.mode="tarbo";
        }
    }
    //ボールの行動バッファに加える
    MyApp.ballList.get(connection.id).setActionBuffer(function(){
        MyApp.ballList.get(connection.id).addSpeed(moveVector);
        MyApp.ballList.get(connection.id).setMode(moveVector.mode);
    });
});
サーバ側は受信したキー押下情報を元にスピードの上げ下げ。
バッファに1つだけ保存して、次のupdate内で実行。

クライアントからkeydownメッセージ受信した時点でスピード更新してもいい気がしたけど、
あるクライアントはメッセージ送信が1ms/回、あるクライアントは500ms/回とかだとまずい気がしたのでバッファに1つだけ保存して、updateで定期的に実行するようにしました。

とりあえず

updateとdrawの定期実行というよくあるメインループをサーバ側でやらせて、
drawをsyncに変えて描写の変わりにクライアントにゲーム状態を送るようにして、
クライアントではupdateの代わりにイベントハンドラでゲームの状態を受信して、それをdrawで定期的に描写してるだけです。

チート的なことをされないために、あたり判定などのメインループのコードはサーバ側だけに書いてます。
ボール移動の際も、クライアントからキーコードを送ってもらうだけで、具体的な処理はサーバ側で行っています。

ballAtack.jsでボールの基本的なクラスを定義して、serverBallAtack.jsでballAtack.jsの各クラスを継承して、ExpressのセッションID・ボール移動処理などのサーバのみに必要な値・メソッドを追加しています。

問題点

  • サーバは60FPSでゲームの状態を送信してますが、それに通信速度が追いつかないと問答無用でカクカクします。
  • 通信途切れたら動けません(再接続はできます)
  • クライアントからsocket.emit(“hoge”,”あばばばば・・・(1MB分続く・・・)”)みたいなの実行されて巨大なデータ送られ続けたらどうなるんだろう?
これでも読めば何か変わるんでしょうかね。


敷居の低さ

そもそもこれを作ったのは大学のサークル同士の交流会のLTのためなのですが、

前日になって、
スライドできてない><→node.js+socket.ioのことでも話すか!→デモでも作って穴埋めしよう→これならサクッと作れそう!
という単純な動機で作りました。

js暦2ヶ月、node.js暦1ヶ月ちょいの自分ですが、それでも半日程度で作ることができました。
そもそも、通信部分はサーバ/クライアント合わせてわずか100行ちょっとで、その他は普通のゲームと同じです。

ゲームに限らず、webサービスでリアルタイムなコンテンツをサクッと追加するのにも使えそうです!
何かおもしろいものないかなあ。

このエントリーをはてなブックマークに追加
はてなブックマーク - Canvas+node.js+socket.ioで簡易オンラインゲーム作ってみた

OpenCVで虹顔の識別(※ただし表情がとぼしくて可愛いおにゃのこに限る)

このエントリーをはてなブックマークに追加
はてなブックマーク - OpenCVで虹顔の識別(※ただし表情がとぼしくて可愛いおにゃのこに限る)

はい、ちょっと***な諸事情で、画像中に全く同じ画像が含まれているかを調べる必要があったのですが、自力ではうまく行かず、
OpenCV使ったら一瞬で出来てしまって不覚にも感動してしまいました。
、という更新。

こんな紛らわしい画像(右側は微妙に口の形が違う)も「違う」と判断してくれてさすがです。

 

、何がしたかったのかというと、虹画像の表情を判断したかったわけで、
さらにその表情ごとの画像は一意に決まってるというまあなんともレアケース。

笑ってるときは画像01、泣いてるときは画像02、みたいに。
表情が乏しいとも言います、はい。

むすっとした顔の穹ちゃんはかわいいけどね!!

、これらの画像から、下のおにゃのこの表情が何なのかを調べてみますと、
    

まあよく分かりませんが「一番左の画像が完全に一致」、と判定してくれるわけです。
OpenCV便利すぎくそわろうた、です。

今回はなんとなくC#でOpenCvSharp使いました。
OpenCv:2.1
.NET:4.0
公式のサンプル引っ張ってきただけです、はい。

要なのはこの2行だけで、

Cv.MatchTemplate(target, template, dst_img, MatchTemplateMethod.CCorrNormed);
Cv.MinMaxLoc(dst_img, out  min_val, out  max_val, out  min_loc, out  max_loc, null);

MatchTemplateで各点の相関係数をdst_imgに、
MinMaxLocで相関係数の最大値と最小値とそれぞれの位置を取得できるみたいです。

相関係数が1に近いほど正の相関があるので1に近ければ近いほど、類似の画像が含まれているということになります。
相関係数が1のときは完全に一致。

似たような画像でも0.998とか高めの値が出たので今回はしきい値を0.999にしました、テキトウw

using System;
using System.Collections.Generic;
using System.Linq;
using OpenCvSharp;
using System.IO;

namespace CVSharpTest
{
    class Program
    {        
        static void Main(string[] args)
        {
            //templateフォルダ以下にテンプレート画像を入れておく。
            IEnumerable<string> tempFiles = Directory.EnumerateFiles("./template","*",SearchOption.TopDirectoryOnly);
            Dictionary<string, double> NCCPair = new Dictionary<string, double>();

            IplImage target = IplImage.FromFile("target.png",LoadMode.Unchanged);            
            IplImage template;
           
            foreach (string file in tempFiles)
            {                
                template = IplImage.FromFile(file, LoadMode.Unchanged);
                double ncc = GetNCCMax(target,template);

                //対象画像からテンプレート画像を検索して表示する
                TemplateMatch(target, template);
               
                NCCPair.Add(file, ncc);
                Console.WriteLine("{0}:{1}", file, ncc);
            }

            //「相関係数の最大値が一番高いもの」が一致画像の可能性が高い
            var result = NCCPair.OrderByDescending((val) => val.Value).First();
            Console.WriteLine("MAX:{0} {1}",result.Key,result.Value);
           
            return;
        }
       
        /// <summary>
        /// 相関係数の最大値を計算する
        /// </summary>
        private static double GetNCCMax(IplImage target, IplImage template)
        {
            IplImage dst_img;                        
            double min_val, max_val;
            CvPoint min_loc, max_loc;
            CvSize dst_size;

            dst_size = new CvSize(target.Width - template.Width + 1, target.Height - template.Height + 1);
            dst_img = Cv.CreateImage(dst_size, BitDepth.F32, 1);
            Cv.MatchTemplate(target, template, dst_img, MatchTemplateMethod.CCorrNormed);
           
            //相関係数の最大値と最小値を取得
            Cv.MinMaxLoc(dst_img, out  min_val, out  max_val, out  min_loc, out  max_loc, null);
           
            Cv.ReleaseImage(dst_img);

            //相関係数の最大値を返す
            return max_val;
        }

        /// <summary>
        /// 対象画像からテンプレート画像の位置を探す
        /// </summary>        
        private static double TemplateMatch(IplImage target, IplImage template, double limen=0.999)
        {
            IplImage dst_img;
            double min_val, max_val;
            CvPoint min_loc, max_loc;
            CvSize dst_size;

            dst_size = new CvSize(target.Width - template.Width + 1, target.Height - template.Height + 1);
            dst_img = Cv.CreateImage(dst_size, BitDepth.F32, 1);
            Cv.MatchTemplate(target, template, dst_img, MatchTemplateMethod.CCorrNormed);

            Cv.MinMaxLoc(dst_img, out  min_val, out  max_val, out  min_loc, out  max_loc, null);

            if (max_val > limen)            
                Cv.Rectangle(target, max_loc,new CvPoint(max_loc.X + template.Width, max_loc.Y +template.Height),new CvScalar(0,0,250),3);                
           
            Cv.NamedWindow("Image", WindowMode.AutoSize);
            Cv.NamedWindow("Image2", WindowMode.AutoSize);
            Cv.ShowImage("Image", target);
            Cv.ShowImage("Image2", template);

            Cv.WaitKey(0);
            Cv.DestroyWindow("Image");
            Cv.DestroyWindow("Image2");  
            Cv.ReleaseImage(dst_img);

            return max_val;
        }
    }
}

今回の本当の目的はちょっとぴーぴーぴーなことなので詳しくはまあアレなのですが、
OpenCVの便利さ、そして手軽さはすごいなあと。

このエントリーをはてなブックマークに追加
はてなブックマーク - OpenCVで虹顔の識別(※ただし表情がとぼしくて可愛いおにゃのこに限る)

ListViewコントロールで文字列による ListViewItemの検索・列挙

このエントリーをはてなブックマークに追加
はてなブックマーク - ListViewコントロールで文字列による ListViewItemの検索・列挙

標準で用意されてると思いきやなかった。ぐぐっても気に入ったものひっかからないのでメモ。
C#3.0以上の拡張メソッド。ListViewItemのテキストまたはSubItemsのテキストから一致するものを検索。

static class Extensions
{
    public static ListViewItem[] Find(this ListView listView, string keyWord)
    {
        return FindSubItems(listView, keyWord, 0);
    }

    public static ListViewItem[] FindSubItems(this ListView listView, string keyWord, int index)
    {
        List<ListViewItem> hitItemList = new List<ListViewItem>();
        foreach (ListViewItem item in listView.Items)
            if (item.SubItems[index].Text.Contains(keyWord))
                hitItemList.Add(item);

        return hitItemList.ToArray<ListViewItem>();
    }
}

拡張メソッドの禁じ手とか知らないのだがこれはいいのだろうか。
元から同じ名前のメソッドがある場合は元のほうが優先して呼び出されるようだ。

検索して引っかかった項目だけ表示~っていうのがやりたかっただけ。
というか項目の表示・非表示を切り替えられればいのだが。

一回項目を全て保存しておいて、項目全部消して検索結果の項目追加して~もう一回検索するときは消して追加して・・・っていう流れが適当

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    List<ListViewItem> itemList = new List<ListViewItem>();

    private void Form1_Load(object sender, EventArgs e)
    {
        itemList.Add(new ListViewItem(new string[] { "1", "灼眼のシャナ"}));
        itemList.Add(new ListViewItem(new string[] { "2", "sola" }));
        itemList.Add(new ListViewItem(new string[] { "2", "灼眼のシャナII" }));
        itemList.Add(new ListViewItem(new string[] { "3", "true tears" }));
        itemList.Add(new ListViewItem(new string[] { "4", "そらのおとしもの" }));
        itemList.Add(new ListViewItem(new string[] { "5", "とらドラ!" }));
        itemList.Add(new ListViewItem(new string[] { "6", "劇場版 灼眼のシャナ" }));

        listView1.Items.AddRange(itemList.ToArray());
    }

    private void textBox1_TextChanged(object sender, EventArgs e)
    {
        string keyWord = textBox1.Text;

        listView1.Items.Clear();
        listView1.Items.AddRange(itemList.ToArray<ListViewItem>());

        ListViewItem[] hitItem = listView1.FindSubItems(keyWord, 1);
        listView1.Items.Clear();
        listView1.Items.AddRange(hitItem);
    }
}

このエントリーをはてなブックマークに追加
はてなブックマーク - ListViewコントロールで文字列による ListViewItemの検索・列挙

C#でOAuth認証でTwitterにpost

このエントリーをはてなブックマークに追加
はてなブックマーク - C#でOAuth認証でTwitterにpost

【追記】2010/5/12 生成後のsignatureをパーセントエンコードしていなかったミスを修正
signature生成に必要なパラメータは大文字、signature自体は小文字でURLエンコードする必要があったようだ。

————————————————————————-
C#でOAuth認証でTL取得とほとんど変わらない。
signatureを生成するとき、signature生成前にstatus(post本文)をURLエンコードしておかないとうまく認証されないみたい。

これでひとまずぼとすたやらシャナbotのOAuthへの移行へめどが立った。
おかしなところ指摘していただけると助かります。

Read more »

このエントリーをはてなブックマークに追加
はてなブックマーク - C#でOAuth認証でTwitterにpost

C#でOAuth認証でTL取得

このエントリーをはてなブックマークに追加
はてなブックマーク - C#でOAuth認証でTL取得

【追記】2010/5/12 生成後のsignatureをパーセントエンコードしていなかったミスを修正
signature生成に必要なパラメータは大文字、signature自体は小文字でURLエンコードする必要があったようだ。

————————————————————————-

tokenとtoken_secretが取得できたところで、TwitterAPIをOAuth認証で利用してタイムラインを取得してみた。
basic認証とは違ってsignatureの生成が面倒だが、そのほかはほとんど変わらない。

まずは以下のパラメータと、consumer_secret,token_secretを利用してsignatureを生成する。

  • oauth_consumer_key(登録時に発行)
  • oauth_nonce(ランダムな毎回異なる8文字以上の英数字)
  • oauth_token(ここで取得)
  • oauth_version(“1.0″を指定)
  • oauth_timestamp(取得時間をUNIX時刻で)
  • oauth_signature_method(“HMAC-SHA1″を指定)
  • 各種APIで使うパラメータ(pageとかsince_idとか)

signatureの生成には、各種APIで使うパラメータ(pageとかsince_idとか)も含めたパラメータを使う。

signatureBaseはこんなかんじになる。
詳しくは検索で・・・だが、HttpMethodとエンコード済みのURL、エンコード済みのパラメータを連結したものを連結。
ここらへんが参考になった。


GET&http%3A%2F%2Ftwitter.com%2Fstatuses%2Ffriends_timeline.xml&oauth_consumer_key%3DCONSUMER_KEY%26oauth_nonce%3DNONCE%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3DTIMESTAMP%26oauth_token%3DTOKEN%26oauth_version%3D1.0%26page%3D2



ハッシュ値を求める際のキーには、consumer_secreとtoken_secretをURLエンコードし、&で結んだものを使用する。
signatureを生成したら、上記パラメータとsignatureをAPIに送るだけ。

例えばこんな感じで送る。


oauth_consumer_key=CONSUMER_KEY&oauth_nonce=NONCE&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1272290458&oauth_token=TOKEN&oauth_version=1.0&page=2&oauth_signature=CaQiJNAfkdIlfb3MxeF1F0P+jqg=

以下、コード。とりあえず動くレベル。


class OAuthTwitter
{
    const string consumer_key = "**********************";
    const string consumer_secret = "**********************";
    const string token = "**********************";
    const string token_secret = "**********************";

    //APIのURLとパラメータでAPIにアクセス
    private string GetAPI(string APIURL)
    {
        return GetAPI(APIURL, null);
    }

    //APIのURLとパラメータでAPIにアクセス
    private string GetAPI(string APIURL, Dictionary<string, string> query)
    {
        string result, queryString;
        result = queryString = string.Empty;

        //signature生成
        string signature = GenerateSignature(APIURL, "GET", query, consumer_secret, token_secret, out queryString);

        //生成したsignatureは小文字でパーセントエンコード
        string postString = queryString + string.Format("&oauth_signature={0}", UrlEncodeSmall(signature));

        //取得開始    
        byte[] data = Encoding.ASCII.GetBytes(postString);        
        WebRequest req = WebRequest.Create(string.Format("{0}?{1}",APIURL,postString));
        WebResponse res;
        try
        {
            res = req.GetResponse();
            Stream stream = res.GetResponseStream();
            StreamReader reader = new StreamReader(stream);
       
            result = reader.ReadToEnd();
            reader.Close();
            stream.Close();
        }
        catch (WebException ex)
        {
            if (ex.Status == WebExceptionStatus.ProtocolError)
            {
                if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Unauthorized)
                {
                    /*401 Unauthorized                    
                    *認証失敗*/

                    return "401 Unauthorized";
                }
                else if (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.BadRequest)
                {
                    /*400 Bad Request                    
                    *リクエストが不正*/

                    return "400 Bad Request";
                }  
            }
            else
            {
                return ex.Message;
            }
                   
        }
        return result;
    }

    //APIのURL,HttpMethod(POST/GET),パラメータ,consumer_secret,token_secretでsignature生成
    private string GenerateSignature(string url, string httpMethod, Dictionary<string, string> query, string consumer_secret, string token_secret, out string conectedQuery)
    {
        //SortedDictionaryでパラメータをkey順でソート
        SortedDictionary<string, string> sortedParams;
        if(query==null)
            sortedParams = new SortedDictionary<string, string>();
        else
            sortedParams = new SortedDictionary<string, string>(query);
   
        string timestamp = GenerateTimestamp();
        string nonce = GenerateNonce();
   
        sortedParams["oauth_consumer_key"] = consumer_key;
        sortedParams["oauth_token"] = token;
        sortedParams["oauth_version"] = "1.0";
        sortedParams["oauth_timestamp"] = timestamp;
        sortedParams["oauth_nonce"] = nonce;
        sortedParams["oauth_signature_method"] = "HMAC-SHA1";
   
        StringBuilder sb = new StringBuilder();
        bool first = true;
        foreach (var p in sortedParams)
        {
            if (first)
            {
                sb.Append(p.Key + "=" + p.Value);
                first = false;
            }
            else
                sb.Append(@"&" + p.Key + "=" + p.Value);
        }
        conectedQuery = sb.ToString();
        sstring signatureBace = string.Format(@"{0}&{1}&{2}", httpMethod, UrlEncode(url),UrlEncode(sb.ToString()));

        //consumer_secretとtoken_secretを鍵にしてハッシュ値を求める
        HMACSHA1 hmacsha1 = new HMACSHA1();
        hmacsha1.Key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", UrlEncode(consumer_secret), UrlEncode(token_secret)));
        byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(signatureBace);
        byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer);
   
        return Convert.ToBase64String(hashBytes);
    }

    private string GenerateNonce()
    {
        string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        StringBuilder result = new StringBuilder(8);
        Random random = new Random();
        for (int i = 0; i < 8; ++i)
            result.Append(letters[random.Next(letters.Length)]);
        return result.ToString();
    }

    private string GenerateTimestamp()
    {
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        return Convert.ToInt64(ts.TotalSeconds).ToString();
    }

    private string UrlEncode(string value)
    {
        string unreserved = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
        StringBuilder result = new StringBuilder();
        byte[] data = Encoding.UTF8.GetBytes(value);
        foreach (byte b in data)
        {
            if (b < 0x80 && unreserved.IndexOf((char)b) != -1)
                result.Append((char)b);
            else
                result.Append('%' + String.Format("{0:X2}", (int)b));
        }
        return result.ToString();
    }
    private string UrlEncodeSmall(string value)
    {
        string unreserved = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";

        StringBuilder result = new StringBuilder();
        byte[] data = Encoding.UTF8.GetBytes(value);
        foreach (byte b in data)
        {
            if (b < 0x80 && unreserved.IndexOf((char)b) != -1)
                result.Append((char)b);
                else
                result.Append('%' + String.Format("{0:x2}", (int)b));
            }
        return result.ToString();
        }
    }

class Program
{
     static void Main(string[] args)
    {
        System.Net.ServicePointManager.Expect100Continue = false;
       
        Dictionary<string,string> query = new Dictionary<string,string>();
        query["page"] = "2";
        OAuthTwitter oauthTwitter = new OAuthTwitter();      

        //TwitterAPIを利用してタイムライン取得
        Console.WriteLine(oauthTwitter.GetAPI("http://twitter.com/statuses/friends_timeline.xml",query));      
   }
}

このエントリーをはてなブックマークに追加
はてなブックマーク - C#でOAuth認証でTL取得

Dansette