• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

command line interface based Twitter client


Commit MetaInfo

Revisiona089eb23fae184402f54847598deb4ed21225225 (tree)
Time2012-12-19 01:25:47
Authorhylom <hylom@hylo...>
Commiterhylom

Log Message

rewrite clienttweets.js

Change Summary

Incremental Difference

--- a/auth.js
+++ b/auth.js
@@ -2,31 +2,39 @@
22 var oauth = require('oauth');
33 var commander = require('commander');
44 var Twitter = require('ntwitter');
5+var fs = require('fs');
6+var configFile = '.account';
57
68 // OAuth parameters
79 var consumerKey = 'd3MqFro8tVXtlEzz5hVrZA';
8-var secretKey = 'RSWIniHQldyhhmLBj2PpP6V4gOnxf3vEEFUEZXT1Q';
10+var consumerSecret = 'RSWIniHQldyhhmLBj2PpP6V4gOnxf3vEEFUEZXT1Q';
911 var requestUrl = 'https://api.twitter.com/oauth/request_token';
1012 var accessUrl = 'https://api.twitter.com/oauth/access_token';
1113 var authUrl = 'https://api.twitter.com/oauth/authenticate';
1214
13-exports.authenticate = function (callback) {
15+/*
16+ * OAuth認証を行う
17+ * @param {Function(err, token, secret)} callback
18+ * 結果を受け取るコールバック関数。
19+ * err引数にはエラーオブジェクトが、token引数にはアクセストークンが、
20+ * secret引数にはsecret文字列が格納される
21+ */
22+function authenticate(callback) {
1423 var self = this;
1524 // Create authenticate client
1625 var client = new oauth.OAuth(
1726 requestUrl,
1827 accessUrl,
1928 consumerKey,
20- secretKey,
29+ consumerSecret,
2130 '1.0',
2231 'oob',
2332 "HMAC-SHA1"
2433 );
2534
26- // Send auth request to twitter
2735 client.getOAuthRequestToken(getAccessToken);
2836
29- // get access token from request token
37+ // リクエストトークンからアクセストークンを取得する
3038 function getAccessToken(err, token, secret, results) {
3139 if (err) {
3240 console.log('oauth request failed.');
@@ -36,31 +44,95 @@ exports.authenticate = function (callback) {
3644 console.log('Open URL printed below, and input displayed PIN code.');
3745 console.log(url);
3846 commander.prompt('Input PIN: ', function (pin) {
39- client.getOAuthAccessToken(token, secret, pin, createTwitterClient);
47+ client.getOAuthAccessToken(token, secret, pin, receiveToken);
4048 });
4149 }
4250
43- // create twitter object
44- function createTwitterClient(err, token, secret, results) {
51+ // アクセストークンを受け取る
52+ function receiveToken(err, token, secret, results) {
4553 if (err) {
46- console.log('oauth access token request failed.');
47- process.exit(1);
54+ callback(err);
55+ return;
4856 }
49- var twitter = self.createTwitterClient({
50- consumerKey: consumerKey,
51- consumerSecret: secretKey,
52- accessTokenKey: token,
53- accessTokenSecret: secret
54- }, callback);
57+ callback(err, token, secret);
5558 }
5659 }
5760
58-exports.createTwitterClient = function (keys, callback) {
59- var twitter = new Twitter({
60- consumer_key: keys.consumerKey,
61- consumer_secret: keys.consumerSecret,
62- access_token_key: keys.accessTokenKey,
63- access_token_secret: keys.accessTokenSecret
61+/*
62+ * ntwitter.Twitterクラスのインスタンスを生成する
63+ * @param {Object} keys アクセスに使用するキー
64+ * @returns 作成されたインスタンス
65+ */
66+function createTwitterClient(keys) {
67+ var twitter = new Twitter(keys);
68+ return twitter;
69+}
70+
71+/*
72+ * .accountファイルにアクセストークン情報を書き出す
73+ * @param {Object} twitter ntwitter.Twitterクラスのインスタンス
74+ * @param {Function(err)} callback エラーオブジェクトを返すコールバック関数
75+ */
76+function writeAuthenticationKeys(twitter, callback) {
77+ var keys = {
78+ access_token_key: twitter.options.access_token_key,
79+ access_token_secret: twitter.options.access_token_secret
80+ };
81+ fs.writeFile(configFile, JSON.stringify(keys, null, 2), 'utf8', callback);
82+}
83+
84+/*
85+ * .accountファイルに保存しておいたアクセストークン情報を読み出す
86+ * @param {Function(err, key)} callback コールバック関数。
87+ * err引数にはエラーオブジェクトが、
88+ * keys引数には読み出したアクセストークン情報を格納するオブジェクト
89+ * 格納される
90+ */
91+function readAuthenticationKeys(callback) {
92+ fs.readFile(configFile, 'utf8', function (err, data) {
93+ if (err) {
94+ callback(err);
95+ return;
96+ }
97+ var keys = JSON.parse(data);
98+ callback(err, keys);
99+ });
100+}
101+
102+/*
103+ * 認証を行ってntwitter.Twitterクラスのインスタンスを返す
104+ * @param {Function(err, twitter)} callback コールバック関数
105+ * err引数にはエラーオブジェクトが、
106+ * twitter引数には作成されたインスタンスが格納される
107+ */
108+exports.authenticate = function (callback) {
109+ var keys = {
110+ consumer_key: consumerKey,
111+ consumer_secret: consumerSecret
112+ };
113+ // ファイルに保存しておいたアクセストークンを読み出す
114+ readAuthenticationKeys(function (err, data) {
115+ // 読み出しに失敗したら認証を実行する
116+ if (err || data.access_token_key === undefined
117+ || data.access_token_secret === undefined ) {
118+ authenticate(function (err, token, secret) {
119+ if (err) {
120+ callback(err);
121+ }
122+ keys.access_token_key = token;
123+ keys.access_token_secret = secret;
124+ var twitter = createTwitterClient(keys);
125+ // アクセストークンをファイルに保存しておく
126+ writeAuthenticationKeys(twitter, function (err) {
127+ callback(err, twitter);
128+ });
129+ });
130+ return;
131+ }
132+ // 読み出しに成功したらその値からクライアントを作成する
133+ keys.access_token_key = data.access_token_key;
134+ keys.access_token_secret = data.access_token_secret;
135+ var twitter = createTwitterClient(keys);
136+ callback(null, twitter);
64137 });
65- callback(null, twitter);
66-};
138+}
--- a/clitweets.js
+++ b/clitweets.js
@@ -1,178 +1,142 @@
11 #!/usr/local/bin/node
22
33 var auth = require('./auth');
4-var fs = require('fs');
5-var util = require('util');
64 var readline = require('readline');
75
8-var configFile = '.account';
6+var tweetWriter = require('./tweetwriter');
97
10-var argv = require('optimist')
11- .usage('$0 - CLI based Twitter client.')
12- .describe('u', 'Twitter username')
13- .describe('p', 'Twitter password')
14- .argv;
15-
16-var rl;
17-var timeLineCache = [];
18-var isTimeLineAlive = false;
19-var readlineMode = '';
8+var rl = null;
209 var twitter = null;
2110
22-// 認証キーを読み込み、認証を行ってメインルーチンを実行する
23-readAuthenticationKeys(function (err, keys) {
24- if (err) {
25- authenticate(doMain);
26- return;
27- }
28- auth.createTwitterClient(keys, doMain);
29-});
30-
31-function pushToCache(texts) {
32- if (isTimeLineAlive) {
33- process.stdout.write(texts);
34- } else {
35- timeLineCache.push(texts);
11+// モードを表す疑似クラス
12+var mode = {
13+ current: null,
14+ start: function (mode) {
15+ if (this.current) {
16+ this.current.exit();
17+ }
18+ this.current = mode;
19+ this.current.start();
3620 }
3721 }
3822
39-function showTweet(tweet) {
40- if (util.isArray(tweet)) {
41- tweet.forEach(showTweet);
42- return;
43- }
44- if (tweet.user === undefined) {
45- return;
23+// タイムラインモードを定義
24+var timeline = {
25+ start: function () {
26+ if (rl !== null) {
27+ rl.close();
28+ rl = null;
29+ }
30+ tweetWriter.resume();
31+ process.stdin.once('data', function () {
32+ mode.start(command);
33+ });
34+ process.stdin.resume();
35+ },
36+ exit: function () {
37+ tweetWriter.pause();
4638 }
47- var name = tweet.user.name + ' (@' + tweet.user.screen_name + ')';
48- var timestamp = tweet.created_at;
49- var text = tweet.text;
50- pushToCache(name + ' ' + timestamp + '\n');
51- pushToCache(text + '\n');
52- pushToCache('\n');
53-}
39+};
5440
55-var newTweet;
56-var modes = {
57- tweet: {
58- start: function () {
59- rl.setPrompt('tweet> ');
60- process.stdout.write('write tweet message and push Ctrl-C.\n');
61- },
62- line: function (data) {
63- if (newTweet === '') {
64- newTweet = data;
65- } else {
66- newTweet = newTweet + '\n' + data;
67- }
68- rl.prompt();
69- },
70- SIGINT: function () {
71- process.stdout.write('\n----\n');
72- process.stdout.write(newTweet + '\n');
73- process.stdout.write('----\n');
74- var message = 'tweet this message? (y:yes/n:no) ';
75- function onAnswer(answer) {
76- if (answer === 'y') {
77- twitter.updateStatus(newTweet, {}, function (err, data) {
78- });
79- process.stdout.write('tweet done.\n\n');
80- exitPrompt();
81- } else if (answer === 'n') {
82- process.stdout.write('tweet canceled.\n\n');
83- exitPrompt();
84- } else {
85- rl.question(message, onAnswer);
86- }
41+// コマンドラインモードを定義
42+var command = {
43+ start: function () {
44+ rl = readline.createInterface({
45+ input: process.stdin,
46+ output: process.stdout,
47+ completer: function (line) {
48+ var completions = ['tweet', 'quit'];
49+ var hits = completions.filter(function(c) {
50+ return c.indexOf(line) == 0;
51+ });
52+ // show all completions if none found
53+ return [hits.length ? hits : completions, line];
8754 }
88- rl.question(message, onAnswer);
89- },
55+ });
56+ rl.on('line', this.line);
57+ rl.on('SIGINT', this.SIGINT);
58+ rl.setPrompt('> ');
59+ rl.prompt();
9060 },
91- prompt: {
92- start: function () {
93- rl.setPrompt('> ');
94- },
95- line: function (data) {
96- switch(data) {
97- case 'tweet':
98- setReadlineMode('tweet');
99- newTweet = '';
100- rl.prompt();
101- break;
102- case 'quit':
103- process.exit(0);
104- case '':
105- rl.prompt();
106- break;
107- default:
108- process.stdout.write('invalid command: ' + data + '\n');
109- rl.prompt();
110- }
111- },
112- SIGINT: function () {
113- exitPrompt();
114- },
61+ exit: function () {
62+ },
63+ line: function (data) {
64+ switch(data) {
65+ case 'tweet':
66+ mode.start(writeMessage);
67+ break;
68+ case 'quit':
69+ process.exit(0);
70+ case '':
71+ mode.start(timeline);
72+ break;
73+ default:
74+ process.stdout.write('invalid command: ' + data + '\n');
75+ mode.start(timeline);
76+ break;
77+ }
78+ },
79+ SIGINT: function () {
80+ mode.start(timeline);
11581 }
11682 }
11783
118-function setReadlineMode(modeName) {
119- var mode = modes[modeName];
120- rl.removeAllListeners('line');
121- rl.removeAllListeners('SIGINT');
122- rl.on('line', mode.line);
123- rl.on('SIGINT', mode.SIGINT);
124- mode.start();
125-}
126-
127-function startPrompt() {
128- rl = readline.createInterface({
129- input: process.stdin,
130- output: process.stdout,
131- completer: function (line) {
132- var completions = ['tweet', 'quit'];
133- var hits = completions.filter(function(c) {
134- return c.indexOf(line) == 0;
135- });
136- // show all completions if none found
137- return [hits.length ? hits : completions, line];
84+// tweet入力モードを定義
85+var newMessage = '';
86+var writeMessage = {
87+ start: function () {
88+ rl.removeAllListeners('line');
89+ rl.removeAllListeners('SIGINT');
90+ rl.on('line', this.line);
91+ rl.on('SIGINT', this.SIGINT);
92+ rl.setPrompt('tweet> ');
93+ process.stdout.write('write tweet message and push Ctrl-C.\n');
94+ newMessage = '';
95+ rl.prompt();
96+ },
97+ exit: function () {
98+ },
99+ line: function (data) {
100+ if (newMessage === '') {
101+ newMessage = data;
102+ } else {
103+ newMessage = newMessage + '\n' + data;
138104 }
139- });
140- setReadlineMode('prompt');
141- rl.prompt();
142-}
143-
144-function exitPrompt() {
145- rl.close();
146- startTimeLine();
147-}
148-
149-function stopTimeLine() {
150- isTimeLineAlive = false;
151-}
152-
153-function startTimeLine() {
154- process.stdin.once('data', function () {
155- stopTimeLine();
156- startPrompt();
157- });
158- process.stdin.resume();
159- timeLineCache.forEach(function (data) {
160- process.stdout.write(data);
161- });
162- timeLineCache = [];
163- isTimeLineAlive = true;
105+ rl.prompt();
106+ },
107+ SIGINT: function () {
108+ process.stdout.write('\n----\n');
109+ process.stdout.write(newMessage + '\n');
110+ process.stdout.write('----\n');
111+ var message = 'tweet this message? (y:yes/n:no) ';
112+ function onAnswer(answer) {
113+ if (answer === 'y') {
114+ twitter.updateStatus(newMessage, {}, function (err, data) {
115+ });
116+ process.stdout.write('tweet done.\n\n');
117+ mode.start(timeline);
118+ } else if (answer === 'n') {
119+ process.stdout.write('tweet canceled.\n\n');
120+ mode.start(timeline);
121+ } else {
122+ rl.question(message, onAnswer);
123+ }
124+ }
125+ rl.question(message, onAnswer);
126+ }
164127 }
165128
166-function doMain(err, twit) {
167- twitter = twit;
168- startTimeLine();
169-
129+// User streamによるタイムライン受信を開始
130+function startUserStream() {
170131 twitter.getHomeTimeline({}, function (err, data) {
171- showTweet(data);
132+ if (err) {
133+ throw err;
134+ }
135+ tweetWriter.write(data.reverse());
172136 });
173137 twitter.stream('user', {}, function (stream) {
174138 stream.on('data', function (data) {
175- showTweet(data);
139+ tweetWriter.write(data);
176140 });
177141 stream.on('error', function(err) {
178142 console.log(err);
@@ -180,35 +144,14 @@ function doMain(err, twit) {
180144 });
181145 };
182146
183-function authenticate(callback) {
184- auth.authenticate(function(err, twitter) {
185- if (err) {
186- callback(err);
187- return;
188- }
189- writeAuthenticationKeys(twitter, function(err) {
190- callback(null, twitter);
191- });
192- });
193-}
147+// ntwitter.Twitterクラスのインスタンスを生成する
148+auth.authenticate(function (err, twit) {
149+ twitter = twit;
194150
195-function readAuthenticationKeys(callback) {
196- fs.readFile(configFile, 'utf8', function (err, data) {
197- if (err) {
198- callback(err);
199- return;
200- }
201- var keys = JSON.parse(data);
202- callback(err, keys);
203- });
204-}
151+ // User streamを使ったタイムライン受信の開始
152+ startUserStream();
153+
154+ // タイムラインモードを開始
155+ mode.start(timeline);
156+});
205157
206-function writeAuthenticationKeys(twitter, callback) {
207- var keys = {
208- consumerKey: twitter.options.consumer_key,
209- consumerSecret: twitter.options.consumer_secret,
210- accessTokenKey: twitter.options.access_token_key,
211- accessTokenSecret: twitter.options.access_token_secret
212- };
213- fs.writeFile(configFile, JSON.stringify(keys, null, 2), 'utf8', callback);
214-}
--- /dev/null
+++ b/tweetwriter.js
@@ -0,0 +1,45 @@
1+// tweetの書き込みを行うモジュール
2+
3+var buffer = [];
4+var util = require('util');
5+var writable = false;
6+
7+exports.write = function (tweet) {
8+ if (util.isArray(tweet)) {
9+ tweet.forEach(_write);
10+ } else {
11+ _write(tweet);
12+ }
13+};
14+
15+function _write(tweet) {
16+ if (tweet.user === undefined) {
17+ return;
18+ }
19+ var name = tweet.user.name + ' (@' + tweet.user.screen_name + ')';
20+ var timestamp = tweet.created_at;
21+ var text = tweet.text;
22+ _push(name + ' ' + timestamp + '\n');
23+ _push(text + '\n');
24+ _push('\n');
25+}
26+
27+function _push(data) {
28+ if (writable) {
29+ process.stdout.write(data);
30+ } else {
31+ buffer.push(data);
32+ }
33+};
34+
35+exports.resume = function () {
36+ buffer.forEach(function (data) {
37+ process.stdout.write(data);
38+ });
39+ buffer = [];
40+ writable = true;
41+},
42+
43+exports.pause = function () {
44+ writable = false;
45+}