aboutsummaryrefslogtreecommitdiffstats
path: root/reading.js
blob: 39723cc6198473e395842bd573e61b880692fcbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
 * ==VimperatorPlugin==
 * @name            reading.js
 * @description     update Twitter's status to current URL and comment
 * @description-ja  今見てるページの URL とタイトルをコメントといっしょに Twitter に投稿する
 * @author          janus_wel <janus_wel@fb3.so-net.ne.jp>
 * @version         0.24
 * @minversion      2.0pre 2008/10/16
 * ==/VimperatorPlugin==
 *
 * LICENSE
 *   New BSD License
 *
 * USAGE
 *   :reading [comment]
 *     Twitter に今見ているページの情報をポストする。 comment はなくてもかまわない。
 *   :reading! [comment]
 *     Twitter に送られる文字列をクリップボードにコピーする。実際には送られない。
 *
 * VALIABLE
 *   g:reading_format
 *     投稿する文章の書式設定。以下の変数指定が可能。
 *       $SERVICENAME : このプラグインが付加する文字列。 g:reading_servicename で指定する。
 *       $TITLE       : ページのタイトル。
 *       $URL         : ページの URL。
 *       $SELECTED    : visual mode やマウスで選択した文字列。
 *       $COMMENT     : コメント。これがないとコメントを書いても反映されない。
 *     default
 *       let g:reading_format='$SERVICENAME : $COMMENT "$TITLE" $URL $SELECTED'
 *
 *   g:reading_servicename
 *     このプラグインが固定で付加する文字列。
 *     default
 *       let g:reading_servicename='I'm reading now'
 *
 *   g:reading_title_default
 *     タイトルがない場合に付加される文字列。
 *     default
 *       let g:reading_title_default='no title'
 *
 * HISTORY
 *   2008/09/05 ver. 0.10   - initial written.
 *   2008/09/24 ver. 0.20   - add URL canonicalization.
 *   2008/10/02 ver. 0.21   - fix the bug not apply encodeURI
 *                            to querystring for Pathtraq API.
 *   2009/10/16 ver. 0.24   - fix for https URL.
 * */

(function() {

// Twitter's URL to post
const DOMAIN   = 'http://twitter.com/';
const POST_URL = 'https://twitter.com/statuses/update.json';

// information functions
// change XPath query when HTML changed.
function Scraper() {}
Scraper.prototype = {
    constants: {
        VERSION: '0.22',
    },

    version: function() { return this.constants.VERSION; },

    getURL: function() {
        return liberator.modules.buffer.URL;
    },

    getTitle: function() {
        var title = $f('//title');
        return title
            ? title.text.replace(/^\s+|\s+$/g, '')
                        .replace(/\r\n|[\r\n\t]/g, ' ')
            : null;
    },

    getSelected: function() {
        var selected = window.content.getSelection().toString();
        return selected ? selected : '';
    }
};

liberator.modules.commands.addUserCommand(['reading'], "update Twitter's status to current page title, URL and comment",
    function(args) {
        try {
            let arg = args.string;

            // build post string -----
            let post_string;

            // get value from global variable or set default
            let format        = liberator.globalVariables.reading_format || '$SERVICENAME : $COMMENT "$TITLE" $URL $SELECTED';
            let serviceName   = liberator.globalVariables.reading_servicename || 'I\'m reading now';
            let title_default = liberator.globalVariables.reading_title_default || 'no title';

            let scraper = new Scraper;
            let title = scraper.getTitle() || title_default;
            let canonicalizedURL = canonicalizeURL(scraper.getURL());

            // expand variable ( evaluate variable ? )
            post_string = format.replace(/\$SERVICENAME/g, serviceName)
                                .replace(/\$TITLE/g,       title)
                                .replace(/\$URL/g,         canonicalizedURL)
                                .replace(/\$SELECTED/g,    scraper.getSelected())
                                .replace(/\$COMMENT/g,     arg);

            // ':matanico!' display the evaluated format.
            if(args.bang) {
                liberator.modules.util.copyToClipboard(post_string, true);
                return;
            }

            // ready posting -----
            // URI encode
            let parameter = 'status=' + encodeURIComponent(post_string);

            // get user account for Twitter
            let [user, pass] = getUserAccount(DOMAIN, POST_URL, null);

            // send status
            let req = new XMLHttpRequest();
            if(req) {
                req.open('POST', POST_URL, true, user, pass);
                req.onreadystatechange = function() {
                    if(req.readyState == 4) {
                        if(req.status == 200) liberator.echo('Posted ' + post_string);
                        else throw new Error('failure in posting status to Twitter. HTTP status code : ' + req.status);
                    }
                };
                req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                req.send(parameter);
            }
        }
        catch(e) {
            liberator.echoerr(e.message);
            liberator.log(e.message);
        }
    },
    // complete logic is none.
    {
        bang: true,
    },
    true
);

// stuff functions
function $f(query, node) {
    node = node || window.content.document;
    var result = (node.ownerDocument || node).evaluate(
        query,
        node,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
    );
    return result.singleNodeValue ? result.singleNodeValue : null;
}

function $s(query, node) {
    node = node || window.content.document;
    var result = (node.ownerDocument || node).evaluate(
        query,
        node,
        null,
        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
        null
    );
    var nodes = [];
    for(let i=0 ; i<result.snapshotLength ; ++i) nodes.push(result.snapshotItem(i));
    return nodes;
}

function canonicalizeURL(url) {
    const PATHTRAQ_CANONICALIZE_URL_API = 'http://api.pathtraq.com/normalize_url2?api=json&url=';

    var req = new XMLHttpRequest();
    req.open('GET', PATHTRAQ_CANONICALIZE_URL_API + encodeURI(url), false);
    req.send(null);
    if(req.status === 200) {
        let canonicalized = req.responseText.replace(/^"|"$/g, '');
        return (canonicalized && canonicalized != 'undefined') ? canonicalized : url;
    }
    else {
        throw new Error(req.status + ' ' + req.statusText + "\n" + req.responseHeaders);
    }
}

// user account manager
// from direct_bookmark.js
// thanks to Trapezoid
function getUserAccount(form, post, arg) {
    var user, password;
    try {
        let passwordManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
        let logins = passwordManager.findLogins({}, form, post, arg);
        if(logins.length > 0) {
            [user, password] = [logins[0].username, logins[0].password];
        } else {
            let promptUser = { value : '' }, promptPass = { value : '' };
            let promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"]
                .getService(Ci.nsIPromptService);

            let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
                    Ci.nsILoginInfo,
                    "init");

            let ret = promptSvc.promptUsernameAndPassword(
                    window, form, 'Enter e-mail address and password.',
                    promptUser, promptPass, null, {}
                    );
            if(ret) {
                [user, password] = [promptUser.value, promptPass.value];
                let formLoginInfo = new nsLoginInfo(form,
                        post, null,
                        user, password, '', '');
                passwordManager.addLogin(formLoginInfo);
            } else {
                liberator.echoerr("account not found - " + form);
            }
        }
    }
    catch(ex) {
        liberator.echoerr("handled exception during getting username and password");
        liberator.log(ex);
    }
    return [user, password];
}

})();
// vim:sw=4 ts=4 et:
span class="p">(cookieString) { var cookies = cookieString.split('; '); var cookie = {}; var key, val; for (let i=0, max=cookies.length ; i<max ; ++i) { [key, val] = cookies[i].split('='); cookie[key] = val; } return cookie; }, getCookie: function (key) { return this.cookie[key] ? this.cookie[key] : null; }, setCookie: function (obj) { this.cookie[obj.key] = obj.value; var string = [ obj.key + '=' + obj.value, 'domain=' + obj.domain, 'expires=' + new Date(new Date().getTime() + obj.expires), ].join(';'); this._setCookieString(string); }, }; // NicoPlayerController Class function NicoPlayerController() { this.initialize.apply(this, arguments); } NicoPlayerController.prototype = { initialize: function () { this.cookieManager = new CookieManager(); }, constants: { VERSION: '0.55', CARDINAL_NUMBER: 10, NICO_DOMAIN: '.nicovideo.jp', NICO_URL: 'http://www.nicovideo.jp/', WATCH_URL: '^http://[^.]+\.nicovideo\.jp/watch/', WATCH_PAGE: 1, FLVPLAYER_NODE_ID: 'flvplayer', STATE_PLAYING: 'playing', PLAY: true, PAUSE: false, STATE_SIZE_NORMAL: 'normal', STATE_SIZE_FIT: 'fit', NAME_PREMIUM_NO: 'premiumNo', NAME_PLAYER_VERSION: 'PLAYER_VERSION', SEEKTO_DEFAULT: 0, SEEKBY_DEFAULT: 0, VOLUMETO_DEFAULT: 100, VOLUMEBY_DEFAULT: 0, DESCRIPTION_HIDDEN_NODE_ID: 'des_1', DESCRIPTION_DISPLAYED_NODE_ID: 'des_2', DESCRIPTION_HIDDEN_STATE: 0, DESCRIPTION_DISPLAYED_STATE: 1, COOKIE_DESCRIPTION_NAME: 'desopen', COOKIE_EXPIRES: 60 * 60 * 24 * 365 * 1000, COMMAND_NORMAL: [ ['naka', 'normal comment (flow right to left)'], ['ue', 'fix comment to vertical top and horizonal center of the screen'], ['shita', 'fix comment to vertical bottom and horizonal center of the screen'], ['medium', 'normal size comment'], ['big', 'big size comment'], ['small', 'small size comment'], ['white', 'white color comment'], ['red', 'red color comment'], ['pink', 'pink color comment'], ['orange', 'orange color comment'], ['yellow', 'yellow color comment'], ['green', 'green color comment'], ['cyan', 'cyan color comment'], ['blue', 'bule color comment'], ['purple', 'purple color comment'], ['184', 'anonymouse comment'], ['sage', 'post comment on "sage" mode'], ['invisible', 'invisible comment'], ], COMMAND_PREMIUM: [ ['niconicowhite', 'nicinicowhite color comment'], ['truered', 'truered color comment'], ['passionorange', 'passionorange comment'], ['madyellow', 'madyellow comment'], ['elementalgreen', 'elementalgreen comment'], ['marineblue', 'marineblue'], ['nobleviolet', 'nobleviolet'], ['black', 'black'], ], }, getControllerVersion: function () { return this.constants.VERSION; }, getPlayerVersion: function () { return this.getValue(this.constants.NAME_PLAYER_VERSION); }, pagecheck: function() { if(this.getURL().match(this.constants.WATCH_URL)) return this.constants.WATCH_PAGE; throw new Error('current tab is not watch page on nicovideo.jp'); }, getURL: function() { return liberator.modules.buffer.URL; }, _flvplayer: function() { if(this.pagecheck() === this.constants.WATCH_PAGE) { let flvplayer = window.content.document.getElementById(this.constants.FLVPLAYER_NODE_ID); if(! flvplayer) throw new Error('flvplayer is not found'); return flvplayer.wrappedJSObject || flvplayer; } return null; }, togglePlay: function() { var p = this._flvplayer(); (p.ext_getStatus() !== this.constants.STATE_PLAYING) ? p.ext_play(this.constants.PLAY) : p.ext_play(this.constants.PAUSE); if(p.ext_getStatus() === 'end') { let base = p.ext_getPlayheadTime(); let self = this; setTimeout(function () { if (base !== p.ext_getPlayheadTime()) { p.ext_play(self.constants.PAUSE); } else { p.ext_setPlayheadTime(0); p.ext_play(self.constants.PLAY); } }, 100); } }, toggleMute: function() { var p = this._flvplayer(); p.ext_setMute(! p.ext_isMute()); }, toggleCommentVisible: function() { var p = this._flvplayer(); p.ext_setCommentVisible(! p.ext_isCommentVisible()); }, toggleRepeat: function() { var p = this._flvplayer(); p.ext_setRepeat(! p.ext_isRepeat()); }, toggleSize: function() { var p = this._flvplayer(); (p.ext_getVideoSize() === this.constants.STATE_SIZE_NORMAL) ? p.ext_setVideoSize(this.constants.STATE_SIZE_FIT) : p.ext_setVideoSize(this.constants.STATE_SIZE_NORMAL); }, toggleDescription: function () { if(!(this.pagecheck() === this.constants.WATCH_PAGE)) { return; } // get nodes var hidden = window.content.document.getElementById(this.constants.DESCRIPTION_HIDDEN_NODE_ID); var displayed = window.content.document.getElementById(this.constants.DESCRIPTION_DISPLAYED_NODE_ID); // get cookie this.cookieManager.readCookie(this.constants.NICO_URL); var val = this.cookieManager.getCookie(this.constants.COOKIE_DESCRIPTION_NAME); if(!(hidden && displayed && val !== undefined && val !== null)) { return; } // change 'display' property of description nodes var escape = hidden.style.display; hidden.style.display = displayed.style.display; displayed.style.display = escape; // change cookie var change = (val == this.constants.DESCRIPTION_HIDDEN_STATE) ? this.constants.DESCRIPTION_DISPLAYED_STATE : this.constants.DESCRIPTION_HIDDEN_STATE; this.cookieManager.setCookie({ key: this.constants.COOKIE_DESCRIPTION_NAME, value: change, domain: this.constants.NICO_DOMAIN, expires: this.constants.COOKIE_EXPIRES, }); }, seekTo: function(position) { if(position) { if(position.match(/^(\d+):(\d+)$/)) { position = parseInt(RegExp.$1, this.constants.CARDINAL_NUMBER) * 60 + parseInt(RegExp.$2, this.constants.CARDINAL_NUMBER); } if(isNaN(position)) throw new Error('assign unsigned number : seekTo()'); } else position = this.constants.SEEKTO_DEFAULT; var p = this._flvplayer(); if (position < 0) p.ext_setPlayheadTime(parseInt(p.ext_getTotalTime()) + parseInt(position)); else p.ext_setPlayheadTime(position); }, seekBy: function(delta) { if(delta) { if(isNaN(delta)) throw new Error('assign signed number : seekBy()'); } else delta = this.constants.SEEKBY_DEFAULT; var p = this._flvplayer(); var position = p.ext_getPlayheadTime(); position += parseInt(delta, this.constants.CARDINAL_NUMBER); p.ext_setPlayheadTime(position); }, volumeTo: function(volume) { if(volume) { if(isNaN(volume)) throw new Error('assign unsigned number : volumeTo()'); } else volume = this.constants.VOLUMETO_DEFAULT; var p = this._flvplayer(); p.ext_setVolume(volume); }, volumeBy: function(delta) { if(delta) { if(isNaN(delta)) throw new Error('assign signed number : volumeBy()'); } else delta = this.constants.VOLUMEBY_DEFAULT; var p = this._flvplayer(); var volume = p.ext_getVolume(); volume += parseInt(delta, this.constants.CARDINAL_NUMBER); p.ext_setVolume(volume); }, getValue: function(name) { return this._flvplayer().GetVariable(name); }, setValue: function(name, value) { return this._flvplayer().SetVariable(name, value); }, // return the clone not to damage // Array.apply() is cloning Array // (adding method to Array has a lot of troubles) // refer: http://la.ma.la/blog/diary_200510062243.htm getAvailableCommands: function() { return this.getValue(this.constants.NAME_PREMIUM_NO) ? this.constants.COMMAND_NORMAL.concat(this.constants.COMMAND_PREMIUM) : Array.apply(null, this.constants.COMMAND_NORMAL) } }; // global object var controller = new NicoPlayerController(); // command register liberator.modules.commands.addUserCommand( ['nicoinfo'], 'display player information', function() { try { let info = [ 'player version : ' + controller.getPlayerVersion(), 'controller version : ' + controller.getControllerVersion(), ].join("\n"); liberator.echo(info, liberator.modules.commandline.FORCE_MULTILINE); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicopause'], 'toggle play / pause', function() { try { controller.togglePlay(); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicomute'], 'toggle mute', function() { try { controller.toggleMute(); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicommentvisible'], 'toggle comment visible', function() { try { controller.toggleCommentVisible(); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicorepeat'], 'toggle repeat', function() { try { controller.toggleRepeat(); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicoseek'], 'controll seek bar', function(args, special) { try { let arg = (args.length > 1) ? args[0].toString() : args.string; special ? controller.seekBy(arg) : controller.seekTo(arg); } catch(e) { liberator.echoerr(e); } }, { bang: true, } ); liberator.modules.commands.addUserCommand( ['nicovolume'], 'controll volume', function(args, special) { try { let arg = (args.length > 1) ? args[0].toString() : args.string; special ? controller.volumeBy(arg) : controller.volumeTo(arg); } catch(e) { liberator.echoerr(e); } }, { bang: true, } ); liberator.modules.commands.addUserCommand( ['nicosize'], 'toggle video size', function() { try { controller.toggleSize(); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicodescription'], 'toggle display or not the description for video', function() { try { controller.toggleDescription(); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicomment'], 'fill comment box', function(args) { try { let arg = args.string; let command, comment; [command, comment] = expandExCommand(arg); comment = comment.replace(/&emsp;/g, EMSP) .replace(/&nbsp;/g, NBSP) .replace(/<LF>/g, LF); if(command) { controller.setValue('inputArea.MailInput.text', command); } controller.setValue('ChatInput.text', comment); } catch(e) { liberator.echoerr(e); } }, {} ); liberator.modules.commands.addUserCommand( ['nicommand'], 'fill command box', function(arg) { try { controller.setValue('inputArea.MailInput.text', arg.string); } catch(e) { liberator.echoerr(e); } }, { completer: function(args) { var arg = args.string; // get available commands by roll var availableCommands = controller.getAvailableCommands(); // for no argument if(!arg) { return [0, availableCommands]; } // make array of inputted words // and current input word shoud be last (dayone ?) var inputted = arg.toLowerCase().split(/\s+/); var current = inputted[inputted.length - 1]; // complete position is the top of last word var completePosition = arg.lastIndexOf(' ') + 1; // exclude inputted word from candidates var candidates = availableCommands.filter( function(commandSet) { for(let i=0, numofInputted=inputted.length ; i<numofInputted ; ++i) { if(commandSet[0] === inputted[i]) { inputted.splice(i, 1); return false; } } return true; }); // display all candidates in after space ' ' if(inputted[inputted.length - 1] !== current) { // complete position is the next of last space completePosition = arg.length + 1; return [completePosition, candidates]; } // return the set that start with current word var commands = candidates.filter( function(commandSet) { return (commandSet[0].indexOf(current) === 0); }); return [completePosition, commands]; }, } ); // for ex-command ------------------------------------------------------- // constants const MAX_LINE = { big: 16, medium: 25, small: 38, }; const EMSP = '\u3000'; const NBSP = '\u00a0'; const LF = '\u000a'; const PROPATIES_DEFAULT = { fixFlag: false, max : MAX_LINE['medium'], line : 1, size : '', }; const COMMAND_SEPARATOR = '|'; // functions function expandExCommand(arg) { var command, comment; // command and comment is separated by COMMAND_SEPARATOR var temp = arg.split(COMMAND_SEPARATOR); if(temp.length > 1) { command = temp.shift(); comment = temp.join(COMMAND_SEPARATOR); } else { comment = arg; } // ex_command is putted in braces if(comment.match(/^\{([^{}]+)\}(.+)/)) { let exCommand = RegExp.$1; let text = RegExp.$2; let properties = analysisExCommand(exCommand); // fine tune command about comment size if(properties.size) { if(command) { command = command.replace(/\s*big\s*/g, ' ') .replace(/\s*medium\s*/g, ' ') .replace(/\s*small\s*/g, ' '); } command += ' ' + properties.size; } // expand!! comment = buildLineBreakString(properties.line) + text; if(properties.fixFlag) { let post = buildLineBreakString(properties.max - properties.line + 1); comment += post + NBSP; } } return [command, comment]; } // "&nbsp;" and <LF> on each line function buildLineBreakString(numof) { // faster than string concatenate (+, +=) var string = Array(numof * 2); for(let i=1 ; i<numof ; ++i) { string.push(NBSP); string.push(LF); } return string.join(''); } // RegExp hell function analysisExCommand(exCommand) { // default set var properties = PROPATIES_DEFAULT; // fix or not if(exCommand.match(/\bfix\b/)) { properties.fixFlag = true; } // comment size and max line if(exCommand.match(/\b(big|medium|small)\b/)) { properties.size = RegExp.$1; properties.max = MAX_LINE[properties.size]; } else if(exCommand.match(/\bmax(\d+)\b/)) { properties.max = RegExp.$1; } // line if(exCommand.match(/\bline(-?\d+)\b/)) { let line = parseInt(RegExp.$1, 10); if(line < 0) line = properties.max + line + 1; if(line > properties.max) line = properties.max; properties.line = line; } return properties; } })(); // vim: set sw=4 ts=4 et;