Calmery.me

みっかぼうずにならないようがんばる

ElectronでTwitterのOAuth認証をする

これは Electron Advent Calendar 2016 - Qiita 15 日目の記事です.

はじめに

Electron で Twitter と連携したアプリケーションを作りたいけれど,どうすればアプリケーションから Oauth 認証できるのかということで作りました.
github.com

$ git clone https://github.com/calmery/ElectronAdventCalendar2016.git
$ cd ElectronAdventCalendar2016
$ npm -g install electron-prebuilt
$ npm install
$ electron .
// common.js
// Twitter API Token
consumerKey: 'YOUR_CONSUMER_KEY',
consumerKeySecret: 'YOUR_CONSUMER_KEY_SECRET',

実装

Node.js で Web サーバを立てて,それを Electron から操作するといった感じ.Node.js と Electron の間は socket.io を使ってやり取りを行うようにした.Web サーバには http モジュールと express を使っている.

実際に http モジュールを使い Web サーバを立てるときにポート番号を指定することができるが,自動でポート番号を割り当てることもできる.こちらからポート番号を指定すると,ポート番号が競合してしまったり,他のブラウザなどから開けてしまうので自動で割り当てた方が個人的にはいい気がする.

// ポート番号を指定して実行
server.listen( 3000 );

// ポート番号を指定せずに実行
server.listen();

// ポート番号を取得
server.listen().address().port;

passport を使って認証する

肝心の認証だが passport を使うと簡単にできた.詳しくは Node.js + Express + passport-twitterで認証Webアプリをつくる - Qiita などを参考にすると良さそう.API キーの指定とルーティングをするだけでよかった.

const passport        = require( 'passport' ),
      twitterStrategy = require( 'passport-twitter' ),
      session         = require( 'express-session' );

app.use( passport.initialize() );
app.use( passport.session() );
app.use( session( { secret: 'lectern' } ) );

app.get( '/oauth', passport.authenticate( 'twitter' ) );
app.get( '/callback', passport.authenticate( 'twitter', { 
    successRedirect: '/',
    failureRedirect: '/fail' 
} ) );

passport.serializeUser( function( user, done ){ done( null, user ) } );
passport.deserializeUser( function( user, done ){ done( null, user ) } );

passport.use( new twitterStrategy.Strategy( {
    consumerKey   : 'CONSUMER_KEY',
    consumerSecret: 'CONSUMER_KEY_SECRET',
    callbackURL   : 'http://127.0.0.1:' + port + '/callback'
}, function( token, tokenSecret, profile, done ){
    console.log( token, tokenSecret );
    ...
} ) );

js-yamlトークンを保存する

取得したトークンは js-yaml を使用して保存した.

// common.js
const jsYaml = require( 'js-yaml' );

module.exports = {
    
    getAbsolutePath: function(){
        return path.resolve( path.join.apply( this, [].slice.call( arguments ) ) );
    },
    
    writeFileSync: function( filePath, content ){
        try {
            fs.writeFileSync( this.getAbsolutePath( filePath ), content );
            return true;
        } catch( error ){
            return false;
        }
    },
    
    jsYaml: jsYaml
    
};

// express.js
const common = require( './common' );

common.writeFileSync( 'user.yaml', common.jsYaml.safeDump( {
    access_token: token,
    access_token_secret: tokenSecret
} ) );

こんな YAML ファイルが作られる.

access_token: YOUR_ACCESS_TOKEN
access_token_secret: YOUR_ACCESS_TOKEN_SECRET

ちなみにアプリケーションを立ち上げたとき,このアクセストークンを保存したファイルが無ければ認証ページにリダイレクトするようにしている.

app.get( '/', function( request, response ){
    if( common.exists( 'user.yaml' ) ) 
        response.sendFile( common.getAbsolutePath( 'public/index.html' ) );
    else 
        response.redirect( '/oauth' );
} )

TwitterAPI を使う

Twitter API の呼び出しは関数にまとめた.

// twitter.js
module.exports.getClient = function(){

    const config = common.yamlLoader( 'user.yaml' );

    const client = new twitter( {
        consumer_key       : common.consumerKey,
        consumer_secret    : common.consumerKeySecret,
        access_token_key   : config.access_token,
        access_token_secret: config.access_token_secret
    } );

    return {

        getUserTweet: function( condition ){
            return new Promise( function( resolve, reject ){
                client.get( 'statuses/user_timeline', makeOption( condition ), function( error, tweet, response ){
                    if( error === null ) 
                        resolve( tweet );
                    else 
                        reject( error );
                } );
            } );
        },
        ...
// index.js
io.sockets.on( 'connection', function( socket ){
    
    client = twitter.getClient()
    
    const emit = function( key, content ){
        io.sockets.to( socket.id ).emit( key, content );
    };
    
    const emitError = function( content ){
        emit( 'error', content );
    };
    
    socket.on( 'getUserTweet', function( user_id ){
        client.getUserTweet( user_id ).then( function( tweet ){
            emit( 'userTweet', tweet );
        }, emitError );
    } );
    
} );

ツイートを表示する

Electron 側での表示はこんな感じ.

<script src="/socket.io/socket.io.js"></script>
<script src="resources/preload.js"></script>
<script src="resources/index.js"></script>
// preload.js
var socket = io()

socket.on( 'error', function( error ){
    console.log( error )
} )

// index.js
socket.emit( 'getUserTweet', 'calmeryme' )

socket.on( 'userTweet', function( tweet ){
    
    for( var i=0; i<tweet.length; i++ )
        document.body.innerHTML += tweet[i].text + '<br><br>'
    
} )

指定したユーザのツイートを表示できた.
f:id:calmery:20161214211134p:plain

まとめ

Electron を使って Oauth 認証し,ユーザのツイートを取得することができた.Web の知識だけで簡単にアプリケーションを作ることができるのは本当にありがたい.Electron 万歳.あとは electron-packager を使って配布できる形式にすればいい.パッケージ化したアプリケーションの容量が大きくなってしまうことが難点だがよりアプリケーションっぽくなる.30分で出来る、JavaScript (Electron) でデスクトップアプリを作って配布するまで - Qiita が参考になった.もうほんとライブラリさまさまなので感謝しかない><