tag:blogger.com,1999:blog-72862954153311432552009-05-22T00:28:21.947+08:00陆离斑「博」纷总总其离合兮,斑驳陆离其上下wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.comBlogger132125tag:blogger.com,1999:blog-7286295415331143255.post-800795135288786202009-05-05T14:33:00.005+08:002009-05-05T14:47:19.967+08:00不要低估一颗姚明的心/总决赛级别的防守和开拓者的系列赛开始前一样,我也列出了三个关键点。<br />
<br />
<b>首先,火箭能不能防住 Gasol 是最重要的。</b>去年总决赛中 Gasol 被 Perkins 和 Kevin Garnett 彻底遏制是 Celtics 的取胜的关键之一,当 Gasol 被遏制的时候,湖人就回到了那支 05 年被太阳翻盘的只有 Kobe 一个人战斗的湖人。这一点今天火箭做得最好,在最后时刻的几次补篮前,Gasol 似乎只有 13 投 3 中。<br />
<br />
<b>其次,保护住防守篮板,尤其是第二阵容。</b>湖人是常规赛中进攻篮板效率第三好的球队,前两名是波特兰和费城。火箭第一轮让开拓者场均拿到了 9.8 个进攻篮板,虽然比他们的赛季平均要少了 3 个,但光 Przybilla 和 Oden 两个人就贡献 5.0 个前板。而湖人在季后赛的表现也不比开拓者差,Gasol 和 Odom 合起来也能在爵士头上拿到 5.1 个进攻篮板。从这场比赛中,我们也可以看到火箭的第二阵容 Hayes + Landry 在高度上相差很多,光 Odom 一个人就拿到了 4 个前板,整场比赛湖人拿到了 12 个前场篮板。<br />
<br />
<b>第三,和开拓者的时候一样,当火箭在防守端做到最好的时候,Aaron Brooks 或 Artest 能不能在进攻端拿到足够取胜的分数。</b>而且,这一点比对开拓者的时候更重要,因为湖人是全联盟攻守转换速度最快的几支球队之一,当他们能够保证自己的后场篮板的时候,快攻会非常之多。<br />
<br />
也许你会奇怪三条里面,没有一条是关于 Kobe 的。但绝对不是对 Kobe 的防守不重要,而是说这个地球上没有一支球队可以完全限制住 Kobe Bryant,你所能做的就是在比赛开始前为他准备好 30 分,在他投篮的时候遮住他的双眼,在他突破的路线上让姚明挡在他的身前,当他起跳的时候往他身上扔三四个人,接下来就只有看上帝更爱谁了。。。话说回来,当姚明能够顶防 Kobe 的挡拆的时候,火箭似乎又变回了 Van Gundy 时代的那支“即使 Kobe 拿到50分我还是能赢球”的火箭队。<br />
<br />
最后说一句,裁判也是人。凡是看到一个人血流满面的站在面前的时候,难免会动恻隐之心;凡是看到一个7尺6的巨人痛苦倒地后坚持完比赛的时候,难免会心生敬佩。于是,湖人的犯规以 26 比 14,火箭的罚球以 29 比 19 远远领先于对手,最后几次的吹罚也不像是在斯坦普斯中心了。<br />
<br />
最后的最后,希望姚明的膝盖没事。<br />
<br />
<b>更新一下:看起来姚明的膝盖没事。这是不是有点像去年总决赛的第一场 Paul Pierce 被人抬出球场的情景?</b><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-80079513528878620?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-77318444265112548482009-05-01T14:37:00.010+08:002009-05-01T23:34:36.240+08:00恭喜火箭破处/Batum 的价值/加强版的开拓者火箭 4 - 2 开拓者。恭喜姚明7年职业生涯第一次进入西区半决赛。恭喜火箭12年来第一次进入第二轮。<br><br>今天 Batum 用另类的方式证明了自己的价值。之前的五场比赛中,Artest 场均 13.4 分,命中率 37.7%,三分球命中率 28.0%,惨不忍睹情何以堪。但是第六场比赛中,之前场均 1.8 分的 Batum 被开拓者主教练 McMillan 摁在了板凳上,整场比赛用 Roy 或 Outlaw 对位,让 Artest 一口气轰下职业生涯的季后赛新高 27 分。结果论而说,McMillan 这一变招相当的失败。<br><br>之前也说过,Artest 在面对身高臂长跑跳出色的防守者的时候,效率非常一般。所以,即使 McMillan 不希望再看到 Batum 出现在轮转阵容中,也应该把 Outlaw 放到先发。McMillan 把 Rudy Fernandez提拔,只得用 Roy 去防守 Artest。但 Roy 整场比赛都无法限制 Artest 和姚明的挡拆。有意思的是,昨天 Artest 莫名的大夸 Roy 更胜 Kobe 和 LeBron 之余,也说了 Roy 在防守端还不是精英级别的,马上就被验证了。<br><br>火箭接下来的对手是湖人。湖人从各种角度来说,都是开拓者的加强版——这也是人们看好开拓者的未来的理由之一。相对应的是,火箭偶尔也会被人叫做山寨版的凯尔特人。除了“伪三巨头”之外,<b>火箭的防守体系,和去年在总决赛中将 Kobe 困于牢笼的凯尔特人的一样,师承防守大师 Tom Thibodeau 之手是一个很重要的原因。</b><br><br>在凯尔特的体系中,Allen,Pierce 和 Posey 负责对 Kobe 贴身紧逼;Kevin Garnett 的活动范围非常大,他从进入三分线的那一刻起就开始夹击 Kobe;KG 身后的 Perkin 是最后一堵墙。相比之下,由 Battier 和 Artest 组成的火箭的侧翼防守不会输给任何一只球队;但是姚明以往在三秒区内更有威慑力,但中距离的挡拆防守和 KG 不能相提并论;而一旦姚明扑出去,身后的 Scola 能不能像 Perkins 一样保护好篮下也是一个问号。这也是湖人本赛季能横扫火箭的一个因素之一。<br><br>但是,今天的姚明在防守端拿出了一个赛季以来的最高水准。他的活动范围直到 FIBA 的三分线,像一把大锁一样卡在了火箭的油漆区外,上一场被 Blake 和 Rudy 们频频利用的中距离空间,这一场消失殆尽。有那么一瞬间让人以为那是去年季后赛中的 Kevin
Garnett。像凯尔特人那样打出总决赛级别的防守,这是火箭和湖人对抗唯一的选择。<br><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-7731844426511254848?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-23100230218801534492009-04-27T14:08:00.002+08:002009-04-27T14:09:47.473+08:00破绕前的终章<a href="http://blog.luliban.com/2009/04/roy.html" id="qnwf" title="上场比赛后说了">上场比赛后说了</a>,火箭的破绕前战术分为三部曲:弱侧突破,罚球线跳投,以及把球给到姚明。今天火箭终于做到了最后一条,姚明的14次出手比前两场总数还要多。三场比赛一场一个台阶,Adelman 展现出了季后赛大师级的调整能力,这是我在 Van Gundy 时代的火箭身上所没有看到的。<br />
<br />
接下来的比赛中,火箭还要继续加强对开拓绕前战术的解读能力。如果有弱侧的队员来协防,后卫就要把球从强侧快速的移动到弱侧;如果是由 Aldridge 沉底补防,对位的 Scola 或 Landry 就应该上提到罚球线来策应,或者直接跳投。如果只有 Przybilla 或 Oden 一个人在绕前,姚明可以从强侧横要到弱侧,球也要随之转移。因为开拓者肯定会混合是用这三种不同的绕前战术,而回到玫瑰花园的时候,Przybilla 不可能再开场两分钟就被吹下去,Oden 的大动作拉扯也多少会被无视一些。<br />
<br />
再说说 Roy 的改变,前两场比赛中 Roy 在挡拆中的选择无非是跳投和突破。今天 Roy 在挡拆后,不再一味的利用速度冲击火箭的内线,而是适时的收步,吸引火箭其他球员收缩防守后,再把球传到三分线外。沉寂了三场的 Travis Outlaw 和 Steve Blake 由此得到解放,两人联手在三分线外 9投4中。总的来说,这支开拓者已经越来越像那支在常规赛两次击败湖人的开拓者了。<br />
<br />
再说一个细节,Lowry 连续两场比赛在最后关头被 Rudy Fernandez 颜射进了大号三分,Lowry 的身高更适合去防守 Blake,也许 Adelman 会在下一场做出调整。<br /><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-2310023021880153449?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com2tag:blogger.com,1999:blog-7286295415331143255.post-41867900873327468802009-04-25T13:42:00.003+08:002009-04-25T13:51:19.307+08:00对 Roy 的挡拆防守 / 火箭破绕前还差三分之一解说的时候,苏群说了一句话,“火箭就像是开拓者的陪练,不管赢没赢下这个系列赛,这都成为开拓者成长道路上的一个磨练”。其实反过来说也合适,火箭这赛季的两个阿喀琉斯之踵——姚明的挡拆防守和为姚明破绕前——通过和开拓者的系列赛,你可以清楚的看到火箭这两个最大的漏洞正在逐渐的被补上。<br />
<br />
当 Brandon Roy 挡拆进攻时,姚明不会再像常规赛一样 Shadow Post 沉在三秒区,而是和 Roy 面对面攻防,直到防守 Roy 的 Artest 或 Battier 回到他的防守位置为止。这也是今天火箭能把 Roy 18 次出手仅命中 6 球的两个主要原因之一。另一个就是火箭的主场哨保护了姚明,不像在玫瑰花园第二场比赛的时候一样,很容易被 Roy 的突破来制造犯规。相信在接下来的系列赛当中,Roy 恐怕很难再像第二场一样彪到 42 分的高分了。<br />
<br />
再说说绕前战术,这是一种赌博性的战术。当一个大个子对姚明执行绕前的时候,身后必然会留下很大的空位,这需要其他球员来填补身后的空位。<b>你可以把绕前战术想象成一颗洋葱头,开拓者的防守球员就像一层一层的洋葱皮,而姚明就被围困在其中心。</b><br />
<br />
火箭的破绕前战术可以分为三种。第一种是利用弱侧的空档进行突破,这在第二场比赛中火箭已经做到了极致,Von Wafer 的坚决突破拿到了 20 分,以及 Aaron Brooks 或 Luis Scola 的弱侧挡拆在第三节也为火箭拉开7分的领先优势。虽然火箭第二场输了,但开拓者的主教练 McMillan 还是意识到了问题并作出了调整,在第三场比赛中让弱侧的球员更注意盯防火箭的外线球员。所以,火箭在第三场比赛中,也有几次成功的在弱侧给到了姚明皮球。<b>火箭的弱侧进攻已经剥去了洋葱头的第一层皮。</b><br />
<br />
既然弱侧必须兼顾,McMillan 只有让大前锋 Aldridge 来做补防的工作。火箭的第二种破绕前战术正是针对这一点,Luis Scola 和 Carl Landry 的罚球线上提,在中距离让开拓者付出代价。今天比赛的上半场就是一个经典案例,Scola 上半场连投带突12投6中13分全队最高,这种表现甚至让 McMillan 一度放弃了绕前战术,因此下半场的前三次进攻中姚明都在强侧很轻松的拿到了皮球。<b>火箭的大前锋为姚明剥去了洋葱头的第二层皮。<br /><br /></b>但是 Scola 的第四次犯规下场改变了整个比赛的进程。虽然 Landry 上半场中距离发炮5投5中效率惊人,但他不像 Scola 一样有杀伤能力,所以 McMillan 又恢复了绕前防守,甚至既不让弱侧进行补防,也不让大前锋协防。洋葱头就剩下最后一层皮——Przybilla 和 Oden 的独力绕前,<b>这时候开拓者的绕前战术就变成一种纯赌博性的战术,就赌火箭的外线不能够抓住那 0.5 秒的机会,把球给到内线的姚明。</b><br />
<br />
最后的结果不必多说,虽然比赛赢了,但看看姚明的出手数(7次,只比上一场多了1次而已)就知道火箭的破绕前战术并没有成功。但也不能说完全失败,因为现在这颗洋葱头就只剩下一层皮,火箭也不是没有剥皮成功的经验——常规赛对开拓者的那一场大胜就是例子。<br />
<br />
如果火箭破得了绕前,防得了挡拆,那么火箭基本上就可以战胜开拓者了。而第二轮的对手湖人是一支和开拓者结构非常类似的球队,Kobe 之于 Roy,Bynum 之于 Oden,Gasol 之于 Aldridge,第一轮的宝贵经验会成为火箭对抗湖人的筹码。<br />
<br />
<br />
豆瓣的认证码:doubanclaim5ca247f8bb42286e<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-4186790087332746880?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-49659943482927406852009-04-19T14:06:00.003+08:002009-04-19T14:13:27.743+08:00姚破绕前/像 Kobe 一样打挡拆/Ron 和 AB 的进攻效率今天比赛之前,给火箭列出了三个问题。<br />
<br />
<b>姚明和火箭如何应对 Przybilla + Oden + Aldridge 的绕前防守?</b><br />
开拓者不会整场比赛都进行绕前,但在关键时刻适当的战术变化却是秃子头上的虱子——明摆的事。应该说,McMillan 今天的绕前来得有点晚,直到第三节的前六分钟开拓者才开始对姚明进行绕前,而这个时候开拓者已经落后20分。不过效果也是立竿见影,Artest 和 Brooks 接连几次不合理的出手,但之后裁判的主场哨反而帮了倒忙。把姚明四次犯规吹下去之后,火箭把防守端的一个大窟窿给补上了。在领先20分的情况下,防守比进攻更显重要,只要不让对手打出高潮,一场胜利基本跑不掉。<br />
<br />
但火箭不可能场场比赛都在第三节领先20分,如果在比分焦灼的情况下,开拓者绕前的话,火箭的执行情况会怎样呢?<br />
<b><br />Roy 能不能像 Kobe 一样利用姚明挡拆的弱点?</b><br />
McMillian 从第一节开始就让 Roy 打击姚明的挡拆,并早早的让姚明背上一次犯规。但如果看了 Kobe 对火箭的录像,Kobe 在上半场总是会让队友充分的热身,直到第三节或第四节才开始打挡拆。因为挡拆很难让其他球员融入到整体进攻中来,过早的使用就会陷入今天开拓者的窘境。今天的 Roy,比起 Kobe 来更像前几天和大败给火箭的 Chris Paul。<br />
<br />
另外和常规赛不同,姚明在防守挡拆时会出来顶防,而让两个底角的防守者收缩禁区。但开拓者是联盟第一的进攻篮板球球队,如果姚明出去,身后的篮板球是不是会成为另外一个问题?<br />
<br />
<b>Artest 和 Brooks 的进攻效率?</b><br />
说实话,比赛之前,我有点担心 Artest 和 Brooks 的进攻效率。因为本赛季面对跑跳素质出色的防守者的时候,Artest的效率相当糟糕。对湖人的 Ariza 只有 32.6% 的命中率,对开拓者的 Batum 和 Outlaw 也只有 36.4%。Brooks 在三月份虽然能以 44% 的命中率场均砍下 14.9 分,但四月份收官阶段的表现一般,得分和命中率分别只有 9.9 分和 36.1%。<br />
<br />
但从今天的表现来看,我是想多了。Artest 的 17分和 AB 的 27分4板7助都是比赛的关键,但两个人唯一的瑕疵都来自于第三节前半段姚明被绕前的时候,也许下次 McMillan 会更早更坚决的执行绕前战术。<br />
<br />
总的来说,火箭今天虽然大胜,但仍不足喜。<br />
<ol>
<li>这是开拓者第一场季后赛,这是他们应缴的学费,Aldridge 和其他开拓者球员不可能每一场系列赛都打出这种效率。</li>
<li>即使在总篮板球数上落后(火箭投失的球少),开拓者还是抓到了 15 个进攻篮板,如果比分焦灼着进入第四节,这会是一个威胁。</li>
<li>虽然只有短短六分钟,火箭在绕前问题上仍然没有很好的解决方案。</li>
</ol>
<br />
再说个开拓者的亮点,今天 Oden 算是小爆了大叔。不过,如果对比一下大叔对 O'Neal 的防守——双腿趴开,重心前压,两只手死顶在 O'Neal 背上——和对 Oden 的防守——双腿直立,两只手高举,就想着扇个帽摇手指——你就知道大叔多少有点轻敌了。<br /><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-4965994348292740685?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-82335630075755356162009-03-02T03:33:00.006+08:002009-03-02T03:45:25.164+08:00authdouban 快速上手<a href="http://github.com/wuyuntao/appengine-authdouban/tree/master" title="authdouban">authdouban</a> 是一个即插即用(pluggable)的 AppEngine 应用,可以让你的 web 应用支持<a href="http://www.douban.com/service/apidoc/auth" title="豆瓣的 OAuth 认证">豆瓣的 OAuth 认证</a>和管理已授权的豆瓣帐户。authdouban 目前的版本是 <a href="http://github.com/wuyuntao/appengine-authdouban/blob/a5df957e8778d3dfb3d69db060d3efd008683060/VERSION" title="0.1">0.1</a>。欢迎大家的建议和意见,你可以 <a href="http://github.com/" title="GitHub">GitHub</a> 的仓库中检出或<a href="http://github.com/wuyuntao/appengine-authdouban/zipball/master" title="下载">下载</a>。<br />
<br />
<h3>
视图 Views</h3>
<br />
authdouban 由下面五个 views 组成。<br />
<ul>
<li>list accounts : 列出用户已授权的豆瓣帐户</li>
<li>authorize : 将用户被重定向到豆瓣的授权页面。用户同意(不同意)授权后,返回到授权成功(失败)页面</li>
<li>authorization complete : 授权成功页面</li>
<li>authorization failure : 授权失败页面</li>
<li>delete account : 删除已授权的豆瓣帐户</li>
</ul>
<br />
<h3>
依赖 Requirements</h3>
<ul>
<li><a href="http://code.google.com/p/gdata-python-client/" title="gdata-python-client">gdata-python-client</a> </li>
<li><a href="http://code.google.com/p/douban-python/" title="douban-python">douban-python</a> </li>
</ul>
<br />
<h3>
设置 Settings</h3>
<br />
应用的设置保存在 settings.py,一共有五个选项。首先要填写应用的豆瓣 API key,如果没有的话,你可以到<a href="http://www.douban.com/service/apikey/apply" id="lgdt" title="豆瓣的 API 申请页面">豆瓣的 API key 申请页面</a>去填表获得。<br />
<pre class="codes"><code>DOUBAN_API_KEY = 'yourapikeyhere'
DOUBAN_API_SECRET = 'yourapisecrethere'</code></pre>
<br />
选择是否将豆瓣用户的档案信息(比如用户名,城市,头像 URL 等)保存在服务器上。如果是,应将 STORE_DOUBAN_PROFILE 设为 True,反之 False。如果不在服务器上保存,也可以在生成页面时通过 javascript 抓取,这样会比较即时。<br />
<pre class="codes"><code>STORE_DOUBAN_PROFILE = True</code></pre>
<br />
如果把用户资料保存在服务器上,不可避免会有一个资料过期的问题,不过 authdouban 支持自动更新用户档案的信息。你可以修改 MAX_CACHE_TIME 来设置一个缓存的有效时限(默认为 24 小时),也就是如果用户信息最近一次更新的时间超过了 24 小时,则自动从豆瓣抓取新的用户信息。如果将 MAX_CACHE_TIME 设为 0,就永远不会更新用户信息。不过,根据<a href="http://www.douban.com/service/apidoc/terms" id="jvbk" title="豆瓣 API 的使用条款">豆瓣 API 的使用条款</a>,在服务器端缓存的用户信息,不能超过 24 小时就是了。<br />
<pre class="codes"><code>MAX_CACHE_TIME = 24</code></pre>
<br />
最后一个选项是 MAX_STORED_ACCOUNTS,你可以用它限定一个用户最多可以授权的豆瓣帐户的数量。设为 0 的话,即为没有限制,不过 AppEngine 最多只能返回 1000 条查询。如果超过了限定,authdouban 会自动删除一个最早授权的豆瓣帐户。<br />
<pre class="codes"><code>MAX_STORED_ACCOUNTS = 10</code></pre>
<br />
<h3>
URL 映射</h3>
<br />
URL 映射保存在 urls.py 文件中,和上面的视图相对应。<br />
<ul>
<li>/account/douban/</li>
<li>/account/douban/authorize/</li>
<li>/account/douban/authorize/complete/</li>
<li>/account/douban/authorize/failure/</li>
<li>/account/douban/delete/<id>/</li>
</ul>
<br />
需要特别注意的是<br />
<blockquote><p>
如果你修改了 urls.py 中的映射,那你也要改一下 views.py 中相应的 URL(wyt:可以在文件中搜一下 "/account/douban/")。我不知道 AppEngine 中怎样才能实现像 <a href="http://docs.djangoproject.com/en/dev/topics/http/urls/#reverse" title="django.core.urlresolvers.reverse">django.core.urlresolvers.reverse</a> 一样的逆解析,所以只能手动改了。如果有同学知道怎么做,请一定要留言~</p></blockquote>
<br />
<h3>
模板 Templates</h3>
<br />
模板在 templates/authdouban 文件夹下,你可以 copy 到你的应用的模板目录<br />
<ul>
<li>list_account.html : 列出已授权的豆瓣帐户</li>
<li>delete_account.html : 删除豆瓣帐户的授权</li>
<li>complete.html : 授权成功</li>
<li>failure.html : 授权失败</li>
<li>douban_user.html : 豆瓣用户档案的模板</li>
<li>douban_user_no_delete.html : 也是豆瓣用户档案的模板,但是没有“删除帐户”的按钮</li>
</ul><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-8233563007575535616?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-85861742307493862712009-02-19T00:36:00.011+08:002009-03-02T03:45:25.164+08:00authdouban: 在 AppEngine 上管理豆瓣授权帐户<a href="http://github.com/wuyuntao/appengine-authdouban/tree/master" title="authdouban">authdouban</a>,是一个管理豆瓣授权帐户的即插即用(pluggable)的 AppEngine App,目前支持添加,删除和管理多个经授权的豆瓣帐户。authdouban 还在开发中,非常需要大家的参与和建议,欢迎留言。<br />
<br />
买一送一,authdouban 中还包括一个<a href="http://code.google.com/p/douban-python/">豆瓣官方的 Python 客户端</a>项目的分支版本。这个分支版本会跟着豆瓣官方的上游版本更新,<strike>但是会将用到 httplib 的代码替换成 urlfetch 方法</strike>(wyt: <a href="http://googleappengine.blogspot.com/2009/02/sdk-version-119-released.html">AppEngine SDK 1.1.9</a> 已经支持 httplib 了,所以不需要再用 urlfetch 替换 httplib 了,不过还是<a href="http://code.google.com/p/douban-python/issues/detail?id=5" title="有些地方需要修改">有些地方需要修改</a>),使之在 AppEngine 上可以顺利运行。<br />
<br />
<b>2009-02-21 更新:</b>豆瓣接受了<a href="http://code.google.com/p/douban-python/issues/detail?id=5&can=1">我提交的patch</a>,所以如果从官方 SVN 中检出最新的(<a href="http://code.google.com/p/douban-python/source/detail?r=47">r47</a>)的上游版本和 authdouban 中的分支版本是一样的。<br />
<br />
你可以<a href="http://authdouban.luliban.com/account/douban/" title="点击这里">点击这里</a>尝试一下用 authdouban 实现的简单界面。下面有几张截图。<br />
<br />
授权成功后。<br />
<div>
<a href="http://lh5.ggpht.com/_sKun2mxXeAk/SZw4XNzcaQI/AAAAAAAAA6s/AT1vN0WvyWc/s800/authentication-complete.png"><img src="http://lh5.ggpht.com/_sKun2mxXeAk/SZw4XNzcaQI/AAAAAAAAA6s/AT1vN0WvyWc/s400/authentication-complete.png" /></a>
</div>
<br />
已授权的帐户列表。<br />
<div>
<a href="http://lh3.ggpht.com/_sKun2mxXeAk/SZw4XAoqG3I/AAAAAAAAA6k/JNB0aXfKEv0/s800/account-list.png"><img src="http://lh3.ggpht.com/_sKun2mxXeAk/SZw4XAoqG3I/AAAAAAAAA6k/JNB0aXfKEv0/s400/account-list.png" /></a>
</div>
<br />
删除已授权的豆瓣帐户。<br />
<div>
<a href="http://lh5.ggpht.com/_sKun2mxXeAk/SZw4XE8Q6AI/AAAAAAAAA60/lEZaAC3i0F4/s800/delete_account.png"><img src="http://lh5.ggpht.com/_sKun2mxXeAk/SZw4XE8Q6AI/AAAAAAAAA60/lEZaAC3i0F4/s400/delete_account.png" /></a>
</div><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-8586174230749386271?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com5tag:blogger.com,1999:blog-7286295415331143255.post-1576423698008660862009-02-16T23:40:00.003+08:002009-02-16T23:50:11.914+08:00全明星观感Pheonix 有可能是进入21世纪以来最落寞的一届全明星主场。<br />
<br />
金融风暴的阴影自不用说。Steve “主场球队的老大外加过去两届常规赛 MVP” Nash,居然被排除在全明星阵容之外,连替补名单 David Stern 也没给个面子。在 O'Neal 的阴影下郁郁不得志的 Amare Stoudamire 被球队放在交易市场上引来超过十支球队(包括火箭)的报价。<a title="太阳的主教练 Terry Porter 半个小时前刚被球队炒了鱿鱼" href="http://bbs.hoopchina.com/536087.html" id="f5wc">太阳的主教练 Terry Porter 一个小时前刚被球队炒了鱿鱼</a>,接任者的优点唯有听话二字,用 Stoudamire 的话说,A great players' coach。<br />
<br />
聊以欣慰的是,Shaq O'Neal 还是为 Phoenix 的主场球迷还是捧回了全明星赛 MVP 的奖杯。不过比起 Phoenix 来,Shaq 似乎对与他分享奖杯的 Kobe 和教练席上的 Phil Jackson 所在的城市更感兴趣一些。所谓落叶归根嘛。<br />
<br />
<h4><a title="奥尼尔:我把最棒的表演留给全明星赛" href="http://bbs.hoopchina.com/read.php?tid=536633" id="sq4r">奥尼尔:我把最棒的表演留给全明星赛</a></h4>
<blockquote>
<p>奥尼尔依旧是全明星赛上的那个大活宝。不过所有人都对奥尼尔是否能够参与下届全明星赛报以怀疑的态度。即将在3月6日迎来自己37岁生日的奥尼尔本人也不例外。<br><br>当奥尼尔被问及在球员介绍时候所表演的面具舞时,“Jabberwockees是最专业的舞蹈团队,但是我在舞蹈中尽量表现的出挑。”他接着说,“因为我意识到这也许是我最后一次站在全明星的舞台上了,所以我要在菲尼克斯全明星赛上给所有球迷和我自己都留下令人难忘的表现。”<br><br>奥尼尔全明星赛出场10分55秒内得到17分5篮3助,他和科比共同分享全明星赛MVP奖杯。赛后西部全明星主教练菲尔-杰克逊说,“这是我第一次见到一名球员仅仅在全明星赛场呆了11分钟就获得了MVP。但是毫无疑问他的表现让人印象深刻。”</p></blockquote><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-157642369800866086?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-45932044138993527272009-01-13T10:24:00.006+08:002009-01-14T17:40:35.551+08:00九点,把文章标题还给我的链接~<ol>
<li>在九点的阅读器里,只有点击“查看原文”才能进入原帖。点击标题只会扩展文章,而不是像原来的九点一样进入原帖。如
<a href="http://9.douban.com/reader/">http://9.douban.com/reader/</a> </li>
<li><strike>在某些页面展开文章以后,甚至没有办法找到原帖的链接</strike></li>
<li>有的 feed 只输出摘要,你不得不再点击一次标题收起文章后,才能找到“查看原文”打开原帖</li>
<li>点击标题栏扩展文章的功能,Google Reader,鲜果和抓虾都有,但她们都会将标题指向原帖,用户点击旁边的留白时才会扩展文章</li>
<li>一个有些尴尬的设计,看不出对九点有什么实质的好处,可是这对 blogger 并不友好</li>
<li>没有 blogger 会喜欢一个阅读器抓取他们的内容后,不给链接,或者只给一个12px大小的“查看原文”链接</li>
<li>总之,请把文章标题还给原文链接,就像在九点的频道里那样~<br /></li>
</ol><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-4593204413899352727?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com5tag:blogger.com,1999:blog-7286295415331143255.post-27229596018271779192009-01-08T14:54:00.015+08:002009-01-23T02:05:54.229+08:00eBook Viewer - 用豆瓣 + Gears 管理本地电子书<b><a href="http://ebookviewer.appspot.com/" id="n6bq" title="eBook Viewer">eBook Viewer</a> (中文名没想好)是一个用<a href="http://www.douban.com/" id="yuf2" title="豆瓣">豆瓣</a>提供的书籍信息管理本地电子书的工具。</b>用户可以为电子书文件和豆瓣书目建立一一对应的关系,并利用豆瓣搜索来查找和管理这些电子书。欢迎大家试用,如果有什么问题的话,欢迎留言,或者加入 <a href="http://www.douban.com/group/ebookviewer/">eBook Viewer 小组</a>讨论。<br />
<br />
开发的起因是,我的硬盘上存了 10G+ 电子书,怎样快速的找到想要的书渐渐变成了一个问题。虽然基本上有分类存储,但一些类别很微妙的书就说不清当初放哪里了;还有不少书是从 BT or eMule 上打包拖下来,文件名相当混乱,搜索文件名的办法也不太管用;所以,我想到是不是可以把这些电子书和豆瓣上的条目对应起来,方便以后的查找和管理。于是就有了 eBook Viewer。<br />
<br />
eBook Viewer 用到了<a href="http://www.douban.com/service/apidoc/">豆瓣 API</a> + <a href="http://gears.google.com/">Google Gears</a> 的组合。因为 Gears 提供了 Javascript 跨域访问的功能,所以<a href="http://blog.luliban.com/2008/11/google-gears-api.html">查询豆瓣 API 的请求</a>都是从客户端发出的,获取和分析 JSON 用的是自己写的 <a href="http://blog.luliban.com/2008/11/jquery-douban-api-gdata-json.html" id="qlo1" title="jQuery Douban">jQuery Douban</a> 插件,可以方便的和 Gears 协同工作。<br />
<br />
添加电子书的时候,电子书除了和相关的豆瓣书目一起被记录到数据库之外,eBook Viewer 也将电子书保存到本地的 Store 中,并提供一个本地的下载链接如 <a href="http://ebookviewer.appspot.com/book/wyt/stored/2/Python%20Cookbook.chm" id="t56q" title="http://ebookviewer.appspot.com/book/wyt/stored/2/Python Cookbook.chm">/book/wyt/stored/2/Python Cookbook.chm</a>,方便用户想要读这本电子书的时候可以保存到文件夹中。这个链接,虽然看起来像是远程链接,实际上只有在本地才有效。<br />
<br />
浏览器。我在 Firefox 3.0.5 (Linux + Windows),IE 7 和 Chrome 1.0 上测试过 eBook Viewer,都可以正常运行。Safari for Mac 如果有安装 Gears 的话理论上也可以,但我就没有条件测试了。不打算支持 IE6 及其更糟的 IE 浏览器。<br />
<br />
如果需要备份系统,问到电子书被保存在哪个文件夹,具体可以参考 <a href="http://code.google.com/apis/gears/api_database.html#directories" id="ap7q" title="Google Gears 的文档">Google Gears 的文档</a>。<br />
<br />
一般来说,Linux + Firefox 用户的电子书会被保存在<br />
~/.mozilla/firefox/tb3ujwvc.default/Google\ Gears\ for\ Firefox/ebookviewer.appspot.com/<br />
<br />
Windows XP + Firefox 用户是在<br />
C:\Documents and Settings\Bob\Local Settings\Application Data\Mozilla\Firefox\Profiles\uelib44s.default\Google Gears for Firefox\ebookviewer.appspot.com\<br />
<br />
Window XP + IE 用户是在<br />
C:\Documents and Settings\Bob\Local Settings\Application Data\Google\Google Gears for Internet Explorer\ebookviewer.appspot.com\<br />
<br />
<h3>
eBook Viewer 使用流程</h3>
第一步,是在右侧的搜索框里输入你想要添加的书本的关键字,如 python,并回车。eBook Viewer 会从豆瓣 API 返回搜索的结果。<br />
<br />
<a href="http://lh4.ggpht.com/_sKun2mxXeAk/SWTWXQrSfOI/AAAAAAAAA24/hVHlUaQKawU/s800/0_ready_to_search.png"><img src="http://lh4.ggpht.com/_sKun2mxXeAk/SWTWXQrSfOI/AAAAAAAAA24/hVHlUaQKawU/s400/0_ready_to_search.png" /></a>
<br />
<br />
<a href="http://lh3.ggpht.com/_sKun2mxXeAk/SWTXUd7x8cI/AAAAAAAAA3c/ko9rAQin12w/s800/1_search_result.png"><img src="http://lh3.ggpht.com/_sKun2mxXeAk/SWTXUd7x8cI/AAAAAAAAA3c/ko9rAQin12w/s400/1_search_result.png" /></a><br />
<br />
第二步,选择你想要添加的书如 Python Cookbook,单击右键并“打开”,或者直接双击,弹出一个包含基本的书本信息和电子书存档记录的对话框。<br />
<br />
<a href="http://lh3.ggpht.com/_sKun2mxXeAk/SWTXUeFt4tI/AAAAAAAAA3k/rq94fGZj1BQ/s800/2_open_book_dialog.png"><img src="http://lh3.ggpht.com/_sKun2mxXeAk/SWTXUeFt4tI/AAAAAAAAA3k/rq94fGZj1BQ/s400/2_open_book_dialog.png" /></a>
<br />
<br />
<a href="http://lh4.ggpht.com/_sKun2mxXeAk/SWTXU3AAuGI/AAAAAAAAA3s/iCH1ZeTZo4w/s800/3_book_dialog.png"><img src="http://lh4.ggpht.com/_sKun2mxXeAk/SWTXU3AAuGI/AAAAAAAAA3s/iCH1ZeTZo4w/s400/3_book_dialog.png" /></a>
<br />
<br />
第三步,点击“添加电子书”。你可以在弹出的文件对话框中,选中一本或多本电子书,如 Python Cookbook.chm,然后“OK”。一切顺利的话,这个 chm 文件就会被存储到 Gears Store 中,以后打开 Python Cookbok 的时候都可以看到这本电子书,你可以在想读的时候点击文件名,保存到本地文件夹。电子书的格式可以是文本(.txt),网页(.html),PDF 或 CHM 文件。<br />
<br />
<a href="http://lh4.ggpht.com/_sKun2mxXeAk/SWTXVIxTQKI/AAAAAAAAA30/8qs_wEUEfEM/s800/4_add_ebook_file.png"><img src="http://lh4.ggpht.com/_sKun2mxXeAk/SWTXVIxTQKI/AAAAAAAAA30/8qs_wEUEfEM/s400/4_add_ebook_file.png" /></a>
<br />
<br />
<a href="http://lh5.ggpht.com/_sKun2mxXeAk/SWTXVVmTqOI/AAAAAAAAA38/1pvwHboQugw/s800/5_ebook_file_added.png"><img src="http://lh5.ggpht.com/_sKun2mxXeAk/SWTXVVmTqOI/AAAAAAAAA38/1pvwHboQugw/s400/5_ebook_file_added.png" /></a>
<br />
<br />
如果你还想添加其他 Python 相关的电子,也不需要从第一步从头开始,你可以直接选择其他的书如 Dive into Python,从第二步开始做下去。如果你想返回初始页面,可以对着空处点击右键并“主页”。这样 eBook Viewer 会显示你已经收藏的电子书,另外右键菜单里可以翻页、选择多种排列和升降序组合。<br />
<br />
<a href="http://lh6.ggpht.com/_sKun2mxXeAk/SWTXuYnUYQI/AAAAAAAAA4E/xzN2UVsW4HA/s800/6_back_to_homepage.png"><img src="http://lh6.ggpht.com/_sKun2mxXeAk/SWTXuYnUYQI/AAAAAAAAA4E/xzN2UVsW4HA/s400/6_back_to_homepage.png" /></a>
<br />
<br />
<a href="http://lh3.ggpht.com/_sKun2mxXeAk/SWTXulYEiWI/AAAAAAAAA4M/_Sf8zCP1CZI/s800/7_home_page_context_menu.png"><img src="http://lh3.ggpht.com/_sKun2mxXeAk/SWTXulYEiWI/AAAAAAAAA4M/_Sf8zCP1CZI/s400/7_home_page_context_menu.png" /></a><br /><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-2722959601827177919?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com22tag:blogger.com,1999:blog-7286295415331143255.post-60765991497183683752009-01-03T13:22:00.007+08:002009-01-04T11:43:39.886+08:00悠言悠闲:2008年 Blog 小结<ol><li>39篇文章。去年初是希望能一周写一篇的,没有达成 :(<br></li><li><a title="Feedsky" href="http://www.feedsky.com/" id="aint">Feedsky</a> 有126个订阅,<a title="FeedBurner" href="http://www.feedburner.com/" id="s2uf">FeedBurner</a> 有 35个订阅,原始 feed 在 <a title="Google Reader" href="http://reader.google.com/" id="ty2r">Google Reader</a> 有 40 个订阅。水分大大的样子 :)</li><li>一年来有 13,000+ 访问和 20,000+ 页面浏览</li><li>8月7日是访问最多的一天,有 162 visits,大多数是冲着 <a title="Google Reader 风的豆瓣广播 widget" href="/2008/08/google-reader-style-douban-miniblog.html" id="ecd4">Google Reader 风的豆瓣广播 widget</a> 来的。感谢<a title="豆瓣" href="http://www.douban.com/" id="l9qd">豆瓣</a>友邻们的推荐先~</li><li>浏览器:Firefox 47%, IE 45%, Opera 3.1% and Safari 1.8%. Chrome 貌似很猛,才出场四个月已经有 2.6% :)<br></li><li>操作系统:Windows 80%, Linux 16%, Mac ~3%<br></li><li>搜索引擎:Google 81%。百度 17%,不过10月重定向域名之后,百度就不再收录我的 blog 了...</li><li>最多人看的5篇文章:
<ul><li><a title="绕过 GHS 用自定义域名发布 Blogger" href="/2008/05/ghs-blogger.html" id="tofk">绕过 GHS 用自定义域名发布 Blogger</a></li>
<li><a title="一个 jQuery 的时间选择插件" href="/2008/08/yet-another-jquery-time-picker-plugin.html" id="hifw">一个 jQuery 的时间选择插件</a></li>
<li><a title="在 Gentoo 上部署 Git + Gitosis 服务器" href="/2008/05/gentoo-git-gitosis.html" id="vz13">在 Gentoo 上部署 Git + Gitosis 服务器</a></li>
<li><a title="在 Google App Engine 上应用豆瓣 Python 客户端" href="/2008/04/google-app-engine-python.html" id="xt8i">在 Google App Engine 上应用豆瓣 Python 客户端</a></li>
<li><a title="Google Reader 风的豆瓣广播 widget" href="/2008/08/google-reader-style-douban-miniblog.html" id="r-qn">Google Reader 风的豆瓣广播 widget</a></li>
</ul></li>
<li>最少人看的5篇文章:<ul>
<li><a title="用 Gaupol 生成文本字幕" href="/2008/09/gaupol.html" id="gj4-">用 Gaupol 生成文本字幕</a></li>
<li><a title="Firefox 下载日" href="/2008/06/firefox.html" id="xpis">Firefox 下载日</a></li>
<li><a title="Battier or Artest,谁将首发?" href="/2008/09/battier-or-artest.html" id="s7b-">Battier or Artest,谁将首发?</a></li>
<li><a title="用curl获取内网的外部ip" href="/blog/2008/01/curlip.html" id="td50">用 curl 获取内网的外部 ip</a></li>
<li><a title="用 Google Gears 跨域调用豆瓣 API" href="/2008/11/google-gears-api.html" id="ugca">用 Google Gears 跨域调用豆瓣 API</a></li>
</ul></li><li>有来自 65 个国家的访问,最多的5个:中国 84%,台湾 4.3%,美国 2.7%,香港 2.4% 和日本 0.94% :)</li></ol><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-6076599149718368375?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com2tag:blogger.com,1999:blog-7286295415331143255.post-62164766792769403352008-12-06T00:15:00.006+08:002009-01-09T12:00:27.673+08:00有备无患:用 jQuery Douban 写 GreaseMonkey 脚本<a title="jQuery Douban" href="http://blog.luliban.com/2008/11/jquery-douban-api-gdata-json.html" id="bax7">jQuery Douban</a> 是一个简化豆瓣 API 应用开发的 jQuery 插件。jQuery Douban 支持用 GM_xmlhttpRequest 方法实现跨域访问,所以我们可以在 GreaseMonkey 脚本中方便的调用豆瓣 API。这篇文章会以<a title="友邻高亮脚本" href="http://userscripts.org/scripts/show/38149" id="bh3b">友邻高亮脚本</a>为例,说明在 GreaseMonkey 中使用 jQuery Douban 的方法。<br /><br />
<b>Metadata。</b>jquery.douban.js 依赖于 jquery.js、sha1.js 和 oauth.js,所以我们首先要在 UserScript Metadata 中加入这四个 js 文件。<br />
<pre class="codes"><code><span style="color: rgb(96, 160, 176);"><i>// ==UserScript==</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// @name Douban Highlight Friends Script</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// @namespace http://blog.luliban.com/</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// @description Highlight all your friends and contacts on every douban page</i></span>
<span class="lineno special"></span><span style="color: rgb(96, 160, 176);"><i>// @include http://www.douban.com/*</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><b><span style="color: rgb(96, 160, 176);"><i>// @require http://jquery-douban.appspot.com/media/scripts/jquery.js</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// @require http://jquery-douban.appspot.com/media/scripts/sha1.js</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// @require http://jquery-douban.appspot.com/media/scripts/oauth.js</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// @require http://jquery-douban.appspot.com/media/scripts/jquery.douban.js</i></span></b>
<span class="lineno special"></span><span style="color: rgb(96, 160, 176);"><i>// ==/UserScript==</i></span>
<span style="color: rgb(96, 160, 176);"></span></code></pre>
<br />
<b>初始化豆瓣 service。</b>除了向豆瓣申请的 API key 之外,用 $.douban 创建 service 实例的时候,还要将 type 设为 'greasemonkey',这样就可以用 GM_xmlhttpRequest 方法来跨域访问豆瓣 API 了。<br />
<pre class="codes"><code><span style="color: rgb(96, 160, 176);"><i>// API key</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> api_key <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(64, 112, 160);">'0107c5c3c9d4eaa40317514b5d7ec64c'</span><span style="color: rgb(102, 102, 102);">;</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> api_secret <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(64, 112, 160);">'7feaf4ec7b6779f8'</span><span style="color: rgb(102, 102, 102);">;</span>
<span style="color: rgb(96, 160, 176);"><i>// 初始化豆瓣service</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> service <span style="color: rgb(102, 102, 102);">=</span> $.douban({
<span style="color: rgb(96, 160, 176);" class="lineno"></span> key<span style="color: rgb(102, 102, 102);">:</span> api_key<span style="color: rgb(102, 102, 102);">,</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> secret<span style="color: rgb(102, 102, 102);">:</span> api_secret<span style="color: rgb(102, 102, 102);">,</span>
<span class="lineno special"></span> <b>type<span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(64, 112, 160);">'greasemonkey'</span></b>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> });
</code></pre><br />
<b>OAuth 授权认证。</b>虽然用户的友邻不是受限资源,即使不进行 OAuth 认证也一样可以获得,但是 OAuth 认证在豆瓣 API 应用中常常会用到,所以还是应该写一下 OAuth 认证。<br />
<br />
下面这段代码实现了获取 request token,将用户重定向到授权页面,用户同意授权后获取 access token 的过程。如果怕麻烦的话,可以把这段代码直接拷贝到你的脚本中。不过需要注意一下加粗的三行,将其中的两行限定了可以执行获取 access token 命令的页面,另外一条将授权命令注册到 GreaseMonkey 的脚本命令(User Script Commands)中。<br />
<pre class="codes"><code><span style="color: rgb(96, 160, 176);"><i>// 获取已存的 request token 和 access token</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> request_key <span style="color: rgb(102, 102, 102);">=</span> GM_getValue(<span style="color: rgb(64, 112, 160);">'request_key'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> request_secret <span style="color: rgb(102, 102, 102);">=</span> GM_getValue(<span style="color: rgb(64, 112, 160);">'request_secret'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> access_key <span style="color: rgb(102, 102, 102);">=</span> GM_getValue(<span style="color: rgb(64, 112, 160);">'access_key'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> access_secret <span style="color: rgb(102, 102, 102);">=</span> GM_getValue(<span style="color: rgb(64, 112, 160);">'access_secret'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span class="lineno special"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> user_id <span style="color: rgb(102, 102, 102);">=</span> GM_getValue(<span style="color: rgb(64, 112, 160);">'user_id'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(0, 112, 32);"><b>if</b></span> (access_key) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 如果 access token 已存,直接登录</i></span>
<span class="lineno special"></span> service.login({ key<span style="color: rgb(102, 102, 102);">:</span> access_key<span style="color: rgb(102, 102, 102);">,</span> secret<span style="color: rgb(102, 102, 102);">:</span> access_secret });
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 然后做些什么……</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);" class="lineno"></span> <b>} <span style="color: rgb(0, 112, 32);">else</span> <span style="color: rgb(0, 112, 32);">if</span> (request_key <span style="color: rgb(102, 102, 102);">&&</span> location.href.match(<span style="color: rgb(35, 83, 136);">/#friend_marker/</span>)) {</b>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 如果只有 request token 已存,则用request key获取相应的 access token</i></span>
<span class="lineno special"></span> service.getAccessToken({
<span style="color: rgb(96, 160, 176);" class="lineno"></span> key<span style="color: rgb(102, 102, 102);">:</span> request_key<span style="color: rgb(102, 102, 102);">,</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> secret<span style="color: rgb(102, 102, 102);">:</span> request_secret
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }<span style="color: rgb(102, 102, 102);">,</span> onAccessToken);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// 设置授权命令</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>function</b></span> authorize() {
<span class="lineno special"></span> <span style="color: rgb(96, 160, 176);"><i>// 清除已存的 access token</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'access_key'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'access_secret'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'user_id'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span class="lineno special"></span> <span style="color: rgb(96, 160, 176);"><i>// 获取 request token</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> service.getRequestToken(onRequestToken);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <b>GM_registerMenuCommand(<span style="color: rgb(64, 112, 160);">"授权豆瓣帐户(高亮友邻脚本)"</span><span style="color: rgb(102, 102, 102);">,</span> authorize);</b>
<span style="color: rgb(96, 160, 176);"><i>// 处理获取 request token 响应</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>function</b></span> onRequestToken(token) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (token.key) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (confirm(<span style="color: rgb(64, 112, 160);">"现在将转入授权页面,请选择同意授权。授权成功后,将返回当前页面"</span>)) {
<span class="lineno special"></span> <span style="color: rgb(96, 160, 176);"><i>// 保存 request token</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'request_key'</span><span style="color: rgb(102, 102, 102);">,</span> token.key);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'request_secret'</span><span style="color: rgb(102, 102, 102);">,</span> token.secret);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 重定向到授权页面</i></span>
<span class="lineno special"></span> setTimeout(<span style="color: rgb(0, 112, 32);"><b>function</b></span>() {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <b>location.href <span style="color: rgb(102, 102, 102);">=</span> service.getAuthorizationUrl(token<span style="color: rgb(102, 102, 102);">,</span> location.href <span style="color: rgb(102, 102, 102);">+</span> <span style="color: rgb(64, 112, 160);">'#friend_marker'</span>);</b>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }<span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 160, 112);">500</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span> } <span style="color: rgb(0, 112, 32);"><b>else</b></span> {
<span class="lineno special"></span> alert(<span style="color: rgb(64, 112, 160);">'授权失败,请稍后再试。'</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// 处理获取 access token 响应</i></span>
<span class="lineno special"></span><span style="color: rgb(0, 112, 32);"><b>function</b></span> onAccessToken(token<span style="color: rgb(102, 102, 102);">,</span> user_id) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (token.key) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 保存 access token 和用户id</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'access_key'</span><span style="color: rgb(102, 102, 102);">,</span> token.key);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'access_secret'</span><span style="color: rgb(102, 102, 102);">,</span> token.secret);
<span class="lineno special"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'user_id'</span><span style="color: rgb(102, 102, 102);">,</span> user_id);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 清除 request token</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'request_key'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'request_secret'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span class="lineno special"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 登录 service</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> service.login(token);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> } <span style="color: rgb(0, 112, 32);"><b>else</b></span> {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> alert(<span style="color: rgb(64, 112, 160);">'授权失败,请稍后再试。'</span>);
<span class="lineno special"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }</code></pre><br />
<b>更新友邻列表。</b>虽然文档中多是同步获取的方式,不过实际上却是用异步获取的时候比较多,所以 jQuery Douban 的 GreaseMonkey HTTP handler 并不支持同步获取数据(Gears 也是这样)。获取朋友和关注列表分别是用 service.user.friends 和 service.user.contacts,不过一次最多只能获取 50 个用户,所以这里用递归将获得的用户都加入一个数组,然后将数组中的用户名 join 后保存到 GreaseMonkey。<br />
<pre class="codes"><code><span style="color: rgb(96, 160, 176);"></span><span style="color: rgb(96, 160, 176);"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i></i></span><span style="color: rgb(102, 102, 102);"></span><span class="lineno special"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"></span><span style="color: rgb(102, 102, 102);"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span class="lineno special"></span><span style="color: rgb(96, 160, 176);"></span><span class="lineno special"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> friends <span style="color: rgb(102, 102, 102);">=</span> GM_getValue(<span style="color: rgb(64, 112, 160);">'friends'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> contacts <span style="color: rgb(102, 102, 102);">=</span> GM_getValue(<span style="color: rgb(64, 112, 160);">'contacts'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">''</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i></i></span><span class="lineno special"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"></span><span style="color: rgb(96, 160, 176);" class="lineno"></span><span class="lineno special"></span><span style="color: rgb(96, 160, 176);"><i>// 更新朋友列表</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>function</b></span> get_friends(offset) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> offset <span style="color: rgb(102, 102, 102);">=</span> offset <span style="color: rgb(102, 102, 102);">||</span> <span style="color: rgb(64, 160, 112);">0</span><span style="color: rgb(102, 102, 102);">;</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 新建临时列表</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (offset <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 160, 112);">0</span>) <span style="color: rgb(0, 112, 32);">window</span>._friends <span style="color: rgb(102, 102, 102);">=</span> [];
<span class="lineno special"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <b>service.user.friends(user_id<span style="color: rgb(102, 102, 102);">,</span> offset<span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 160, 112);">50</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(0, 112, 32);">function</span>(users) {</b>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 如果没有取到所有的朋友,则继续抓取</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (users.total <span style="color: rgb(102, 102, 102);">&&</span> (offset <span style="color: rgb(102, 102, 102);">+</span> <span style="color: rgb(64, 160, 112);">50</span> <span style="color: rgb(102, 102, 102);"><</span> users.total)) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> get_friends(offset <span style="color: rgb(102, 102, 102);">+</span> <span style="color: rgb(64, 160, 112);">50</span>);
<span class="lineno special"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (users.entries) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 将获得的朋友的id,加入临时列表</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> $.each(users.entries<span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(0, 112, 32);"><b>function</b></span>() {
<span class="lineno special"></span> <span style="color: rgb(0, 112, 32);">window</span>._friends.push(<span style="color: rgb(0, 112, 32);"><b>this</b></span>.userName);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> });
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 保存朋友列表</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> GM_setValue(<span style="color: rgb(64, 112, 160);">'friends'</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 112, 160);">'|'</span> <span style="color: rgb(102, 102, 102);">+</span> <span style="color: rgb(0, 112, 32);">window</span>._friends.join(<span style="color: rgb(64, 112, 160);">'|'</span>) <span style="color: rgb(102, 102, 102);">+</span> <span style="color: rgb(64, 112, 160);">'|'</span>);
<span class="lineno special"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <b>});</b>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }<span style="color: rgb(96, 160, 176);"><i></i></span>
<span style="color: rgb(96, 160, 176);"><i>// 更新关注列表</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>function</b></span> get_contacts(offset) { ... }</code></pre><br />
上面这些代码就是友邻高亮脚本中用到 jQuery Douban 的部分了。不是很多,但也包括了初始化 service,OAuth 授权认证和获取豆瓣 API 这些关键的部分了。如果正想动手写一个 GreaseMonkey 脚本的话,希望会有帮助。如果有任何建议,问题或BUG,欢迎留言 FIX ME :-)<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-6216476679276940335?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com5tag:blogger.com,1999:blog-7286295415331143255.post-40722788076360667632008-11-28T17:10:00.008+08:002009-01-09T12:07:09.341+08:00jQuery Douban - 简化豆瓣 API 开发的 jQuery 插件<h3>什么是 jQuery Douban</h3>
<a title="jQuery Douban" href="http://jquery-douban.appspot.com/" id="bax7">jQuery Douban</a> 是一个简化豆瓣 API 调用和 GData JSON 分析的 jQuery 插件,支持 OAuth 授权认证和各种豆瓣数据读写操作的功能。目前支持的API有用户、条目、收藏、评论、广播、日记、推荐和标签 API,部分支持同城活动的 API。这里有一个简单的<a href="http://jquery-douban.appspot.com/example/book_list/">读取用户收藏的 Demo</a>。<br />
<br />
<h3>什么时候使用 jQuery Douban</h3>
<ul>
<li>你正好在用 jQuery</li>
<li>你正好想从客户端调用豆瓣 API,而不是从服务器</li>
<li>或者你正在为豆瓣编写 GreaseMonkey 脚本</li>
</ul>
<br />
<h3>为什么不直接使用豆瓣API</h3>
<ul>
<li>更喜欢 jQuery 风格的代码</li>
<li>可以用有跨域超能力 Javascript 库,比如 Gears</li>
<li>看到 {"$t":"9787543632608","@name":"isbn13"} 风格的JSON 会头晕</li>
</ul>
<br />
<h3>怎样使用 jQuery Douban</h3>
示范添加一条广播先。 <br />
jQuery Douban 依赖于 jquery,oauth 和 sha1 三个 js 库,所以先要导入这三个 js 文件。<br />
<pre class="codes"><code><span style="color: rgb(6, 40, 115);"><b><script </b></span><span style="color: rgb(64, 112, 160);">type=</span><span style="color: rgb(64, 112, 160);">"text/javascript"</span> <span style="color: rgb(64, 112, 160);">src=</span><span style="color: rgb(64, 112, 160);">"/scripts/jquery.js"</span> <span style="color: rgb(6, 40, 115);"><b>></script></b></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(6, 40, 115);"><b><script </b></span><span style="color: rgb(64, 112, 160);">type=</span><span style="color: rgb(64, 112, 160);">"text/javascript"</span> <span style="color: rgb(64, 112, 160);">src=</span><span style="color: rgb(64, 112, 160);">"/scripts/sha1.js"</span> <span style="color: rgb(6, 40, 115);"><b>></script></b></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(6, 40, 115);"><b><script </b></span><span style="color: rgb(64, 112, 160);">type=</span><span style="color: rgb(64, 112, 160);">"text/javascript"</span> <span style="color: rgb(64, 112, 160);">src=</span><span style="color: rgb(64, 112, 160);">"/scripts/oauth.js"</span> <span style="color: rgb(6, 40, 115);"><b>></script></b></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(6, 40, 115);"><b><script </b></span><span style="color: rgb(64, 112, 160);">type=</span><span style="color: rgb(64, 112, 160);">"text/javascript"</span> <span style="color: rgb(64, 112, 160);">src=</span><span style="color: rgb(64, 112, 160);">"/scripts/jquery.douban.js"</span> <span style="color: rgb(6, 40, 115);"><b>></script></b></span>
</code></pre>
然后可以添加广播了<br />
<pre class="codes"><code><span style="color: rgb(96, 160, 176);"><i>// 创建豆瓣服务实例</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> service <span style="color: rgb(102, 102, 102);">=</span> $.douban({ key<span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(64, 112, 160);">"apiKey"</span><span style="color: rgb(102, 102, 102);">,</span> secret<span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(64, 112, 160);">"apiSecret"</span> });
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// 登录豆瓣帐户</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> login <span style="color: rgb(102, 102, 102);">=</span> service.login({ key<span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(64, 112, 160);">"accessKey"</span><span style="color: rgb(102, 102, 102);">,</span> secret<span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(64, 112, 160);">"accessSecret"</span>});
<span class="lineno special"></span><span style="color: rgb(0, 112, 32);"><b>if</b></span> (login) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i> </i><i> // 添加一条广播</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>var</b></span> miniblog <span style="color: rgb(102, 102, 102);">=</span> service.miniblog.add({ content: <span style="color: rgb(64, 112, 160);">"添加一条广播"</span> });
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 弹出“添加成功:添加一条广播”</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> alert(<span style="color: rgb(64, 112, 160);">"添加成功:"</span> <span style="color: rgb(102, 102, 102);">+</span> miniblog.content);
<span class="lineno special"></span> <span style="color: rgb(96, 160, 176);"><i>// 删除一条广播</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> service.miniblog.remove(miniblog);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }
</code></pre><br />
<br />
<h3>哪里可以得到 jQuery Douban</h3>
jQuery Douban 的主页是在 Google Code 上,Homepage: <a href="http://code.google.com/p/jquery-douban/">http://code.google.com/p/jquery-douban/</a>。
但代码用 <a href="http://github.com/">GitHub</a> 来管理,Repo URL: <a title="http://github.com/wuyuntao/jquery-douban/tree/master" href="http://github.com/wuyuntao/jquery-douban/tree/master" id="jo2w">http://github.com/wuyuntao/jquery-douban/tree/master</a>。可以用 Git 检出代码 <pre class="commands"><code>$ git clone git://github.com/wuyuntao/jquery-douban.git</code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-4072278807636066763?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com5tag:blogger.com,1999:blog-7286295415331143255.post-86818612252443747212008-11-27T00:23:00.007+08:002008-11-27T09:00:39.949+08:00有备无患:用 Google Gears 跨域调用豆瓣 API有两种可以实现 Gears 的跨域(Cross-Origin)访问的办法。第一种相对安全,但是要在豆瓣的服务器上安置一个 <a title="cross-origin worker" href="http://code.google.com/apis/gears/gears_faq.html#crossOriginWorker" rel="nofollow">cross-origin worker</a>。<br>
<br>
<blockquote><p>Cross-origin worker 可以由 <a title="WorkerPool API" href="http://code.google.com/apis/gears/api_workerpool.html" id="ri.g">WorkerPool API</a> 创建。WorkerPool 类似于线程池的概念,它让 javascript 从页面上解放出来,可以在后台独立运行,父子 workers 之间也可以互相通信,而且不同服务器上的 workers(也就是 Cross-origin workers)也可以互相传递数据,这样就实现了一个安全的浏览器跨域访问方式。<br></p>
</blockquote>
<br>
当然,我们不可能在豆瓣的服务器上动手脚。所以,我们只能在迂回中前进,第二种方法就是自建一个跨域代理(cross-origin proxy),并在上面安置 worker 供 Gears 调用,尽管这有被 XSS 攻击的潜在风险。<br>
<br>
举例来说,我们要从 http://foo.com/ 上调用豆瓣 API,发送一条广播到 http://api.douban.com/miniblog/saying。这通常受到 Same-Origin Policy 的限制。所以,我们通过父 worker 向安置在跨域代理上的 cross-origin worker 发送指令,让它去访问 http://bar.com/proxy?url=http://api.douban.com/people/wyt。成功后,从向本地的 worker 报告工作成果。<br>
<br>
<p>我先用 <a title="App Engine" href="http://code.google.com/appengine/" rel="nofollow">App Engine</a> 写了一个<a title="简单的跨域代理" href="http://github.com/wuyuntao/jquery-douban/tree/master/apps%2Fproxy.py" id="w.ih">简单的跨域代理</a>,仅做参考。然后在 app.yaml 中设置好 worker.js 的访问路径。<br>
</p>
<pre class="codes"><code>- url: /worker.js
<span style="color: rgb(96, 160, 176);" class="lineno"></span> static_files: media/scripts/worker.js
<span style="color: rgb(96, 160, 176);" class="lineno"></span> upload: media/scripts/worker.js
</code></pre>
<br>
接着编写 worker.js。以前 Gears 只可以在 workers 之间传递文本,现在已经可以传递函数(function)以外的其他对象,比如 Array 和 Object。所以我们可以向 cross-origin worker 传递一个包含了 HTTP request 所需参数的字典(dict),比如说<br>
<br>
<pre class="codes"><code><span style="color: rgb(0, 112, 32);"><b>var</b></span> options = {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(64, 112, 160);">'url'</span>: <span style="color: rgb(64, 112, 160);">'http://bar.com/proxy?url=http://api.douban.com/miniblogs/saying'</span>,
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(64, 112, 160);">'type'</span>: <span style="color: rgb(64, 112, 160);">'POST'</span>,
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(64, 112, 160);">'headers'</span>: {
<span class="lineno special"></span> <span style="color: rgb(64, 112, 160);">'Authorization'</span>: <span style="color: rgb(64, 112, 160);">'OAuth realm="", ...'</span>,
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(64, 112, 160);">'Content-Type'</span>: <span style="color: rgb(64, 112, 160);">'application/atom+xml'</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> },
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(64, 112, 160);">'data'</span>: <span style="color: rgb(64, 112, 160);">'<?xml version="1.0" encoding="UTF-8"?><entry ...'</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> };
</code></pre>
<br>
worker.js 中通过 Gears 的 HttpRequest API,向代理发送请求,并将代理返回的结果发送给本地的父 worker 供浏览器调用。这里有一个简单的实现 <a title="gears_worker.js" href="http://github.com/wuyuntao/jquery-douban/tree/master/src%2Fgears_worker.js" id="hub5">gears_worker.js</a>,只做了返回成功后的处理。<br>
<br>
<pre class="codes"><code><span style="color: rgb(96, 160, 176);"><i>// 新建 Worker</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> wp <span style="color: rgb(102, 102, 102);">=</span> google.gears.workerPool<span style="color: rgb(102, 102, 102);">;</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// 允许 Worker 跨域脚本访问</i></span>
<span class="lineno special"></span> wp.allowCrossOrigin();
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> wp.onmessage <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32);"><b>function</b></span>(a<span style="color: rgb(102, 102, 102);">,</span> b<span style="color: rgb(102, 102, 102);">,</span> message) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>var</b></span> s <span style="color: rgb(102, 102, 102);">=</span> message.body<span style="color: rgb(102, 102, 102);">;</span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span class="lineno special"></span> <span style="color: rgb(96, 160, 176);"><i>// 新建 HTTP Request</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>var</b></span> request <span style="color: rgb(102, 102, 102);">=</span> google.gears.factory.create(<span style="color: rgb(64, 112, 160);">'beta.httprequest'</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> request.onreadystatechange <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32);"><b>function</b></span>() {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 将响应文本返回给父 Worker</i></span>
<span class="lineno special"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (request.readyState <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 160, 112);">4</span>) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> wp.sendMessage(request.responseText<span style="color: rgb(102, 102, 102);">,</span> message.sender);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> }
<span style="color: rgb(96, 160, 176);" class="lineno"></span> };
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span class="lineno special"></span> <span style="color: rgb(96, 160, 176);"><i>// 打开 URL</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> request.open(s.type<span style="color: rgb(102, 102, 102);">,</span> s.url);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 设置请求 Headers</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>for</b></span> (<span style="color: rgb(0, 112, 32);"><b>var</b></span> name <span style="color: rgb(0, 112, 32);"><b>in</b></span> s.headers)
<span class="lineno special"></span> request.setRequestHeader(name<span style="color: rgb(102, 102, 102);">,</span> s.headers[name]);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(96, 160, 176);"><i>// 发送请求</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>if</b></span> (s.data) request.send(s.data);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>else</b></span> request.send()
<span class="lineno special"></span> };
</code></pre>
全都部署完成之后,我们就可以在 http://foo.com/ 上通过运行 javascript 调用豆瓣 API 了。下面是一个示例<br>
<pre class="codes"><code><span style="color: rgb(96, 160, 176);"><i>// 新建 WorkerPool</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> workerPool <span style="color: rgb(102, 102, 102);">=</span> google.gears.factory.create(<span style="color: rgb(64, 112, 160);">'beta.workerpool'</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(96, 160, 176);"><i>// 处理响应</i></span>
<span class="lineno special"></span> workerPool.onmessage <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32);"><b>function</b></span>(a<span style="color: rgb(102, 102, 102);">,</span> b<span style="color: rgb(102, 102, 102);">,</span> message) {
<span style="color: rgb(96, 160, 176);" class="lineno"></span> <span style="color: rgb(0, 112, 32);"><b>var</b></span> data <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32);">eval</span>(<span style="color: rgb(64, 112, 160);">"("</span> <span style="color: rgb(102, 102, 102);">+</span> message.body <span style="color: rgb(102, 102, 102);">+</span> <span style="color: rgb(64, 112, 160);">")"</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> alert(data);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> };
<span style="color: rgb(96, 160, 176);" class="lineno"></span>
<span class="lineno special"></span><span style="color: rgb(96, 160, 176);"><i>// 创建子 worker 并将预先定义好的 options 字典发送过去</i></span>
<span style="color: rgb(96, 160, 176);" class="lineno"></span><span style="color: rgb(0, 112, 32);"><b>var</b></span> childWorkerId <span style="color: rgb(102, 102, 102);">=</span> workerPool.createWorkerFromUrl(<span style="color: rgb(64, 112, 160);">'http://bar.com/worker.js'</span>);
<span style="color: rgb(96, 160, 176);" class="lineno"></span> workerPool.sendMessage(options<span style="color: rgb(102, 102, 102);">,</span> childWorkerId);
</code></pre>
<br>这样部署好之后,就可以用 Google Gears 跨域调用豆瓣 API 了。但还是有一个问题,因为所有的请求都集中到跨域代理,如果没有申请放宽限制的话,很快就会被 403 吧。果然,最好的办法还是豆瓣官方支持 Gears 的远程调用吗?<br>
<br>
<h4>参考页面</h4>
<ul><li><a title="Cross Domain messaging with gears" href="http://gearsblog.blogspot.com/2008/03/cross-domain-messaging-with-gears.html" id="maup">Cross Domain messaging with gears</a></li><li><a title="Crossdomain proxy on Google App Engine" href="http://w00kie.com/2008/06/18/crossdomain-proxy-on-google-app-engine/" id="ju6v">Crossdomain proxy on Google App Engine</a></li><li><a title="WorkerPool API" href="http://code.google.com/apis/gears/api_workerpool.html#typical_use" id="kb26">WorkerPool API</a></li><li><a title="What is a cross-origin worker? What is it useful for? How do I use it?" href="http://code.google.com/apis/gears/gears_faq.html#crossOriginWorker" id="h7xh">What is a cross-origin worker? What is it useful for? How do I use it?</a></li></ul>
<br />
<br />
<br>
P.S. Blogger.com 莫名的被 GFWed 了。为什么 Blogger API 可以通过 https:// 访问,可 Blogger 自己却不可以像 GMail 和 GDocs 一样通过 https:// 呢,现在就只能靠 GDocs 发布了。。。 <br><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-8681861225244374721?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com4tag:blogger.com,1999:blog-7286295415331143255.post-20110151866622159242008-10-14T20:53:00.002+08:002008-10-14T21:47:52.237+08:00悠言悠闲:linux 下玩/wine 東方地霊殿<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/_sKun2mxXeAk/SPSYxHEixdI/AAAAAAAAAtU/O3691Fz5D0Q/s1600-h/touhou-chireiden-start.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img src="http://1.bp.blogspot.com/_sKun2mxXeAk/SPSYxHEixdI/AAAAAAAAAtU/_jqIquXwUDY/s400-R/touhou-chireiden-start.png" /></a></div>
<br />
<ol>
<li><a rel="nofollow" href="http://www16.big.or.jp/~zun/html/th11top.html">东方地灵殿</a>(東方地霊殿 〜 Subterranean Animism.)是日本同人社团<a rel="nofollow" href="http://zh.wikipedia.org/wiki/%E4%B8%8A%E6%B5%B7%E3%82%A2%E3%83%AA%E3%82%B9%E5%B9%BB%E6%A8%82%E5%9B%A3">上海爱莉丝幻乐团</a>制作的一款弹幕系射击游戏</li>
<li>wine 的版本为 1.1.5</li>
<li>无法启动 th11.exe,需要<a rel="nofollow" href="http://www.dll-files.com/dllindex/dll-files.shtml?d3dx9_36">下载</a>,或者从 Windows 下面复制一个 d3dx9_36.dll 文件,
<pre class="commands"><code>$ cp d3dx9_36.dll ~/.wine/drive_c/windows/system32/</code></pre>
</li>
<li>运行地灵殿的窗口模式的时候,分辨率似乎被限制在了640x480。<a rel="nofollow" href="http://forum.ubuntu.org.cn/viewtopic.php?f=121&t=144249">没有找到解决办法</a></li>
<li>如果使用虚拟桌面(Virtual Desktop) + 地灵殿的全屏模式,感觉会有点卡,帧速降到50fps左右</li>
<li>如果使用虚拟桌面 + 窗口模式,游戏界面黑屏有声音</li>
<li>如果取消虚拟桌面 + 窗口模式,感觉上速度和 Windows 下面差不多,不过限速 60fps,实际差多少也不知道就对了</li>
<li>运行正确的话,wine 应该只会输出下面这几行消息,
<pre class="commands"><code>$ wine th11.exe
fixme:win:EnumDisplayDevicesW ((null),0,0x32f8d4,0x00000000), stub!
fixme:d3d:WineD3D_ChoosePixelFormat Add OpenGL context recreation support to SetDepthStencilSurface
fixme:d3d:WineD3D_ChoosePixelFormat Add OpenGL context recreation support to SetDepthStencilSurface
fixme:win:WINNLSEnableIME hUnknown1 (nil) bUnknown2 1: stub!
</code></pre>
</li>
<li>有一篇更加详细的说明,<a href="http://d.hatena.ne.jp/lugia/20080718/1216352077">東方地霊殿に関するメモ</a>(日语)</li>
</ol>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/_sKun2mxXeAk/SPSY3WJkAWI/AAAAAAAAAtc/VlB01fVRxX4/s1600-h/touhou-chireiden.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img src="http://4.bp.blogspot.com/_sKun2mxXeAk/SPSY3WJkAWI/AAAAAAAAAtc/vN0WTvw5vB0/s400-R/touhou-chireiden.png" /></a></div><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-2011015186662215924?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com7tag:blogger.com,1999:blog-7286295415331143255.post-44313735428802909262008-09-20T15:55:00.013+08:002009-01-23T16:38:33.816+08:00有备无患:豆瓣离线 - 利用 Google Gears 实现离线浏览豆瓣的 GreaseMonkey 脚本<b><a href="http://blog.luliban.com/2008/09/douban-offline-greasemonkey-google.html">豆瓣离线</a>(Douban Offline)是一个可以让用户可以离线浏览豆瓣的 <a href="https://addons.mozilla.org/firefox/addon/748" id="i5o1" rel="nofollow" title="GreaseMonkey">GreaseMonkey</a> 脚本。</b><b>它可以在网络无法使用的情况下,浏览保存在本地的页面。它也可以用来收藏或备份豆瓣上的条目、小组和友邻等页面。</b><br />
<br />
豆瓣离线使用 <a href="http://code.google.com/apis/gears/" id="tqm4" rel="nofollow" title="Google Gears API">Google Gears API</a> 来实现离线浏览的功能。<a href="http://gears.google.com/" id="hzqn" rel="nofollow" title="Google Gears">Google Gears</a> 是一款 <a class="mw-redirect" href="http://zh.wikipedia.org/w/index.php?title=Google&variant=zh-cn" rel="nofollow" title="Google">Google</a> 开发的软件。Gears 通过 <a href="http://zh.wikipedia.org/w/index.php?title=SQLite&variant=zh-cn" rel="nofollow" title="SQLite">SQLite</a>
数据库让客户端能够把网页暂存起来,并通过内部服务器(Local server)把数据库中暂存的网页重现,从而让用户实现离线上网的功能。<a href="http://docs.google.com/" rel="nofollow" title="Google Docs">Google Docs</a> 和 <a href="http://reader.google.com/" rel="nofollow" title="Google Reader">Google Reader</a> 都支持用 Gears 将本地暂存的资料与网络做同步。<br />
<br />
豆瓣离线现在版本为 0.1,还有许多的不完善之处,欢迎大家的建议、意见和 Bug 报告:-)<br />
<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="http://3.bp.blogspot.com/_sKun2mxXeAk/SNSuTFxQpXI/AAAAAAAAAs0/AnbkR1tYWbQ/s1600-h/screen_shot_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/_sKun2mxXeAk/SNSuTFxQpXI/AAAAAAAAAs0/FL7UYli7t_U/s400-R/screen_shot_1.png" /></a></div>
<br />
<h3>
功能</h3>
<ul>
<li>保存豆瓣上的某个页面,以及相关的图片、CSS 和 Javascript 脚本</li>
<li>按分类浏览已缓存的页面,目前的分类有:条目,小组和用户</li>
</ul>
<br />
<h3>
使用方法</h3>
<ul>
<li>如果希望保存当前页面,我们先找到导航栏“退出”附近的“离线”。点击“离线”,待离线状态栏展开完毕后,点击“离线”下面的“收藏此页面”。注意,点击之后不要马上关闭窗口,因为相关的文件可能还在下载中。另外我们也可以打开 FireBug 的终端(Console)来看看究竟那些文件被下载下来了。<br /></li>
<li>如果希望浏览已缓存的页面,<del>点击 Firefox -> File(文件) -> Work Offline(离线工作)来强制 Firefox 进入离线工作的状态。</del>点击“离线” -> “离线浏览”,然后在缓存页面目录,我们可以点击其中的链接访问已被缓存的页面。<br /></li>
<li>如果希望恢复在线浏览,在同样的地方点击“在线浏览”。</li>
<li>第一次使用豆瓣离线脚本时,Gears 会弹出两个安全警告对话框,都选择 Allow(允许)就可以了。<br /></li>
</ul>
<br />
<h3>
安装需求</h3>
<ol>
<li>Firefox (>=3.0.0): <a href="http://www.mozilla.com/en-US/firefox/" rel="nofollow">http://www.mozilla.com/en-US/firefox/</a> <br /></li>
<li>
GreaseMonkey (>=0.8):<a href="https://addons.mozilla.org/en-US/firefox/addon/748" id="ctp4" rel="nofollow" title="https://addons.mozilla.org/en-US/firefox/addon/748">https://addons.mozilla.org/en-US/firefox/addon/748</a> <br /></li>
<li>Google Gears (>=0.4):<a href="http://gears.google.com/" id="r.w1" rel="nofollow" title="http://gears.google.com/">http://gears.google.com/</a> <br /></li>
</ol>
<br />
<h3>
下载</h3>
<br />
大家可以从我的 GitHub Repo 中检出最新的脚本试用<br />
<pre class="commands"><code>git clone git://github.com/wuyuntao/douban-offline.git</code></pre>
<br />
或者从 UserScripts 网站上下载<br />
<a href="http://userscripts.org/scripts/show/34040"><img alt="下载" class="no-border" src="http://luliban.com/images/download90.gif" style="border: medium none;" /></a><br />
<br />
<br />
<h4>2008-09-23 UPDATE 0.2:</h4>
豆瓣离线脚本更新为 0.2,有下面这些改动:<br />
<ol>
<li>可以手动切换上线/离线浏览</li>
<li>可以更新缓存页</li>
<li>新增两个分类,日记和相册</li>
<li>选中分类有高亮<br /></li>
</ol>
<br /><br />
<h4>2008-10-15 下载链接更新:</h4>
祸不单行。<a rel="nofollow" href="http://github.com/">github</a> 前几天数据库出了问题,到现在还没有恢复;
<a href="http://userscripts.org/">userscripts.org</a> 貌似被 GFW 给封了;今天 GAE 提供的四个 IP 中的又一个阵亡了。。。不管怎样,上面两个下载的链接都不管用了,现在提供一个本地的下载链接。<br />
<a href="http://github.com/wuyuntao/douban-offline/raw/master/douban_offline.user.js"><img alt="下载" class="no-border" src="http://blog.luliban.com/images/download90.gif" style="border: medium none;" /></a>
<br />
<br />
<h4>2008-10-22 UPDATE 0.4:</h4>
豆瓣离线脚本更新为 0.4,有下面这些改动:<br />
<ol>
<li>增加了翻页的功能</li>
<li>增加了评论和豆邮两个标签 </li>
<li>增加了一条 <a href="http://www.douban.com/note/17803282/">Douban Helper</a> 的控制台命令:write(wyt:Douban Helper 请<a href="http://userscripts.org/scripts/review/32686/">安装最新版本</a>)</li>
<li>改正了相册日记等页面离线按钮位置不正确的bug</li>
</ol><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-4431373542880290926?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com22tag:blogger.com,1999:blog-7286295415331143255.post-47674192339843630902008-09-14T17:56:00.006+08:002009-01-26T11:28:00.058+08:00有备无患:Google Reader 风的豆瓣广播 widget 更新今天为 <a href="http://blog.luliban.com/2008/08/google-reader-style-douban-miniblog.html">Google Reader 风的豆瓣广播 widget</a> 新增了三个选项:手动设置宽度 width,隐藏标题 hidetitle 和隐藏主页链接 hidefooter。谢谢 <a href="http://cow.hkbloggers.org/">cow</a> 和 <a href="http://asiapan.cn/">asiapan</a> 的<a href="http://blog.luliban.com/2008/08/google-reader-style-douban-miniblog.html#comments">建议</a>先。<br />
<br />
使用方法和<a href="http://blog.luliban.com/2008/08/google-reader-style-douban-miniblog.html">以前的说明</a>一样,只需要将下面这段代码插入到你的 blog 模板中,并替换其中的参数。
<br />
<br />
<pre class="codes"><code><script type="text/javascript" src="http://blog.luliban.com/scripts/miniblog.js?username=wyt&amp;maxresults=16&amp;style=blue"></script>
</code></pre>
<br />
<h4>
参数列表如下</h4>
<ul>
<li><b>username: 用户名,或者用户 id。(必填)</b></li>
<li>maxresults: 输出广播条目的数量。最大值为50,默认为10。</li>
<li>style: widget 的配色样式,默认为绿色。其他样式包括 black(黑)、blue(蓝)、gray(灰)、green(绿)、khaki(卡其色)、pink(粉红)、slate(蓝灰) 和 none(无)。其中,none 为不带任何样式的原始 HTML,适合希望自定义广播风格的 bloggers。</li>
<li>width: 手动设定 widget 的宽度,输入应为宽度的像素值,如 width=300</code>。默认为自适应宽度(推荐)。</li>
<li>hidetitle: 如果设 hidetitle=true,将隐藏顶部标题。默认为 false。</li>
<li>hidefooter: 如果设 hidefooter=true,将隐藏底部豆瓣主页的链接。默认为 false。</li>
</ul>
<br />
隐藏标题和主页的效果如下<br />
<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="http://3.bp.blogspot.com/_sKun2mxXeAk/SMzo7rziAaI/AAAAAAAAAsU/1kU68xOEFGw/s1600-h/douban-miniblog-widget-screenshot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img src="http://lh5.ggpht.com/wuyuntao.1122/SMzo7rziAaI/AAAAAAAAAsU/_yINjkTIxiI/s800/douban-miniblog-widget-screenshot.png" /></a></div><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-4767419233984363090?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com5tag:blogger.com,1999:blog-7286295415331143255.post-64499940746016583562008-09-12T19:26:00.005+08:002008-09-12T20:10:38.547+08:00有备无患:升级到 Git 1.6.01 之后无法启动 git-daemon 的解决办法<a rel="nofollow" href="http://git.or.cz/">git</a> 升级到 1.6 之后,发现 git-daemon 不能启动,自然也不能作 clone 或 push 之类的操作。原因是原来的 /usr/bin/git-daemon 搬到了 /usr/libexec/git-core 目录,但是 Gentoo 的启动脚本没有随之更新。不过有人已经提交了<a rel="nofollow" href="https://bugs.gentoo.org/236685">这个 bug</a>。<br />
<br />
自己也可动手,将启动脚本 /etc/init.d/git-daemon 中的 start 函数修改如下。<br />
<pre class="codes"><code>start() {
ebegin "Starting git-daemon"
start-stop-daemon --start --quiet --background \
--exec /usr/libexec/git-core/git-daemon -- ${GITDAEMON_OPTS}
eend $?
}</code></pre>
<br />
另外,我用来架设 Git 版本库的 <a rel="nofollow" href="http://scie.nti.st/2007/11/14/hosting-git-repositories-the-easy-and-secure-way">gitosis</a> 也和 git 1.6 有一点兼容问题,原因是 /usr/bin/git-shell 也搬到了 /usr/libexec/git-core 目录。好在<a rel="nofollow" href="http://eagain.net/gitweb/?p=gitosis.git;a=commitdiff;h=73a032520493f6b4186185d4826d12edb5614135;hp=7e407d1013e2610401792302325c04c40c57a376">已经有 patch 推出</a>,只需要修改 /usr/lib/python2.5/site-packages/gitosis/serve.py,将其中调用 <b>git-shell</b> 的指令,替换成调用 <b>git shell</b> 即可。<br />
<pre class="codes"><code>--- a/gitosis/serve.py
+++ b/gitosis/serve.py
@@ -201,6 +201,6 @@ class Main(app.App):
sys.exit(1)
main_log.debug('Serving %s', newcmd)
- os.execvp('git-shell', ['git-shell', '-c', newcmd])
+ os.execvp('git', ['git', 'shell', '-c', newcmd])
main_log.error('Cannot execute git-shell.')
sys.exit(1)
</code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-6449994074601658356?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-60616374713223259272008-09-10T12:23:00.009+08:002009-01-09T01:19:58.020+08:00南言北哲:Battier or Artest,谁将首发?<b>1,Battier or Artest,谁将首发?</b><br />
<br />
<b id="phx-" style="color: #cc0000;">Artest。</b>联盟最好的人盯人防守者之一,上赛季场均20.5分的进攻水准,以及38%的三分球命中率。加上曾在主教练 Rick Adelman 手底下打过一年球,对火箭现在这套动态进攻的体系了然在胸。如果在训练营和季前赛中一切顺利,他极有可能会取代 Shane Battier 成为火箭的正印前锋。<br />
<br />
有同学认为,Artest 应妨 Luis Scola 之例,先从替补打起,走循序渐进的路线。可是你要知道,Scola 虽然在 FIBA 摸爬滚打了多年,但是他也需要时间来适应 NBA,另一方面主教练也需要时间来了解他的技术特点,所以 Scola 必须从替补打起。Artest 却是已经在联盟混迹了整九年,别说 Adelman,就是联盟其余的29位主教练也同样对他知根知底。<br />
<br />
另外,虽然 Artest 曾经表过态,说愿意从替补打起。可是,以史为鉴,Artest 不是甘居替补之人——05年他炮轰 Jermaine O'Neal 出手权多和进攻效率低,被交易到 Sacramento 之后,又与 Mike Bibby 争当“国王”。(wyt: 最终都以阿泰“抢班夺权”失败而告终。)<br />
<br />
<b id="ts0x0">2,如果 Battier 做替补,能不能胜任第六人的角色?</b><br />
<br />
<b id="nzmp" style="color: #cc0000;"><span id="nzmp0">像 Battier 这样的防守型球员也一样可以胜任第六人,这</span></b><b id="tt-6" style="color: #cc0000;"><span id="tt-60">甚至</span></b><b id="tt-61"><span id="tt-62" style="color: red;"><span style="color: #cc0000;">还有可能成为一股潮流。</span><br id="fu3w" />
<br id="fu3w0" />
</span></b>联盟的大多数第六人都是长于进攻的球员,比如被交易回国王的 Bobby Jackson,湖人签下肥约的 Sasha Vujacic,小牛老将 Jerry Stackhouse,还有掘金的 J.R. Smith 等,他们的主要作用是在主力下场的时候带领其他替补球员,提供持续的进攻力,并为主力争取更多的休息时间。(wyt:我不喜欢把 Manu Ginobili 和 Jason Terry 归类到第六人。如果非要说,我更愿意称他们为“伪第六人”,或者,“联盟第六人奖项欺诈师”)<br />
<br />
但是对于拥有所谓的“三巨头”的球队,这个角色常常是由“三巨头”的其中之一所担任,比如凯尔特人的 Ray Allen 和马刺的 Manu Ginobili。因而这些球队对第六人在进攻端的要求也有所不同。以 Boston 为例,上赛季第六人就是场均只有7.4分进帐的防守悍将 James Posey。总决赛中出色的表现,也让黄蜂弃用原来的第六人 Jannero Pargo,花重金将 Posey 挖来作为下赛季他们的第六人。另外,湖人下赛季也有可能让 Odom 出任第六人。<br />
<br />
Battier 之于火箭,就像 Posey 之于 Boston 和 New Olean。而且 Battier 有更出色的协防能力和不错的背身技巧,可以想见新赛季的第四节,我们经常会看到 Battier 和 Artest 一起打完最后一分钟。<br />
<br />
<b id="ts0x1">3,</b><b id="ts0x2">如果 </b><b id="ts0x3">Artest 做先发,出手次数会不会不够?</b><br />
<br />
<b id="uwyg" style="color: #cc0000;"><span id="uwyg0">这很可能是杞人忧天。</span></b><br />
<br />
上赛季,有三支球队的核心球员被称作“三巨头”,分别是凯尔特人的 Garnett、Pierce 和 Allen,马刺的 Duncan、Parker 和 Ginobili,以及湖人的 Kobe、Odom 和 Gasol。我们首先来看看他们的<a href="http://spreadsheets.google.com/pub?key=p0fkEiAjyzAtC67rSFQT_dg&gid=0" id="w-n9" target="_blank" title="出手分配情况">出手分配情况</a>,并和火箭作一下对比。<br />
<br />
<br />
表格中的 FGA 为出手次数 (Field Goal Attempts),FTA 为罚球次数 (Free Throw Attempts),TP 为球队节奏 (Team Pace),TSA 为每100次球权的真实出手次数 (True Shooting Attempts),所使用的统计公式为 TSA = (FGA + 0.44 * FTA) / TP * 100。要说明的,湖人的数据是“打劫” Gasol 以后的全明星之后的数据,而其他球队是整个赛季的数据。<br />
<br />
<div>
<a href="http://picasaweb.google.com/lh/photo/vPndGBboJXN_75727-oDig?authkey=QNJyVUfN53A&feat=embedwebsite"><img src="http://lh6.ggpht.com/_sKun2mxXeAk/SMdMeZ595GI/AAAAAAAAAl0/kbWCtgVuHs0/s400/big-three-true-shooting-attempts-stats.png" /></a>
</div>
<div>
<a href="http://picasaweb.google.com/lh/photo/D1-2dhPk15Y1dpY_pE1HPQ?authkey=QNJyVUfN53A&feat=embedwebsite"><img src="http://lh3.ggpht.com/_sKun2mxXeAk/SMdM_Mw_7nI/AAAAAAAAAl8/SEcWw_MO3zo/s400/big-three-true-shooting-attempts-chart.png" /></a>
</div>
<br />
从图表中,我们可以看出光是姚明,T-Mac 和 Battier 三个人的出手次数,就已经和其他几个三巨头基本持平。所以即使 Artest 取代 Battier 成为先发,只要姚明和 T-Mac 愿意牺牲自己的球权,火箭的出手次数对于 Artest 来说是绰绰有余的。<br />
<br />
另外,由于伤病的关系,姚明和 T-Mac 都有相当长一段时间单独带队,所以也有同学质疑,这段时间他们的出手数是不是突增的,会不会影响到最后的统计结果。我也捎带统计了一下他们同时带队的10月、11月和2月份,姚明的出手数是20.0次,而 T-Mac
的是 24.4次。相比整个赛季并没有太大的变化,这主要是因为无论是姚明还是
T-Mac 带队,他们都是以火箭这个团队来进攻,谁也没有像 Kobe 在 Bynum 和 Gasol
缺席的时候那样贸然增加自己的出手。<br /><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-6061637471322325927?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com2tag:blogger.com,1999:blog-7286295415331143255.post-60863815027037935042008-09-05T11:35:00.002+08:002008-09-05T13:28:49.208+08:00有备无患:用 Gaupol 生成文本字幕<a href="http://home.gna.org/gaupol/">Gaupol</a> 是一个用 Python 编写的文本字幕文件的编辑工具,支持包括.ssa、.ass、.srt和.sub在内的多种字幕文件格式,并提供文本校正的方法以及时间处理。其用户界面基于 pyGTK,注重对多文档和翻译的批量处理。最新版本为 <a rel="nofollow" href="http://download.gna.org/gaupol/0.13/gaupol-0.13.news">0.13</a>。<br />
<br />
下面的命令示范了利用 Gaupol 生成一个 SubRip 格式的字幕文件,以作备忘。<br />
<br />
<pre class="commands"><code>$ python
>>> from gaupol.files.subrip import SubRip
>>> from gaupol import NEWLINE
>>> srt = SubRip('/home/wyt/demo.srt', encoding='utf-8', newline=NEWLINE.UNIX)
>>> srt.write(starts=[u'00:00:5.000', u'00:00:15.000', u'00:00:25.000'], \
ends=[u'00:00:10.000', u'00:00:20.000', u'00:00:30.000'], \
texts=[u'字幕测试一', u'字幕测试二', u'字幕测试三'])
</code></pre>
<br />
生成的 .srt 字幕文件如下<br />
<br />
<pre class="codes"><code>1
00:00:5,000 --> 00:00:10,000
字幕测试一
2
00:00:15,000 --> 00:00:20,000
字幕测试二
3
00:00:25,000 --> 00:00:30,000
字幕测试三
</code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-6086381502703793504?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com0tag:blogger.com,1999:blog-7286295415331143255.post-92132491164085026002008-08-10T23:58:00.000+08:002008-08-11T09:12:08.989+08:00有备无患:truncatehanzi - 按字数截取中文的 Django template filterDjango 自带的一个模板过滤器(Template Filter)<a href="http://www.djangoproject.com/documentation/templates/#truncatewords" rel="nofollow" title="truncatewords">truncatewords</a>,可以按单词数截取字符串生成摘要。不过,truncatewords 只能用空格来分隔单词,对汉字就无能为力了。所以,我写了另一个模板过滤器 truncatehanzi,它通过 unicode 编码来区分西方字符和 CJK 汉字,对包括英语字母在内的西方字符以空格或其他符号来分隔并计算单词数,而对 CJK 汉字则是按字数计算。<br />
<br />
使用方法和 truncatewords 一样,<br />
<pre class="codes"><code>{{ value|truncatehanzi:6 }}</code></pre>
如果 value 为“截取English、Γρεεκ等字母语言和CJK汉字。”,输出“截取English、Γρεεκ等字...”。<br />
<br />
代码分为两个部分,截取汉字的脚本 truncate_hanzi.py 和生成过滤器的脚本 filters.py。<br />
<br />
<h4>truncate_hanzi.py</h4>
<pre style="font-family: "monospace","Consolas","Courier New","Bitstream Vera Sans Mono"; " class="codes"><code><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># -*- coding: UTF-8 -*-</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> import <span style="color: rgb(14, 132, 181); font-weight: bold;">re</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> from <span style="color: rgb(14, 132, 181); font-weight: bold;">django.utils.encoding</span> import force_unicode<br><span class="lineno special"></span> from <span style="color: rgb(14, 132, 181); font-weight: bold;">django.utils.functional</span> import allow_lazy<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">def</span> <span style="color: rgb(6, 40, 126);">truncate_hanzi</span>(s, num):<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> s <span style="color: rgb(102, 102, 102);">=</span> force_unicode(s)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> length <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32);">int</span>(num)<br><span class="lineno special"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> length <span style="color: rgb(102, 102, 102);"><=</span> <span style="color: rgb(64, 160, 112);">0</span>:<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(64, 112, 160);">u'...'</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># Set up regex for alphanumeric characters</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># \u00c0-\u02af: Latin</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># \u0370-\u1fff: Greek and alphabet characters in other language </span><br><span class="lineno special"></span> re_alnum <span style="color: rgb(102, 102, 102);">=</span> re<span style="color: rgb(102, 102, 102);">.</span>compile(<span style="color: rgb(64, 112, 160);">ur'[a-zA-Z0-9_\-\u00c0-\u02af\u0370-\u1fff]'</span>, re<span style="color: rgb(102, 102, 102);">.</span>U)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># Set up regex for hanzi</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># \u3040-\ufaff: CJK characters</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> re_hanzi <span style="color: rgb(102, 102, 102);">=</span> re<span style="color: rgb(102, 102, 102);">.</span>compile(<span style="color: rgb(64, 112, 160);">ur'[\u3040-\ufaff]'</span>, re<span style="color: rgb(102, 102, 102);">.</span>U)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> hanzi <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(64, 112, 160);">u''</span><br><span class="lineno special"></span> hanzi_len <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(64, 160, 112);">0</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> word_temp <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(64, 112, 160);">u''</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">for</span> char <span style="color: rgb(0, 112, 32); font-weight: bold;">in</span> s:<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># Check for alphabet characters</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> re_alnum<span style="color: rgb(102, 102, 102);">.</span>match(char):<br><span class="lineno special"></span> word_temp <span style="color: rgb(102, 102, 102);">+=</span> char<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">continue</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> word_temp:<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> hanzi <span style="color: rgb(102, 102, 102);">+=</span> word_temp<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> hanzi_len <span style="color: rgb(102, 102, 102);">+=</span> <span style="color: rgb(64, 160, 112);">1</span><br><span class="lineno special"></span> word_temp <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(64, 112, 160);">''</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># Check for length</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> hanzi_len <span style="color: rgb(102, 102, 102);">>=</span> length:<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">not</span> hanzi<span style="color: rgb(102, 102, 102);">.</span>endswith(<span style="color: rgb(64, 112, 160);">'...'</span>):<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> hanzi <span style="color: rgb(102, 102, 102);">+=</span> <span style="color: rgb(64, 112, 160);">'...'</span><br><span class="lineno special"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">break</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># Check for hanzi</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> re_hanzi<span style="color: rgb(102, 102, 102);">.</span>match(char):<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> hanzi_len <span style="color: rgb(102, 102, 102);">+=</span> <span style="color: rgb(64, 160, 112);">1</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> hanzi <span style="color: rgb(102, 102, 102);">+=</span> char<br><span class="lineno special"></span> hanzi <span style="color: rgb(102, 102, 102);">+=</span> word_temp<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> hanzi<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> truncate_hanzi <span style="color: rgb(102, 102, 102);">=</span> allow_lazy(truncate_hanzi, <span style="color: rgb(0, 112, 32);">unicode</span>)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">def</span> <span style="color: rgb(6, 40, 126);">demo</span>():<br><span class="lineno special"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">print</span> truncate_hanzi(<span style="color: rgb(64, 112, 160);">'截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。'</span>, <span style="color: rgb(64, 160, 112);">6</span>)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">print</span> truncate_hanzi(<span style="color: rgb(64, 112, 160);">'截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。'</span>, <span style="color: rgb(64, 160, 112);">11</span>)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">print</span> truncate_hanzi(<span style="color: rgb(64, 112, 160);">'截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。'</span>, <span style="color: rgb(64, 160, 112);">20</span>)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> __name__ <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 112, 160);">'__main__'</span>:<br><span class="lineno special"></span> demo()<br></code></pre>
<br />
<h4>filters.py</h4>
<pre style="font-family: "monospace","Consolas","Courier New","Bitstream Vera Sans Mono";" class="codes"><code><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(96, 160, 176); font-style: italic;"># -*- coding: UTF-8 -*-</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> from <span style="color: rgb(14, 132, 181); font-weight: bold;">django.template</span> import Library<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> from <span style="color: rgb(14, 132, 181); font-weight: bold;">django.template.defaultfilters</span> import stringfilter<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span class="lineno special"></span> register <span style="color: rgb(102, 102, 102);">=</span> Library()<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(85, 85, 85); font-weight: bold;">@stringfilter</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">def</span> <span style="color: rgb(6, 40, 126);">truncatehanzi</span>(value, arg):<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(64, 112, 160); font-style: italic;">"""</span><br><span class="lineno special"></span> <span style="color: rgb(64, 112, 160); font-style: italic;"> Truncates a string after a certain number of words including</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(64, 112, 160); font-style: italic;"> alphanumeric and CJK characters.</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(64, 112, 160); font-style: italic;"> Argument: Number of words to truncate after.</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(64, 112, 160); font-style: italic;"> """</span><br><span class="lineno special"></span> from <span style="color: rgb(14, 132, 181); font-weight: bold;">truncate_hanzi</span> import truncate_hanzi<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">try</span>:<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> length <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32);">int</span>(arg)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">except</span> <span style="color: rgb(0, 112, 32);">ValueError</span>: <span style="color: rgb(96, 160, 176); font-style: italic;"># Invalid literal for int().</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> value <span style="color: rgb(96, 160, 176); font-style: italic;"># Fail silently.</span><br><span class="lineno special"></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> truncate_hanzi(value, length)<br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> truncatehanzi<span style="color: rgb(102, 102, 102);">.</span>is_safe <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32);">True</span><br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> <br><span style="color: rgb(96, 160, 176); font-style: italic;" class="lineno"></span> register<span style="color: rgb(102, 102, 102);">.</span>filter(<span style="color: rgb(64, 112, 160);">'truncatehanzi'</span>, truncatehanzi)<br></code></pre>
<br /><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-9213249116408502600?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com12tag:blogger.com,1999:blog-7286295415331143255.post-10765497063856388092008-08-06T00:45:00.008+08:002009-01-26T11:42:20.082+08:00有备无患:一个 jQuery 的时间选择插件<a href="http://plugins.jquery.com/project/yatimepicker">Yet another jQuery time picker plugin.</a> Google 上兜了一圈,没找到中意的时间选择(Time Picker)插件,于是自己写了一个。<a href="http://plugins.jquery.com/project/yatimepicker">timepicker</a> 是一个弹出式的对话框,分为上下两个部分,上半部分是可以分别设定时分秒的计数器,下半部分提供一些常用的时间选项,比如“Now”,“Noon”,“8 p.m.”等。通过聚焦(focus)到相应的 input 控件,可以激活 timepicker。选择好时间后,点击对话框底部的 Close 按钮会将结果保存到 input 控件中,而点击 Clear 按钮会清除 input 控件中已存的时间。键盘上的 Tab 键和 ESC 键也有与之对应的功能。<br />
<br />
这是我的第一个 <a href="http://plugins.jquery.com/" rel="nofollow">jQuery 插件</a>,欢迎大家使用,我也会继续完善这个插件。如果有任何的 Bug,建议和意见,欢迎在文章后留言,或者 Email 给我,或者在<a href="http://plugins.jquery.com/project/yatimepicker">插件的主页</a>写下你的评论。:-)<br />
<br />
<h4>
使用方法</h4>
<pre class="codes"><code>$('#time-picker').timepicker();</code></pre>
<br />
<h4>
截图</h4>
<a href="http://picasaweb.google.com/lh/photo/7ZTO2omV-u-mQ-w7PwMEwQ?authkey=QNJyVUfN53A&feat=embedwebsite" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img src="http://lh3.ggpht.com/_sKun2mxXeAk/SX0wxFbVa6I/AAAAAAAAA5k/mwrsnvCaL_Q/s800/screenshot.png" /></a><br />
<br />
<h4>演示</h4>
<a href="http://demo.luliban.com/2009/01/jquery-plugin-yet-another-timepicker.html">http://demo.luliban.com/2009/01/jquery-plugin-yet-another-timepicker.html</a><br />
<br />
<br />
<br />
<h4>
下载</h4>
你可以选择下载压缩档。<br />
<a href="http://github.com/wuyuntao/timepicker/tarball/master"><img alt="下载" src="http://blog.luliban.com/images/download90.gif" style="border: medium none;" /></a><br />
<br />
或者用 Git 从架设在 <a href="http://github.com/">GitHub</a> 的软件仓库中检出源代码使用。<br />
<pre class="codes"><code>$ git clone git://github.com/wuyuntao/timepicker.git</code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-1076549706385638809?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com7tag:blogger.com,1999:blog-7286295415331143255.post-26728986724102517232008-08-04T10:20:00.005+08:002008-09-10T13:13:34.257+08:00有备无患:Google Reader 风的豆瓣广播 widget最近,豆瓣连续发布了几个重要的 API 更新,包括了备受期待的<a href="http://www.douban.com/group/topic/3790826/" id="m2tk" rel="nofollow" title="[7.26]豆瓣API更新">友邻</a>和<a href="http://www.douban.com/group/topic/3835188/" id="q.sk" rel="nofollow" title="[7.31]豆瓣广播API发布">广播</a>相关的接口。特别是广播的 API,自从用豆瓣广播取代了 <a href="http://twitter.com/" id="r3kp" rel="nofollow" title="Twitter">Twitter</a> 当作吐口水的工具之后,我一直期待广播也有像 <a href="https://addons.mozilla.org/en-US/firefox/addon/5081" id="iao-" rel="nofollow" title="TwitterFox">TwitterFox</a> 一样的客户端,有个 widget 可以在 blog 上显示最近的状态,而这些都是要以广播的 API 为基础的。<br />
<br />
言归正传,我写了一个类似 Google Reader 的分享阅读剪辑(Shared Item Clips)风格的豆瓣广播 widget,可以放在 blog 的侧边栏。使用的方法很简单,只需要将下面这段代码插入到你的 blog 模板中。(wyt:如果不知道怎么操作的话,可以参考<a href="http://www.douban.com/about?topic=badge" id="kia4" rel="nofollow" title="豆瓣秀指南">豆瓣秀指南</a>。)<br />
<br />
<pre class="codes"><code><script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?<b id="nkc9">username=wyt&amp;maxresults=16&amp;style=blue</b>"></script>
</code></pre>
<br />
注意上面代码中粗体的部分,首先我们要将``wyt``换成自己的用户名;``maxresults``为 widget 显示的广播条数(最大值为50,默认为10,可以省略);``style``表示 widget 的配色,和 Google Reader 分享阅读的配色基本一致,包括:black、blue、gray、green、khaki、pink、slate 和 none。默认为 green,如果输入的 style 不在上述配色中,则返回 none。none 为不带任何 style 的原始 HTML,适合希望自定义广播风格的 blogger。<br />
<br />
最终效果如下图。想看看实物 demo 的话,可以点击<a href="http://demos.luliban.com/douban/miniblog-widget.html" id="o483" title="Douban Miniblog Widget Demo">这里</a>。要说明的是,看得到漂亮的圆角效果的只有 Firefox 用户(wyt:这也是 Google Reader 的风格之一,:p),IE、<a href="http://www.opera.com/" id="tw.2" rel="nofollow" title="Opera">Opera</a> 和 <a href="http://www.apple.com/safari/" id="i1_t" rel="nofollow" title="Safari">Safari</a> 用户对不住了。如果有任何的建议或意见,欢迎留言 :-)<br />
<br />
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://lh5.ggpht.com/wuyuntao.1122/SMdTnHGtzTI/AAAAAAAAAnM/0VxynJlxwB0/s800/douban-miniblog-widget-demo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://lh5.ggpht.com/wuyuntao.1122/SMdTnHGtzTI/AAAAAAAAAnM/0VxynJlxwB0/s800/douban-miniblog-widget-demo.png" /></a></div>
</div><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-2672898672410251723?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com9tag:blogger.com,1999:blog-7286295415331143255.post-69746862562222633702008-07-18T11:52:00.005+08:002008-09-10T12:54:34.609+08:00有备无患:在 Django 上使用 reCAPTCHA 生成验证码<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/_sKun2mxXeAk/SMdS8WBHNWI/AAAAAAAAAnE/gGyO-s07-u0/s1600-h/recaptcha-screenshot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/_sKun2mxXeAk/SMdS8WBHNWI/AAAAAAAAAnE/SM3k-TUyf6A/s400-R/recaptcha-screenshot.png" /></a></div>
</div>
<a href="http://recaptcha.net/" rel="nofollow" title="reCAPTCHA: Stop Spam, Read Books">reCAPTCHA</a> 是由 <a href="http://www.captcha.net/" rel="nofollow" title="CAPTCHA">CAPTCHA</a> 的原作者,也是人类计算学(Human Computation)的专家 <a href="http://www.cs.cmu.edu/%7Ebiglou/" rel="nofollow" title="Luis von Ahn">Luis von Ahn</a> 所设计一个免费的 anti-bot 服务,它在随机提取的单词上加上一些扭曲和可阻碍电脑自动识别的噪声,以利用人类远强于电脑的图形识别能力来检查屏幕前坐着的是人类,还是 bot 或自动脚本。因此,reCAPTCHA 经常被用于生成网站注册或者文章评论的验证码。<br />
<br />
除此之外,reCAPTCHA 还有一项非常公益的设计。虽然 <a href="http://zh.wikipedia.org/wiki/OCR" rel="nofollow" title="OCR">OCR</a> (全称 Optical Character Recognitio,中译光学字元识别)早已被投入使用,但是对那些印刷模糊或分辨度不足的实体书籍而言,OCR 的识别率难说完美。而 reCAPTCHA 将这些上无法由电脑自动识别的单词扫描下来,以图片的形式存入数据库,并在网络上以验证码(CAPTCHA)的形式交由人类来破译,而破译的结果将被用于帮助世界上的实体书加快数字化的进程。<br />
<br />
你可能会问,既然电脑无法识别这些单词,那么 reCAPTCHA 系统又怎么知道用户填写的是正确的验证码呢?reCAPTCHA 的网站上有<a href="http://recaptcha.net/learnmore.html" id="wd:-" rel="nofollow" title="如下解释">详细的解释</a>。<br />
<br />
一个不能被 OCR 正确识别的新单词,总是会和另一个已知答案的单词一起提交给用户。接下来用户需要同时识别这两个单词。如果用户正确识别出其中已知答案的那个单词,系统即假设用户所识别出的未知答案的新单词也可能是正确的。如果其他的用户也识别出相同的答案,系统则逐步提高这种可能性。最终当这种可能性超过某个阈值时,系统就可以将其认作已知答案的单词了。<br />
<br />
言归正传,reCAPTCHA 有<a href="http://recaptcha.net/resources.html" rel="nofollow" title="丰富的 API">丰富的 API</a>,包括对 PHP、JAVA、Ruby,自然还有 Python 的支持。如果要在 Django 上使用 reCAPTCHA,我们可以从 PyPI 下载 <a href="http://pypi.python.org/pypi/recaptcha-client" rel="nofollow" title="reCAPTCHA 的 Python 客户端">reCAPTCHA 的 Python 客户端</a>,或者直接用 easy_install 安装。<br />
<br />
<br />
<pre class="commands"><code><b># easy_install recaptcha-client</b></code></pre>
<br />
不知道为什么,虽然安装成功了,但是我不能导入 recaptcha 模块(wyt:import captcha 或 import recaptcha 都没有用,哪位如果顺利导入过的话,能不能分享一下你的经验呢?先谢了),所以我是把 egg 中的脚本直接复制到我的 Django 项目目录下了。<br />
<br />
接下来,我们需要<a href="https://admin.recaptcha.net/recaptcha/createsite/" title="在 reCAPTCHA 申请一对公钥/私钥">在 reCAPTCHA 注册并申请一对公钥/私钥</a>,并将其保存在 settings.py 中。<br />
<br />
<h4>
settings.py</h4>
<pre class="codes"><code>1 <span style="color: #60a0b0; font-style: italic;"># reCAPTCHA keys</span>
2 RECAPTCHA_PUBLIC_KEY <span style="color: #666666;">=</span> <span style="color: #4070a0;">"6LdqgAIAAAUSHDmo4IIBmsjUsduAUMUoBDZc3J_T"</span>
3 RECAPTCHA_PRIVATE_KEY <span style="color: #666666;">=</span> <span style="color: #4070a0;">"6LdqgAIAAbjsJKLbj2KOjPO6D4isfJ_AzLSO_256"</span>
</code></pre>
<br />
然后,我们要为注册表单添加验证码了。在 <a href="http://www.djangosnippets.org/" title="Django Snippets">Django Snippets</a> 上,<a href="http://www.djangosnippets.org/users/oggy/" title="oggy">oggy</a> 已经将 reCAPTCHA 抽象成一个 RecaptchaForm 类,所以我们只要让注册表单(Registration Form)继承这个类,就可以为注册表单添加验证码的功能了。由于 Django newform 默认是根据 field 的定义顺序来生成表单,所以在继承 RecaptchaForm 的时候,也应该把它放在最后继承。<br />
<br />
<h4>
recaptcha/forms.py</h4>
<pre class="codes"><code>01 <span style="color: #007020; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">django</span> <span style="color: #007020; font-weight: bold;">import</span> newforms <span style="color: #007020; font-weight: bold;">as</span> forms
02 <span style="color: #007020; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">django.newforms</span> <span style="color: #007020; font-weight: bold;">import</span> ValidationError
03 <span style="color: #007020; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">django.conf</span> <span style="color: #007020; font-weight: bold;">import</span> settings
04 <span style="color: #007020; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">recaptcha</span> <span style="color: #007020; font-weight: bold;">import</span> captcha
05
06
07 <span style="color: #007020; font-weight: bold;">class</span> <span style="color: #0e84b5; font-weight: bold;">RecaptchaWidget</span>(forms<span style="color: #666666;">.</span>Widget):
08 <span style="color: #4070a0; font-style: italic;">""" A Widget which "renders" the output of captcha.displayhtml """</span>
09 <span style="color: #007020; font-weight: bold;">def</span> <span style="color: #06287e;">render</span>(<span style="color: #007020;">self</span>, <span style="color: #666666;">*</span>args, <span style="color: #666666;">**</span>kwargs):
10 <span style="color: #007020; font-weight: bold;">return</span> captcha<span style="color: #666666;">.</span>displayhtml(settings<span style="color: #666666;">.</span>RECAPTCHA_PUBLIC_KEY)
11
12 <span style="color: #007020; font-weight: bold;">class</span> <span style="color: #0e84b5; font-weight: bold;">DummyWidget</span>(forms<span style="color: #666666;">.</span>Widget):
13 <span style="color: #4070a0; font-style: italic;">"""</span>
14 <span style="color: #4070a0; font-style: italic;"> A dummy Widget class for a placeholder input field which will</span>
15 <span style="color: #4070a0; font-style: italic;"> be created by captcha.displayhtml</span>
16
17 <span style="color: #4070a0; font-style: italic;"> """</span>
18 <span style="color: #60a0b0; font-style: italic;"># make sure that labels are not displayed either</span>
19 is_hidden<span style="color: #666666;">=</span><span style="color: #007020;">True</span>
20 <span style="color: #007020; font-weight: bold;">def</span> <span style="color: #06287e;">render</span>(<span style="color: #007020;">self</span>, <span style="color: #666666;">*</span>args, <span style="color: #666666;">**</span>kwargs):
21 <span style="color: #007020; font-weight: bold;">return</span> <span style="color: #4070a0;">''</span>
22
23 <span style="color: #007020; font-weight: bold;">class</span> <span style="color: #0e84b5; font-weight: bold;">RecaptchaForm</span>(forms<span style="color: #666666;">.</span>Form):
24 <span style="color: #4070a0; font-style: italic;">"""</span>
25 <span style="color: #4070a0; font-style: italic;"> A form class which uses reCAPTCHA for user validation.</span>
26 <span style="color: #4070a0; font-style: italic;"> </span>
27 <span style="color: #4070a0; font-style: italic;"> If the captcha is not guessed correctly, a ValidationError is raised</span>
28 <span style="color: #4070a0; font-style: italic;"> for the appropriate field</span>
29 <span style="color: #4070a0; font-style: italic;"> """</span>
30 recaptcha_challenge_field <span style="color: #666666;">=</span> forms<span style="color: #666666;">.</span>CharField(widget<span style="color: #666666;">=</span>DummyWidget)
31 recaptcha_response_field <span style="color: #666666;">=</span> forms<span style="color: #666666;">.</span>CharField(widget<span style="color: #666666;">=</span>RecaptchaWidget, label<span style="color: #666666;">=</span><span style="color: #4070a0;">''</span>)
32
33 <span style="color: #007020; font-weight: bold;">def</span> <span style="color: #06287e;">__init__</span>(<span style="color: #007020;">self</span>, request, <span style="color: #666666;">*</span>args, <span style="color: #666666;">**</span>kwargs):
34 <span style="color: #007020;">super</span>(RecaptchaForm, <span style="color: #007020;">self</span>)<span style="color: #666666;">.</span>__init__(<span style="color: #666666;">*</span>args, <span style="color: #666666;">**</span>kwargs)
35 <span style="color: #007020;">self</span><span style="color: #666666;">.</span>_request <span style="color: #666666;">=</span> request
36
37 <span style="color: #007020; font-weight: bold;">def</span> <span style="color: #06287e;">clean_recaptcha_response_field</span>(<span style="color: #007020;">self</span>):
38 <span style="color: #007020; font-weight: bold;">if</span> <span style="color: #4070a0;">'recaptcha_challenge_field'</span> <span style="color: #007020; font-weight: bold;">in</span> <span style="color: #007020;">self</span><span style="color: #666666;">.</span>cleaned_data:
39 <span style="color: #007020;">self</span><span style="color: #666666;">.</span>validate_captcha()
40 <span style="color: #007020; font-weight: bold;">return</span> <span style="color: #007020;">self</span><span style="color: #666666;">.</span>cleaned_data[<span style="color: #4070a0;">'recaptcha_response_field'</span>]
41
42 <span style="color: #007020; font-weight: bold;">def</span> <span style="color: #06287e;">clean_recaptcha_challenge_field</span>(<span style="color: #007020;">self</span>):
43 <span style="color: #007020; font-weight: bold;">if</span> <span style="color: #4070a0;">'recaptcha_response_field'</span> <span style="color: #007020; font-weight: bold;">in</span> <span style="color: #007020;">self</span><span style="color: #666666;">.</span>cleaned_data:
44 <span style="color: #007020;">self</span><span style="color: #666666;">.</span>validate_captcha()
45 <span style="color: #007020; font-weight: bold;">return</span> <span style="color: #007020;">self</span><span style="color: #666666;">.</span>cleaned_data[<span style="color: #4070a0;">'recaptcha_challenge_field'</span>]
46
47 <span style="color: #007020; font-weight: bold;">def</span> <span style="color: #06287e;">validate_captcha</span>(<span style="color: #007020;">self</span>):
48 rcf <span style="color: #666666;">=</span> <span style="color: #007020;">self</span><span style="color: #666666;">.</span>cleaned_data[<span style="color: #4070a0;">'recaptcha_challenge_field'</span>]
49 rrf <span style="color: #666666;">=</span> <span style="color: #007020;">self</span><span style="color: #666666;">.</span>cleaned_data[<span style="color: #4070a0;">'recaptcha_response_field'</span>]
50 ip_address <span style="color: #666666;">=</span> <span style="color: #007020;">self</span><span style="color: #666666;">.</span>_request<span style="color: #666666;">.</span>META[<span style="color: #4070a0;">'REMOTE_ADDR'</span>]
51 check <span style="color: #666666;">=</span> captcha<span style="color: #666666;">.</span>submit(rcf, rrf, settings<span style="color: #666666;">.</span>RECAPTCHA_PRIVATE_KEY, ip_address)
52 <span style="color: #007020; font-weight: bold;">if</span> <span style="color: #007020; font-weight: bold;">not</span> check<span style="color: #666666;">.</span>is_valid:
53 <span style="color: #007020; font-weight: bold;">raise</span> ValidationError(<span style="color: #4070a0;">'You have not entered the correct words'</span>)
</code></pre>
<br />
<br />
<h4>
user/forms.py</h4>
<pre class="codes"><code>01 <span style="color: #007020; font-weight: bold;">from</span> <span style="color: #0e84b5; font-weight: bold;">myproject.recaptcha.forms</span> <span style="color: #007020; font-weight: bold;">import</span> RecaptchaForm
02
03 <span style="color: #007020; font-weight: bold;">class</span> <span style="color: #0e84b5; font-weight: bold;">RegistrationForm</span>(forms<span style="color: #666666;">.</span>Form):
04 <span style="color: #4070a0; font-style: italic;">"""</span>
05 <span style="color: #4070a0; font-style: italic;"> Form for registering a new user account.</span>
06 <span style="color: #4070a0; font-style: italic;"> </span>
07 <span style="color: #4070a0; font-style: italic;"> """</span>
08
09 <span style="color: #007020; font-weight: bold;">class</span> <span style="color: #0e84b5; font-weight: bold;">RegistrationFormWithRecaptcha</span>(RegistrationForm, RecaptchaForm):
10 <span style="color: #4070a0; font-style: italic;">"""</span>
11 <span style="color: #4070a0; font-style: italic;"> Subclass of ``RegistrationForm`` and ``RecaptchaForm`` which can tell whether its user</span>
12 <span style="color: #4070a0; font-style: italic;"> is a human or a computer.</span>
13
14 <span style="color: #4070a0; font-style: italic;"> """</span>
</code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-6974686256222263370?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com5tag:blogger.com,1999:blog-7286295415331143255.post-49015718344047186952008-07-04T21:01:00.004+08:002008-07-04T21:31:25.361+08:00有备无患:浏览器端缓存(基于 jQuery)<p>最近在翻《<a rel="nofollow" title="Ajax 设计模式" href="http://www.douban.com/subject/2121078/">Ajax 设计模式</a>》,所以想把其中的一部分模式实现出来练手 <a rel="nofollow" title="jQuery" href="http://jquery.com/">jQuery</a>,今天写了一个简单的引入 LRU 算法的浏览器端缓存(Browser Side Cache)。<br />
<br />
浏览器端缓存用来保留服务器返回的查询结果。这种缓存是一个 Javascript 里类似映射的对象,存储成对的查询结果;查询是缓存的键(Key),服务器返回的结果是缓存的值(Value)。因此,每当浏览器需要查询服务器时,先检查缓存。如果该查询是缓存中的一个键,则与键对应的值将被当作结果,而不必再向服务器查询。<br />
<br />
LRU (Least Recently Used)是将存储在缓存中的自上一次获取之后,最长时间未被使用的项目(Item)丢弃的一种算法。可以用两个数组(Array)来实现,其一是用来存储查询结果的键值对(Key-Value Pairs),其二是一个先进先出(FIFO)的队列,每一个新项目被塞入队列的尾部,并随着后续项目的跟进,逐渐逼近队列的头部。当队列全满时,每次向尾部塞入一个新项目,就要从头部弹出一个旧项目。而每当一个项目被缓存查询时,它会被送回到队列的尾部,这样可以确保最长时间未被使用的项目总是在开头处。<br />
<br />
另外,还有一种常用的缓存算法,LFU (Least Frequently Used),它将自上一次获取后最少被使用的项目丢弃。<br />
<br />
顺便一说,下面贴出的代码是由<a id="umnt10" title="代码发芽网" href="http://www.fayaa.com/code/home/">代码发芽网</a>生产的纯 HTML。代码发芽网是一个“无需插件支持 Blog 代码高亮,支持近百种编程语言,多种配色主题支持,代码版本管理”的代码片段管理网站。因为不是由 Javascript 脚本来高亮处理,所以在 feed 里也一样可以看到效果,这一点很赞。不过,由于用处不大的 id 和 class 也包含在生成的 HTML 里,所以体积偏大。比如说下面这段代码,在删除了多余的 id 和 class,套上代码专用的 <a id="umnt11" rel="nofollow" title="pre" href="http://www.w3schools.com/TAGS/tag_pre.asp">pre</a> 和 <a id="umnt12" rel="nofollow" title="code" href="http://www.w3schools.com/TAGS/tag_phrase_elements.asp">code</a> 标签,并将 &nbsp; 还原成空格之后,体积可以缩小一半仅17KB(原来35KB),更适合 blog 来贴代码。<br />
<br />
<br />
<h4>cache.js</h4>
<pre class="codes"><code><span>01</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span> <span>Cache</span><span>(</span><span>size</span><span>)</span> <span>{</span>
<span>02</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* A browser side LRU cache </span>
<span>03</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * Author: Wu Yuntao <http://luliban.com/blog/></span>
<span>04</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * License: GPLv3</span>
<span>05</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> *</span>
<span>06</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * Usage:</span>
<span>07</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * var cache = new Cache(10); // create a new cache object</span>
<span>08</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * cache.put('w', 'wiki'); // put an item into cache</span>
<span>09</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * cache.get('w'); // get the value of item with key</span>
<span>10</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * cache.remove('w'); // remove an item with specified key</span>
<span>11</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * cache.initialize() // re-initialize the cache</span>
<span>12</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * cache.size(10) // resize the cache</span>
<span>13</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>14</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>initialize</span><span>(</span><span>size</span><span>);</span>
<span>15</span> <span>}</span>
<span>16</span>
<span>17</span> <span>Cache</span><span>.</span><span>prototype</span> <span style="color: rgb(102, 102, 102);">=</span> <span>{</span>
<span>18</span> <span>initialize</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>(</span><span>size</span><span>)</span> <span>{</span>
<span>19</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Initialize cache.</span>
<span>20</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * ``size`` is the number of maxmium items this cache should hold.</span>
<span>21</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * Default is maxium integer.</span>
<span>22</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>23</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span> <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">new</span> <span style="color: rgb(0, 112, 32);">Array</span><span>();</span>
<span>24</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span> <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">new</span> <span style="color: rgb(0, 112, 32);">Array</span><span>();</span>
<span>25</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_size</span> <span style="color: rgb(102, 102, 102);">=</span> <span>size</span> <span style="color: rgb(102, 102, 102);">||</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_size</span> <span style="color: rgb(102, 102, 102);">||</span> <span style="color: rgb(0, 112, 32);">Number</span><span>.</span><span>MAX_VALUE</span><span style="color: rgb(102, 102, 102);">;</span>
<span>26</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>27</span>
<span>28</span> <span>is_empty</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>()</span> <span>{</span>
<span>29</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Check if cache is empty</span>
<span>30</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>31</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span>(</span><span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>.</span><span>length</span> <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 160, 112);">0</span> <span style="color: rgb(102, 102, 102);">&&</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span><span>.</span><span>length</span> <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 160, 112);">0</span><span>);</span>
<span>32</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>33</span>
<span>34</span> <span>size</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>(</span><span>size</span><span>)</span> <span>{</span>
<span>35</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Resize cache if ``size`` is specified, or return accual size of cache.</span>
<span>36</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>37</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> <span>(</span><span style="color: rgb(0, 112, 32); font-weight: bold;">typeof</span> <span>size</span> <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 112, 160);">'undefined'</span><span>)</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>.</span><span>length</span><span style="color: rgb(102, 102, 102);">;</span>
<span>38</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_size</span> <span style="color: rgb(102, 102, 102);">=</span> <span>size</span><span style="color: rgb(102, 102, 102);">;</span>
<span>39</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>40</span>
<span>41</span> <span>put</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>(</span><span>key</span><span style="color: rgb(102, 102, 102);">,</span> <span>value</span><span>)</span> <span>{</span>
<span>42</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Put a new item into cache, if the size of cache reaches limit,</span>
<span>43</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * cache will remove the least recently used (LRU) automatically.</span>
<span>44</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * ``key`` of an item should be a string.</span>
<span>45</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * ``value`` of an item could be anything, string, array or object.</span>
<span>46</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * If ``value`` is not defined, returns null.</span>
<span>47</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>48</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> <span>(</span><span style="color: rgb(0, 112, 32); font-weight: bold;">typeof</span> <span>value</span> <span style="color: rgb(102, 102, 102);">!=</span> <span style="color: rgb(64, 112, 160);">'undefined'</span><span>)</span> <span>{</span>
<span>49</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>.</span><span>push</span><span>(</span><span>key</span><span>);</span>
<span>50</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span><span>[</span><span>key</span><span>]</span> <span style="color: rgb(102, 102, 102);">=</span> <span>value</span><span style="color: rgb(102, 102, 102);">;</span>
<span>51</span>
<span>52</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> <span>(</span><span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>.</span><span>length</span> <span style="color: rgb(102, 102, 102);">></span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_size</span><span>)</span> <span>{</span>
<span>53</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>remove_least</span><span>();</span>
<span>54</span> <span>}</span>
<span>55</span> <span>}</span>
<span>56</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span>value</span><span style="color: rgb(102, 102, 102);">;</span>
<span>57</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>58</span>
<span>59</span> <span>get</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>(</span><span>key</span><span>)</span> <span>{</span>
<span>60</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Retrieve an item by its key and move it to the tail of cache.</span>
<span>61</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * If the item of ``key`` does not exist, returns null.</span>
<span>62</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>63</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> <span>(</span><span style="color: rgb(0, 112, 32); font-weight: bold;">typeof</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span><span>[</span><span>key</span><span>]</span> <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 112, 160);">'undefined'</span><span>)</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">null</span><span style="color: rgb(102, 102, 102);">;</span>
<span>64</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">var</span> <span>used_key</span> <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_remove_key</span><span>(</span><span>key</span><span>);</span>
<span>65</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>.</span><span>push</span><span>(</span><span>used_key</span><span>);</span>
<span>66</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span><span>[</span><span>key</span><span>];</span>
<span>67</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>68</span>
<span>69</span> <span>remove_least</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>()</span> <span>{</span>
<span>70</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Manually remove the least recently used item. */</span>
<span>71</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> <span>(</span><span style="color: rgb(102, 102, 102);">!</span><span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>is_empty</span><span>())</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>remove</span><span>(</span><span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>[</span><span style="color: rgb(64, 160, 112);">0</span><span>]);</span>
<span>72</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>73</span>
<span>74</span> <span>remove</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>(</span><span>key</span><span>)</span> <span>{</span>
<span>75</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Remove an item by its key.</span>
<span>76</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> * If the item of ``key`` does not exist, returns null.</span>
<span>77</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>78</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">if</span> <span>(</span><span style="color: rgb(0, 112, 32); font-weight: bold;">typeof</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span><span>[</span><span>key</span><span>]</span> <span style="color: rgb(102, 102, 102);">==</span> <span style="color: rgb(64, 112, 160);">'undefined'</span><span>)</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">null</span><span style="color: rgb(102, 102, 102);">;</span>
<span>79</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_remove_key</span><span>(</span><span>key</span><span>);</span>
<span>80</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_remove_value</span><span>(</span><span>key</span><span>);</span>
<span>81</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>82</span>
<span>83</span> <span>_remove_key</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>(</span><span>key</span><span>)</span> <span>{</span>
<span>84</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Remove the ``key`` in ``this._keys``.</span>
<span>85</span> <span style="color: rgb(96, 160, 176); font-style: italic;"> */</span>
<span>86</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">var</span> <span>i</span> <span style="color: rgb(102, 102, 102);">=</span> <span>$</span><span>.</span><span>inArray</span><span>(</span><span>key</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>);</span>
<span>87</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_keys</span><span>.</span><span>splice</span><span>(</span><span>i</span><span style="color: rgb(102, 102, 102);">,</span> <span style="color: rgb(64, 160, 112);">1</span><span>);</span>
<span>88</span> <span>}</span><span style="color: rgb(102, 102, 102);">,</span>
<span>89</span>
<span>90</span> <span>_remove_value</span><span style="color: rgb(102, 102, 102);">:</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">function</span><span>(</span><span>key</span><span>)</span> <span>{</span>
<span>91</span> <span style="color: rgb(96, 160, 176); font-style: italic;">/* Remove the item in ``this._items``. */</span>
<span>92</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">var</span> <span>value</span> <span style="color: rgb(102, 102, 102);">=</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span><span>[</span><span>key</span><span>];</span>
<span>93</span> <span>delete</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">this</span><span>.</span><span>_items</span><span>[</span><span>key</span><span>];</span>
<span>94</span> <span style="color: rgb(0, 112, 32); font-weight: bold;">return</span> <span>value</span><span style="color: rgb(102, 102, 102);">;</span>
<span>95</span> <span>}</span>
<span>96</span>
<span>97</span> <span>};</span></code></pre><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7286295415331143255-4901571834404718695?l=luliban.blogspot.com'/></div>wythttp://www.blogger.com/profile/04947921907676762514noreply@blogger.com2