aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xtwittperator.js210
1 files changed, 109 insertions, 101 deletions
diff --git a/twittperator.js b/twittperator.js
index de016cc..9a8bc6a 100755
--- a/twittperator.js
+++ b/twittperator.js
@@ -28,7 +28,7 @@ let PLUGIN_INFO =
<name>Twittperator</name>
<description>Twitter Client using ChirpStream</description>
<description lang="ja">OAuth対応Twitterクライアント</description>
- <version>1.7.3</version>
+ <version>1.8.0</version>
<minVersion>2.3</minVersion>
<maxVersion>2.4</maxVersion>
<author mail="teramako@gmail.com" homepage="http://d.hatena.ne.jp/teramako/">teramako</author>
@@ -1221,12 +1221,96 @@ let PLUGIN_INFO =
// }}}
// Twittperator
- function Stream({ name, host, path }) { // {{{
- let connectionInfo;
+ function HTTPConnection(url, options) { // {{{
+ const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ this.events = {};
+
+ this.channel =
+ ioService.newChannelFromURI(ioService.newURI(url, null, null)).QueryInterface(Ci.nsIHttpChannel);
+ this.channel.notificationCallbacks = this;
+
+ if (options) {
+ if ("headers" in options) {
+ for (let [n, v] in Iterator(options.headers))
+ this.channel.setRequestHeader(n, v, true);
+ }
+
+ for (let [n, v] in Iterator(options)) {
+ if (/^on[A-Z]/(n) && (v instanceof Function))
+ this.events[n.toLowerCase()] = v;
+ }
+
+ this.onRecv = options.onReceive;
+ }
+
+ this.channel.asyncOpen(this, null);
+ }
+ HTTPConnection.prototype = {
+ callEvent: function(name) {
+ name = name.toLowerCase();
+ if (name in this.events)
+ return this.events[name].apply(this, Array.slice(arguments, 1));
+ },
+
+ cancel: function() {
+ if (this.channel)
+ return this.channel.cancel(Cr.NS_ERROR_NOT_AVAILABLE);
+ },
+
+ // 実装しないと例外になるメソッドとか
+ onStartRequest: function(request, context) {},
+ onProgress : function(request, context, progress, progressMax) {},
+ onStatus : function(request, context, status, statusArg) {},
+ onRedirect : function(oldChannel, newChannel) {},
+
+ onDataAvailable: function(request, context, stream, sourceOffset, length) {
+ let inputStream =
+ Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+ inputStream.init(stream);
+ let data = inputStream.read(length);
+ this.callEvent("onReceive", data);
+ },
+
+ onStopRequest: function(request, context, status) {
+ if (Components.isSuccessCode(status)) {
+ this.callEvent("onComplete");
+ } else {
+ this.callEvent("onError");
+ }
+ delete this.channel;
+ },
+
+ onChannelRedirect: function(oldChannel, newChannel, flags) {
+ this.channel = newChannel;
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function(IID) {
+ try {
+ return this.QueryInterface(IID);
+ } catch (e) {
+ throw Components.results.NS_NOINTERFACE;
+ }
+ },
+
+ // XPCOM インターフェイスに見せかけているので、QI を実装する必要がある
+ QueryInterface : function(IID) {
+ if (IID.equals(Ci.nsISupports) ||
+ IID.equals(Ci.nsIInterfaceRequestor) ||
+ IID.equals(Ci.nsIChannelEventSink) ||
+ IID.equals(Ci.nsIProgressEventSink) ||
+ IID.equals(Ci.nsIHttpEventSink) ||
+ IID.equals(Ci.nsIStreamListener))
+ return this;
+
+ throw Components.results.NS_NOINTERFACE;
+ }
+ }; // }}}
+ function Stream({ name, url }) { // {{{
+ let connection;
let restartCount = 0;
- let startTime;
let lastParams;
- let lastReceivedTime;
let listeners = [];
// 極めて適当につくってます。
@@ -1241,29 +1325,19 @@ let PLUGIN_INFO =
function restart() {
stop();
-
if (restartCount > 13)
return liberator.echoerr("Twittperator: Gave up to connect to " + name + "...");
-
liberator.echoerr("Twittperator: " + name + " will be restared...");
-
// 試行済み回数^2 秒後にリトライ
setTimeout(function() start(lastParams), Math.pow(2, restartCount) * 1000);
-
restartCount++;
}
function stop() {
- if (!connectionInfo)
+ if (!connection)
return;
-
+ connection.cancel();
liberator.log("Twittperator: stop " + name);
-
- clearInterval(connectionInfo.interval);
- connectionInfo.sos.close();
- connectionInfo.sis.close();
-
- connectionInfo = void 0;
}
function start(params) {
@@ -1271,116 +1345,50 @@ let PLUGIN_INFO =
liberator.log("Twittperator: start " + name);
- startTime = new Date().getTime();
lastParams = params;
let useProxy = !!setting.proxyHost;
- let requestPath = path;
+ let requestUrl = url;
if (params) {
// XXX Twitter がなぜか + を許容しない気がする(401 を返す)ので、再変換する
let query = tw.buildQuery(params).replace(/\+/g, "%20");
- requestPath += '?' + query;
+ requestUrl += '?' + query;
}
- let authHeader = tw.getAuthorizationHeader("http://" + host + requestPath);
-
- if (useProxy)
- requestPath = "http://" + host + requestPath;
-
- let get = [
- "GET " + requestPath + " HTTP/1.1",
- "Host: " + host,
- "Authorization: " + authHeader,
- "Content-Type: application/x-www-form-urlencoded",
- "",
- "",
- ].join("\n");
-
- let socketService =
- let (stsvc = Cc["@mozilla.org/network/socket-transport-service;1"])
- let (svc = stsvc.getService())
- svc.QueryInterface(Ci["nsISocketTransportService"]);
-
- let transport =
- socketService.createTransport(
- null, 0,
- useProxy ? setting.proxyHost : host,
- useProxy ? parseInt(setting.proxyPort || "3128", 10) : 80,
- null);
- let os = transport.openOutputStream(0, 0, 0);
- let is = transport.openInputStream(0, 0, 0);
- let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
- let sos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
-
- sis.init(is);
- sos.setOutputStream(os);
-
- sos.write(get, get.length);
-
+ let authHeader = tw.getAuthorizationHeader(requestUrl);
let buf = "";
- let first = true;
- let interval = setInterval(function() {
+ let onReceive = function(data) {
try {
- let len = sis.available();
- if (len <= 0) {
- // 30秒ごとにゴミデータを送ってくる仕様っぽいので、30x2+10秒 まつことにする。
- if (lastReceivedTime && ((new Date().getTime() - lastReceivedTime) > 70 * 1000)) {
- lastReceivedTime = 0;
- liberator.echoerr("Twittperator: " + name + " timed out");
- restart();
- }
- return;
- }
-
- // 5分間接続されていたら、カウントをクリア
- // 何かの事情で即切断されてしまうときに、高頻度でアクセスしないための処置です。
- if (restartCount && (new Date().getTime() - startTime) > (5 * 60 * 1000))
- restartCount = 0;
-
- lastReceivedTime = new Date().getTime();
- let data = sis.read(len);
let lines = data.split(/\r\n|[\r\n]/);
if (lines.length >= 2) {
lines[0] = buf + lines[0];
for (let [, line] in Iterator(lines.slice(0, -1))) {
try {
- if (first) {
- first = false;
- let [, code] = line.match(/^\S+\s+(\d+)/);
- if (code != '200') {
- stop();
- return liberator.echoerr("Twittperator: " + name + "'s response code is " + code + ".");
- }
- }
if (/^\s*\{/(line))
onMsg(Utils.fixStatusObject(JSON.parse(line)), line);
- else
- liberator.log(name + ': \n' + line);
} catch (e) { liberator.log(e); }
}
buf = lines.slice(-1)[0];
} else {
buf += data;
}
- } catch (e if /^NS_(?:ERROR_NET_RESET|BASE_STREAM_CLOSED)$/(e)) {
- liberator.echoerr("Twittperator: " + name + " was stopped by " + e.name + ".");
- restart();
- stop();
} catch (e) {
liberator.echoerr("Twittperator: Unknown error on " + name + " connection: " + e.name);
- restart();
- stop();
}
- }, 500);
-
- connectionInfo = {
- sos: sos,
- sis: sis,
- interval: interval,
- params: params
};
+
+ connection = new HTTPConnection(
+ requestUrl,
+ {
+ headers: {
+ Authorization: authHeader,
+ },
+ onReceive: onReceive,
+ onError: restart
+ }
+ );
}
function onMsg(msg, raw) {
@@ -1889,7 +1897,7 @@ let PLUGIN_INFO =
TrackingStream.stop();
}
},
- completer: function (context, args) {
+ completer: function(context, args) {
if (setting.trackWords)
context.completions = [[setting.trackWords, "Global variable"]];
}
@@ -2093,8 +2101,8 @@ let PLUGIN_INFO =
let tw = new TwitterOauth(accessor);
// ストリーム
- let ChirpUserStream = Stream({ name: 'chirp stream', host: "chirpstream.twitter.com", path: "/2b/user.json" });
- let TrackingStream = Stream({ name: 'tracking stream', host: "stream.twitter.com", path: "/1/statuses/filter.json" });
+ let ChirpUserStream = Stream({ name: 'chirp stream', url: "https://userstream.twitter.com/2/user.json" });
+ let TrackingStream = Stream({ name: 'tracking stream', url: "http://stream.twitter.com/1/statuses/filter.json" });
// 公開オブジェクト
__context__.OAuth = tw;