root/lang/javascript/vimperator-plugins/branches/2.2/hatebuWatchDog.js

Revision 34472, 13.0 kB (checked in by snaka, 14 months ago)

はてブされた記事も通知するようにしてみた。(ただし相手のブクマが「非公開」だと正しく表示してくれないみたい)

Line 
1//
2//  hatebuWatchDog.js     - hatena bookmark watch dog -
3//
4// LICENSE: {{{
5//
6// This software distributable under the terms of an MIT-style license.
7//
8// Copyright (c) 2009 snaka<snaka.gml@gmail.com>
9//
10// Permission is hereby granted, free of charge, to any person obtaining a copy
11// of this software and associated documentation files (the "Software"), to deal
12// in the Software without restriction, including without limitation the rights
13// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14// copies of the Software, and to permit persons to whom the Software is
15// furnished to do so, subject to the following conditions:
16//
17// The above copyright notice and this permission notice shall be included in
18// all copies or substantial portions of the Software.
19//
20// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26// THE SOFTWARE.
27//
28// OSI page : http://opensource.org/licenses/mit-license.php
29// Japanese : http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license
30//
31// }}}
32// PLUGIN INFO: {{{
33let PLUGIN_INFO =
34<VimperatorPlugin>
35  <name>{NAME}</name>
36  <description>Make notify hatebu-count when specified site's hatebu-count changed.</description>
37  <description lang="ja">指定されたサイトのはてブ数を監視、変動があったらお知らせします。</description>
38  <minVersion>2.0pre</minVersion>
39  <maxVersion>2.2pre</maxVersion>
40  <updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/hatebuWatchDog.js</updateURL>
41  <author mail="snaka.gml@gmail.com" homepage="http://vimperator.g.hatena.ne.jp/snaka72/">snaka</author>
42  <license>MIT style license</license>
43  <version>1.3.0</version>
44  <detail><![CDATA[
45    == Subject ==
46      Make notify hatebu-count when specified site's hatebu-count changed.
47      Usage is just put this script into vimperator's plugin directory.
48
49    == Global variables ==
50      g:hatebuWatchDogInterval:
51        Number. Watching interval. Default:600 Min:60
52      g:hatebuWtachDogTargets:
53        String. Sites where it wants you to watch.
54        If you want watch only one site, you should specify like following.
55        >||
56          :let g:hatebuWatchDogTargets = "http://d.hatena.ne.jp/snaka72/"
57        ||<
58        If you want watch more than one site, you should specify like following.
59        >||
60          :let g:hatebuWatchDogTargets = "['http://d.hatena.ne.jp/snaka72/', 'http://vimperator.g.hatena.ne.jp/snaka72/']"
61        ||<
62      g:hatebuWatchDogAlways:
63        Boole. Make notify every time. (for debug) Default:false
64
65  ]]></detail>
66  <detail lang="ja"><![CDATA[
67    == 概要 ==
68      指定されたサイトの被はてブ数を監視して、その数値に変動があったらお知らせします。
69      使い方は、このスクリプトをVimperatorのpluginディレクトリに格納するだけです。
70
71    == グローバル変数 ==
72      g:hatebuWatchDogInterval:
73        Number. 監視の間隔(秒). デフォルト600 設定可能な最小値:60
74      g:hatebuWtachDogTargets:
75        String. Sites where it wants you to watch
76        監視対象のサイトが一つだけの場合は以下のように設定します。
77        >||
78          :let g:hatebuWatchDogTargets = "http://d.hatena.ne.jp/snaka72/"
79        ||<
80        監視対象のサイトがが複数の場合は以下のように設定します。
81        >||
82          :let g:hatebuWatchDogTargets = "['http://d.hatena.ne.jp/snaka72/', 'http://vimperator.g.hatena.ne.jp/snaka72/']"
83        ||<
84      g:hatebuWatchDogAlways:
85        Boole. 毎回報告を挙げるかどうか。デフォルト:false (主にでバッグ用)
86
87    == ToDo ==
88      - 新着ブックマークのユーザidとコメントの表示
89      - 監視フレームワークにのっける
90
91    ]]></detail>
92  </VimperatorPlugin>;
93// }}}
94
95// Clear all watchers if started watcher exists.
96if (plugins.hatebuWatchDog && plugins.hatebuWatchDog.stopWatching)
97  plugins.hatebuWatchDog.stopWatching();
98
99let publics = plugins.hatebuWatchDog = (function() {
100  // PRIVATE //////////////////////////////////////////////////////////////{{{
101  const libly = plugins.libly;
102  let previousValue = 0;
103  let tasks = [];
104
105  function getCurrentValue(target, onSuccess, onFailure) {
106    // build hatebu xml-rpc request
107    let req = new libly.Request(
108      'http://b.hatena.ne.jp/xmlrpc',
109      {
110        'Content-Type' : 'text/xml'
111      },{
112        postBody : <methodCall>
113                     <methodName>bookmark.getTotalCount</methodName>
114                     <params>
115                       <param><value><string>{target}</string></value></param>
116                     </params>
117                   </methodCall>.toXMLString()
118      }
119    );
120
121    let currentValue;
122    req.addEventListener("onSuccess", function(data) {
123      liberator.log("XML-RPC request was succeeded.");
124      let resXml = new XML(data.responseText.replace(/^<\?xml version[^>]+?>/, ''));
125      currentValue = window.eval(resXml..int.toString());
126      onSuccess(currentValue);
127    });
128    req.addEventListener("onFailure", function(data) {
129      onFailure();
130    });
131    liberator.log("reauest...");
132    req.post();
133    liberator.log("done...");
134  }
135
136  function notifyAlways()
137    window.eval(liberator.globalVariables.hatebuWatchDogAlways) || false;
138
139  function showHatebuNotification(targetSite, currentValue, delta) {
140    let title = delta >= 0
141              ? "hatebuWatchDog\u304B\u3089\u306E\u304A\u77E5\u3089\u305B"  // ordinary notification
142              : "\u6B8B\u5FF5\u306A\u304A\u77E5\u3089\u305B"                // bad notification
143    let suffix = delta != 0 ? "\u306B\u306A\u308A\u307E\u3057\u305F\u3002"
144                            : "\u3067\u3059\u3002";
145    let message = "'" + targetSite + "' \u306E\u88AB\u306F\u3066\u30D6\u6570\u306F '" +
146                  currentValue + "' " + suffix + " (" + getSignedNum(delta) + ")";
147
148    (getNotifier())(title, message, growlIcon);
149  }
150
151  function getSignedNum(num) {
152    if (num > 0) return "+" + num;
153    if (num < 0) return "-" + Math.abs(num);
154    return "0";
155  }
156
157  let _notifier = null;
158  const GROWL_EXTENSION_ID = "growlgntp@brian.dunnington";
159
160  function getNotifier() {
161    if (_notifier) return _notifier;
162
163    if (Application.extensions.has(GROWL_EXTENSION_ID) &&
164        Application.extensions.get(GROWL_EXTENSION_ID).enabled) {
165      _notifier = publics.notify;
166    }
167    else {
168      _notifier = showAlertNotification;
169    }
170    return _notifier;
171  }
172
173  function showAlertNotification(title, message, icon) {
174    liberator.dump("icon:" + icon);
175    Cc['@mozilla.org/alerts-service;1']
176    .getService(Ci.nsIAlertsService)
177    .showAlertNotification(
178      null, //'chrome://mozapps/skin/downloads/downloadIcon.png',
179      title,
180      message
181    );
182  }
183
184  function growl() Components.classes['@growlforwindows.com/growlgntp;1']
185                   .getService().wrappedJSObject;
186  const growlIcon = "http://img.f.hatena.ne.jp/images/fotolife/s/snaka72/20090608/20090608045633.gif";  // temporary
187
188  function growlRegister() {
189    growl().register(
190      PLUGIN_INFO.name,
191      growlIcon,
192      [
193        {name: 'announce', displayName: 'Announce from hatebuWatchDog'},
194        {name: 'sadlynews',displayName: 'Sadly announce from hatebuWatchdog'},
195        {name: 'failed',   displayName: 'Erroer report from hatebuWatchdog'}
196      ]
197    );
198  }
199
200  function getInterval()
201    window.eval(liberator.globalVariables.hatebuWatchDogInterval) || 600; // default : 10 min.
202
203  // for debug
204  let log  = liberator.log;
205  let dump = liberator.dump;
206
207  // }}}
208  // PUBLIC ///////////////////////////////////////////////////////////////{{{
209  let self = {
210    startWatching: function() {
211      let targets;
212      try {
213        targets = window.eval(liberator.globalVariables.hatebuWatchDogTargets);
214      } catch(e) {
215        targets = liberator.globalVariables.hatebuWatchDogTargets;
216      }
217      if (targets) {
218        if (!(targets instanceof Array))
219          targets = [targets];
220        let i = 1, delay = 5000;
221        log("before setTimeout()");
222        targets.forEach(function(targetSite) {
223            setTimeout(function() {
224              publics.addTask({site : targetSite});
225            }, delay * i++);
226        });
227        log("after setTimeout()");
228      }
229      else {
230        liberator.echoerr("Please set g:hatebeWatchDogTargets before watching().");
231      }
232    },
233
234    addTask: function(target) {
235      dump(target.site);
236      const MINUTE = 60; // sec.
237      interval = getInterval() || (10 * MINUTE);       // default 10 min.
238      interval = Math.max(interval, MINUTE);      // lower limt is 1 min.
239
240      // initialize previous value
241      target.previousValue = 0;
242      target.initialize = true;
243      publics.watching(target);
244
245      // set watching interval
246      tasks.push(setInterval(publics.watching, 1000 * interval, target));
247      dump({target: target, interval: interval});
248    },
249
250    clearAllTasks: function() {
251      tasks.forEach(function(task) {
252          clearInterval(task);
253      });
254      tasks = [];
255      dump("watch dog is sleeping...");
256    },
257
258    watching: function(target) {
259      dump("watching...");
260      dump(target);
261
262      getCurrentValue(
263        target.site,
264        function(currentValue) {
265          if (target.initialize) {
266            target.initialize = false;
267            target.previousValue = currentValue;
268            return;
269          }
270          let delta =  currentValue - target.previousValue;
271          if (delta || notifyAlways()) {
272            showHatebuNotification(target.site, currentValue, delta);
273          }
274          target.previousValue = currentValue;
275          if (delta > 0) {
276            liberator.dump("***hoge");
277            self.getBookmarklistByURL(target.site)
278            .slice(0, delta)
279            .forEach(function(item)
280                      self.reportBookmarkedItem(self.parseBookmarkItem(item)));
281          }
282        },
283        function() {
284          liberator.echoerr("Cannot get current value.");
285        }
286      );
287    },
288
289    notify: function(title, message) {
290      growlRegister();
291      growl().notify(
292        PLUGIN_INFO.name,
293        'announce',
294        title,
295        message
296      );
297    },
298
299    getBookmarkListRss: function(url) {
300      return util.httpGet("http://b.hatena.ne.jp/bookmarklist.rss?url=" + encodeURIComponent(url));
301    },
302
303    getBookmarklistByURL: function(url) {
304      liberator.dump("********** getBookmarklistByURL");
305      let res = util.httpGet('http://b.hatena.ne.jp/bookmarklist.rss?url=' + encodeURIComponent(url));
306      liberator.dump(res);
307      return self.evaluateXPath("//rss:item", res.responseXML, self.nsResolver);
308    },
309
310    nsResolver: {
311        lookupNamespaceURI: function(pfx) (({
312          'rdf'         : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
313          'content'     : "http://purl.org/rss/1.0/modules/content/",
314          'taxo'        : "http://purl.org/rss/1.0/modules/taxonomy/",
315          'opensearch'  : "http://a9.com/-/spec/opensearchrss/1.0/",
316          'dc'          : "http://purl.org/dc/elements/1.1/",
317          'hatena'      : "http://www.hatena.ne.jp/info/xmlns#",
318          'media'       : "http://search.yahoo.com/mrss"
319        })[pfx] || 'http://purl.org/rss/1.0/')
320    },
321
322    // reffered  _libly.js
323    evaluateXPath: function(xpath, context, nsresolver) {
324      if (!xpath) return [];
325
326      var ret = [];
327      context = context || window.content.document;
328      var nodesSnapshot = (
329        context.ownerDocument ||
330        context
331      ).evaluate(
332        xpath,
333        context,
334        nsresolver || self.nsResolver,
335        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
336        null
337      );
338
339      for (let i = 0, l = nodesSnapshot.snapshotLength; i < l; i++) {
340          ret.push(nodesSnapshot.snapshotItem(i));
341      }
342      return ret;
343    },
344
345    parseBookmarkItem: function(item) {
346      let parsed = {
347        title: self.evaluateXPath("./rss:title", item)[0].textContent,
348        creator: self.evaluateXPath("./dc:creator", item)[0].textContent,
349        date: self.evaluateXPath("./dc:date", item)[0].textContent,
350        comment: self.evaluateXPath("./rss:description", item)[0].textContent,
351        tags: self.evaluateXPath("./dc:subject", item).map(function(i) i.textContent).join(",")
352      };
353      return parsed;
354    },
355
356    reportBookmarkedItem: function(item) {
357      liberator.dump(item);
358      (getNotifier())(
359          item.title,
360          item.creator + " bookmarked at " + item.date + "\n" +
361          item.tags + ":" + item.comment,
362          'http://www.hatena.ne.jp/users/' + item.creator.substr(0, 2) + '/' + item.creator + '/profile.gif'
363      );
364    }
365  };
366  // }}}
367  return self;
368})();
369
370// Awaking the watch dog.
371publics.startWatching();
372liberator.dump("Watch dog is awaking ...");
373// vim: sw=2 ts=2 et fdm=marker
Note: See TracBrowser for help on using the browser.