Piro さんにコンタクトしたい
Piro さんこと下田洋志さんにいろいろと訊きたいんだけど、連絡手段がわからない。
id:piro_or こうですか?わかりません><
事始めに Twitter クライアントを書いてみたよ
g:twitter:id:kageroh_:20090416:1239872427
正式にバージョン管理を開始。下記のコードは古い恐れがあります。
全然 XUL 要素使ってないけどね!
http://kgr.s56.xrea.com/misc/xwitter.zip
- 使い方
- alt+UとかEscとかを押してみよう!
こってり書き忘れてたけど、username, password は xwitter.mod に書いてね! あと、起動の仕方は手前味噌だけど、id:kageroh_:20090409:1239282039 が簡単だと思うよ!
><キーワードハイライトつけた。xwitter.mod に正規表現で書く。
- xwitter
- application.ini
[App] Vendor=kageroh Name=Xwitter Version=0.1 BuildID=20090408 ID=kagerohs122@hotmail.com [Gecko] MinVersion=1.9 MaxVersion=1.9.0.*
content xwitter file:content/
-
-
- content
- main.xul
- content
-
リファクタリングした。
できる範囲で JS 分離した。すっきりー!
<?xml version="1.0" encoding="utf-8"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <?xml-stylesheet href="xwitter.css" type="text/css"?> <!DOCTYPE window [ <!ENTITY % xwitter SYSTEM 'xwitter.mod'> %xwitter; ]> <xul:window id="main" title="Xwitter" screenX="&x;" screenY="&y;" xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <xul:vbox id="box" width="&w;" height="&h;" style="overflow:auto"><xul:box /></xul:vbox> <form id="status_update_form" method="post" action="&protocol;://twitter.com/status/update"> <fieldset> <legend for="status" accesskey="u">status</legend> <textarea id="status" rows="2" cols="40"></textarea> <input type="button" id="update-submit" value="update" /> <input type="hidden" id="in_reply_to_status_id" /> </fieldset> </form> <input id="url" /> <xul:script src="&protocol;://&username;:&password;@twitter.com/account/verify_credentials.json" /> <xul:script src="xwitter.js" /> <xul:script> const protocol = '&protocol;' Xwitter.initialize(); Xwitter.EXP = (new RegExp).compile(/®exp;/); window.setInterval(Xwitter.refresh(), &sec; * 1000); </xul:script> </xul:window>
-
-
-
- xwitter.css
-
-
@charset 'utf-8'; @namespace url("http://www.w3.org/1999/xhtml"); * { letter-spacing: 1px; font-size: 12px; line-height: 1.5; } html { background-color: #9ae4e8; margin-bottom: 20px; display: none; } body, div.section { padding: 9px; } body { color: #333; background-color: white; font-family: monospace; } div.section { border-width: 0 0 1px; border-color: #d2dada; border-style: dashed; clear: both; min-height: 50px; } div.section:hover { background-color: #f7f7f7; } p { margin: 4px 16px 0 57px; } p.highlight { background-color: yellow; } p.protect { text-indent: 21px; background-image: url('data:image/gif;base64,R0lGODlhEAAVAPYAAP/v0f/v0M6jS5mZmfDVoc6iSvC9WMvKyu+8VqR6KP/u0PDGc+WyTPC/YPC/Xv/TeuayTP/QcuWxTP/gof/fn/C+W5iYmPDLgf/Vf//PcP/RdPC/XMCSNPDFcfDBY//NZ//Qb//Yif/nuO/Id/DEbc6jSv/emP/aj6R7KO/EbfDBZfDJfv/ip6R7KfDIe//Zjv/Yh//bkvDDav/Te//OavDQkfC9Wf/clu/Oi//kr//emu/LgP/Xg//VgfDMhfDKfv/jqfDAYvDDafDGdfDIefDBZvC+XO+/YfDSlv/blO/DbfDNhu/DafDNisCTNcCSNcGTNc2iSsrJyf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAFMALAAAAAAQABUAAAexgFOCg4SFhoeIiYlSB1IDilKRjJGPhpKVkQcWlo6ElIUCUVGhogUFAqeDAgEAAQquAK2sBlCCUQRINThLFysjC0pMELVTBQEiOUATOkkvMD0zBk6CAgRNPjsuLU4kQipHDE+CJQAsFCYxCVAYDxoZ0rYEP0RDHQkcHg0bFQzEUQA3ToTggeJJBBA0PiCYNuXWghQyigRxYMQGAgQSGE55AuWJRyhOOkIBqaikyZMoDQUCADs='); background-repeat: no-repeat; } em { color: #0084b4; font-style: normal; font-weight: bolder; } div.section input { background-color: transparent; display: block; float: right; width: 16px; height: 16px; cursor: pointer; } input[title] { background-image: url('data:image/gif;base64,R0lGODlhEAAQAPQAAP////v7+/f39/Pz8+/v7+rq6ubm5uLi4t7e3tra2tLS0s7OzsrKysXFxcHBwb29vbm5ubW1tbGxsa2trampqaWlpaCgoJycnJiYmJSUlP///wAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1Qgz94cGFja2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKP3hwYWNrZXQgZW5kPSJ3Ij8+ACH5BAEHABoALAAAAAAQABAAAAV3oCaOWkGQKBo5aUtgl9CiEPM08yhcAWHNhQSD8gAAIhNGoiBSXCqRhyJgFCweEYvloGFYqMawcYBJjBwVgXh8UaAeE/C4wmhR1GEDpTWgrAN7KQgQAAcTEgUAE0woC1EWCgsWDhJmKA4ZOCICDxkLKQyMJAefIiEAOw=='); } input[class="true"] { background-image: url('data:image/gif;base64,R0lGODlhEAAQAPYAAPrv3vrt1vnr1/nq1PrpzfnoyPjnzfjlyvfkyfjkxvfjw/fhv/fgwfbgvvfgtfbeufbfs/betvbcsPXcq//kNvXasPXZr//hOPXZo/XYq//gNP/gN/TYrPTYqvXYpf/fOv/fMf/eOf7dOf/eNfTWoP7dNv3bNfPTmf7aNf3ZMvzWMvzWM/LOlPPPi/vUMvrSNPjNLvfLN/DGevbJOPfKLvHGbe/EePfJLvTGNPLEVe7AcPTFMfPDOvDBV/TEMe/AZvTDLfDBTfPCPu69WvG+P/C+RPG+PPG+O/C9Re+8S/G9Pe67WvC8Pu24V++6P/C5Oe+5O+24Ue+4NO22Uey0SP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1Qgz94cGFja2UgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKP3hwYWNrZXQgZW5kPSJ3Ij8+ACH5BAEHAFUALAAAAAAQABAAAAeHgFWCg1UEhIeIMieIjABQQYyILDAwGZGESSMjPZEDDyQ6MRcXMzYeDQKCEFNKOzcvH7EfLjQ4R00FVRJSGr0hv8AgTAqDHE8lJcC/Jk4IhxZEItLTSAyMQyjKKTWRRRvf4DmMBzwUKkZGKxRCjBVAQD8GC0s+PgmILVERhB1UGIgTGAVwMCgQADs='); } input[class="reply"] { margin-top: 6px; background-image: url('data:image/gif;base64,R0lGODlhEAAQAPIAALW1tbu7u8PDw8rKytTU1Nvb2+Tk5Orq6iH/C0lDQ1JHQkcxMDEyv3BydAAAAhQAAAA4dmNndAAAAawAAAAwbmRpbgAAAdwAAAA4ZHNjbQAAAkwAAALyWFlaIAAAAAAAAHRLAAA+HQAAA8tYWVogAAAAAAAAWnMAAKymAAAXJlhZWiAAAAAAAAAoGAAAFVcAALgzWFlaIAAAAAAAAPNSAAEAAAABFs9zZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov///aMAAAPcAADAbGN1cnYAAAAAAAAAAQHNAAB2Y2d0ACH5BAkAAAgALAAAAAAQABAAAARGEElZyLwYHWAzNkFQeFcRCKhAHJ4BpEIoeiY6DPKYgQF74AGDZyOUEAADkoE1GQCYpMlGF52oqpdBB4sYFLlJLsLwxVIREQA7'); clear: right; } img { margin-right: 9px; float: left; width: 48px; height: 48px; } textarea { display: block; } form, #url { position: fixed; opacity: .8; } form { background-color: white; display: none; top: 0; } #url { bottom: 0; width: 100%; }
-
-
-
- xwitter.js
-
-
いちお、replaceChild で置換した古いノードの removeEventListener した。
function $(id) { return document.getElementById(id); } function $$(tagName, context) { return (context || document).getElementsByTagName(tagName); } function $D() { return (new Date).toGMTString() .replace(/( |:|,)/g, function($_, $1) { switch ($1) { case ' ': return '+'; case ':': return '%3A'; case ',': return '%2C'; } }); } var Xwitter = { GMT: $D(), DOC: document.implementation.createDocument('', '', null), XSL: new XSLTProcessor, HTT: (new RegExp).compile(/(https?:\/\/[-_.!~*\'()\w;\/?:\@&=+\$,%#]+)/), URL: $('url'), BOX: $('box'), FRM: $('status_update_form'), TXT: $('status'), BTN: $('update-submit'), HDN: $('in_reply_to_status_id'), initialize: function() { this.DOC.async = false; this.DOC.load('xwitter.xsl'); this.XSL.importStylesheet(this.DOC); this.BTN.addEventListener('click', function() { Xwitter.update(); }, false); window.addEventListener('keydown', function(event) { switch (event.keyCode) { case KeyEvent.DOM_VK_ESCAPE: Xwitter.escape(); return; } if (event.altKey) switch (event.keyCode) { case KeyEvent.DOM_VK_U: Xwitter.focus(); return; } if (event.ctrlKey) switch (event.keyCode) { case KeyEvent.DOM_VK_R: Xwitter.refresh(); return; case KeyEvent.DOM_VK_W: window.close(); return; } }, false); }, refresh: function() { var xhr = new XMLHttpRequest; xhr.onreadystatechange = function() { if (xhr.readyState !== 4 || xhr.status !== 200) return; var old = Xwitter.BOX.replaceChild(Xwitter.XSL.transformToFragment(xhr.responseXML, document), Xwitter.BOX.firstChild); for (var c, i = 0, e = $$('input'); c = e[i++];) c.addEventListener('click', Xwitter[c.alt ? 'reply' : 'fav'], false); for (var c, i = 0, e = $$('p'); c = e[i++];) { c.addEventListener('dblclick', Xwitter.findURL, false); if (Xwitter.EXP.test(c.textContent)) c.className += ' highlight'; } Xwitter.BOX.firstChild.style.display = 'block'; Xwitter.free(old); old = null; Xwitter.notify(); }; xhr.open('get', protocol + '://twitter.com/statuses/friends_timeline.xml?since=' + Xwitter.GMT); xhr.send(null); Xwitter.wait(); Xwitter.GMT = $D(); return arguments.callee; }, free: function(old) { for (var c, i = 0, e = $$('input', old); c = e[i++];) c.removeEventListener('click', Xwitter[c.alt ? 'reply' : 'fav'], false); for (var c, i = 0, e = $$('p', old); c = e[i++];) c.removeEventListener('dblclick', Xwitter.findURL, false); }, fav: function() { var elm = this, fav = elm.className === 'true'; var xhr = new XMLHttpRequest; xhr.onreadystatechange = function() { if (xhr.readyState !== 4 || xhr.status !== 200) return; elm.className = !fav; Xwitter.notify(); }; xhr.open('post', [ protocol, '://twitter.com/favourings/', fav ? 'destroy' : 'create', '/', elm.title, '.xml' ].join('')); xhr.send(null); Xwitter.wait(); }, reply: function() { Xwitter.HDN.value = this.title; Xwitter.TXT.value = '@' + this.alt + ' '; Xwitter.focus(); }, update: function() { var xhr = new XMLHttpRequest; xhr.onreadystatechange = function() { if (xhr.readyState !== 4 || xhr.status !== 200) return; Xwitter.refresh(); Xwitter.escape(); Xwitter.notify(); }; xhr.open('post', [ protocol, '://twitter.com/statuses/update.xml?', this.TXT.id, '=', encodeURIComponent(this.TXT.value), '&', this.HDN.id, '=', this.HDN.value, '&source=xwitter' ].join('')); xhr.send(null); Xwitter.wait(); }, focus: function() { this.FRM.style.display = 'block'; this.TXT.focus(); }, escape: function() { this.FRM.style.display = 'none'; this.TXT.value = this.HDN.value = ''; }, findURL: function() { if (!Xwitter.HTT.exec(this.textContent)) return; var url = RegExp.$1; var xhr = new XMLHttpRequest; xhr.onreadystatechange = function() { if (xhr.readyState !== 4) return; Xwitter.URL.value = xhr.status !== 200 ? url : eval('(' + xhr.responseText + ')').url || url; Xwitter.URL.select(); Xwitter.notify(); }; xhr.open('get', 'http://ss-o.net/api/reurl.json?url=' + encodeURIComponent(url)); xhr.send(null); Xwitter.wait(); }, wait: function() { Xwitter.BOX.style.cursor = 'wait'; }, notify: function() { Xwitter.BOX.style.cursor = 'auto'; } };
-
-
-
- xwitter.mod
-
-
<?xml encoding="utf-8"?> <!ENTITY username '***************'> <!ENTITY password '***************'> <!ENTITY sec '90'> <!ENTITY w '480'> <!ENTITY h '640'> <!ENTITY x '0'> <!ENTITY y '0'> <!ENTITY regexp '\n'> <!ENTITY protocol 'https'>
-
-
-
- xwitter.xsl
-
-
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml" version="1.0"> <xsl:output method="xml" encoding="utf-8" omit-xml-declaration="no" doctype-public="-//W3C//DTD XHTML 1.1//EN" doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" indent="no" media-type="application/xhtml+xml" /> <xsl:template match="statuses"> <html xml:lang="ja"> <head profile="http://www.w3.org/2003/g/data-view"> <title>Xwitter</title> <link rel="transformation" href="http://www.kanzaki.com/parts/xh2rdf.xsl" /> </head> <body> <xsl:apply-templates select="status" /> </body> </html> </xsl:template> <xsl:template match="status"> <div class="section" id="{generate-id()}"> <xsl:apply-templates select="user" /> </div> </xsl:template> <xsl:template match="user"> <img src="{profile_image_url}" alt="" /> <input type="hidden" title="{../id}" class="{../favorited}" /> <input type="hidden" title="{../id}" class="reply" alt="{screen_name}" /> <em><xsl:value-of select="screen_name" /></em> <p> <xsl:if test="protected = 'true'"> <xsl:attribute name="class">protect</xsl:attribute> </xsl:if> <xsl:value-of select="../text" /> </p> </xsl:template> </xsl:stylesheet>
-
-
- icons
- default
- main.ico
- default
- icons
- defaults
- preferences
- prefs.js
- preferences
-
pref('toolkit.defaultChromeURI', 'chrome://xwitter/content/main.xul'); pref('browser.dom.window.dump.enabled', true); // pref('network.proxy.autoconfig_url', 'http://example.org/proxy.pac'); // pref('network.proxy.type', 2);