- 东方地灵殿(東方地霊殿 〜 Subterranean Animism.)是日本同人社团上海爱莉丝幻乐团制作的一款弹幕系射击游戏
- wine 的版本为 1.1.5
- 无法启动 th11.exe,需要下载,或者从 Windows 下面复制一个 d3dx9_36.dll 文件,
$ cp d3dx9_36.dll ~/.wine/drive_c/windows/system32/ - 运行地灵殿的窗口模式的时候,分辨率似乎被限制在了640x480。没有找到解决办法
- 如果使用虚拟桌面(Virtual Desktop) + 地灵殿的全屏模式,感觉会有点卡,帧速降到50fps左右
- 如果使用虚拟桌面 + 窗口模式,游戏界面黑屏有声音
- 如果取消虚拟桌面 + 窗口模式,感觉上速度和 Windows 下面差不多,不过限速 60fps,实际差多少也不知道就对了
- 运行正确的话,wine 应该只会输出下面这几行消息,
$ 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! - 有一篇更加详细的说明,東方地霊殿に関するメモ(日语)
2008年10月14日
悠言悠闲:linux 下玩/wine 東方地霊殿
2008年9月20日
有备无患:豆瓣离线 - 利用 Google Gears 实现离线浏览豆瓣的 GreaseMonkey 脚本
豆瓣离线(Douban Offline)是一个可以让用户可以离线浏览豆瓣的 GreaseMonkey 脚本。它可以在网络无法使用的情况下,浏览保存在本地的页面。它也可以用来收藏或备份豆瓣上的条目、小组和友邻等页面。
豆瓣离线使用 Google Gears API 来实现离线浏览的功能。Google Gears 是一款 Google 开发的软件。Gears 通过 SQLite 数据库让客户端能够把网页暂存起来,并通过内部服务器(Local server)把数据库中暂存的网页重现,从而让用户实现离线上网的功能。Google Docs 和 Google Reader 都支持用 Gears 将本地暂存的资料与网络做同步。
豆瓣离线现在版本为 0.1,还有许多的不完善之处,欢迎大家的建议、意见和 Bug 报告:-)
大家可以从我的 GitHub Repo 中检出最新的脚本试用
或者从 UserScripts 网站上下载

豆瓣离线使用 Google Gears API 来实现离线浏览的功能。Google Gears 是一款 Google 开发的软件。Gears 通过 SQLite 数据库让客户端能够把网页暂存起来,并通过内部服务器(Local server)把数据库中暂存的网页重现,从而让用户实现离线上网的功能。Google Docs 和 Google Reader 都支持用 Gears 将本地暂存的资料与网络做同步。
豆瓣离线现在版本为 0.1,还有许多的不完善之处,欢迎大家的建议、意见和 Bug 报告:-)
功能
- 保存豆瓣上的某个页面,以及相关的图片、CSS 和 Javascript 脚本
- 按分类浏览已缓存的页面,目前的分类有:条目,小组和用户
使用方法
- 如果希望保存当前页面,我们先找到导航栏“退出”附近的“离线”。点击“离线”,待离线状态栏展开完毕后,点击“离线”下面的“收藏此页面”。注意,点击之后不要马上关闭窗口,因为相关的文件可能还在下载中。另外我们也可以打开 FireBug 的终端(Console)来看看究竟那些文件被下载下来了。
- 如果希望浏览已缓存的页面,
点击 Firefox -> File(文件) -> Work Offline(离线工作)来强制 Firefox 进入离线工作的状态。点击“离线” -> “离线浏览”,然后在缓存页面目录,我们可以点击其中的链接访问已被缓存的页面。 - 如果希望恢复在线浏览,在同样的地方点击“在线浏览”。
- 第一次使用豆瓣离线脚本时,Gears 会弹出两个安全警告对话框,都选择 Allow(允许)就可以了。
安装需求
- Firefox (>=3.0.0): http://www.mozilla.com/en-US/firefox/
-
GreaseMonkey (>=0.8):https://addons.mozilla.org/en-US/firefox/addon/748
- Google Gears (>=0.4):http://gears.google.com/
下载
大家可以从我的 GitHub Repo 中检出最新的脚本试用
git clone git://github.com/wuyuntao/douban-offline.git
或者从 UserScripts 网站上下载

2008-09-23 UPDATE 0.2:
豆瓣离线脚本更新为 0.2,有下面这些改动:- 可以手动切换上线/离线浏览
- 可以更新缓存页
- 新增两个分类,日记和相册
- 选中分类有高亮
2008-10-15 下载链接更新:
祸不单行。github 前几天数据库出了问题,到现在还没有恢复; userscripts.org 貌似被 GFW 给封了;今天 GAE 提供的四个 IP 中的又一个阵亡了。。。不管怎样,上面两个下载的链接都不管用了,现在提供一个本地的下载链接。
2008-10-22 UPDATE 0.4:
豆瓣离线脚本更新为 0.4,有下面这些改动:- 增加了翻页的功能
- 增加了评论和豆邮两个标签
- 增加了一条 Douban Helper 的控制台命令:write(wyt:Douban Helper 请安装最新版本)
- 改正了相册日记等页面离线按钮位置不正确的bug
标签:
google,
greasemonkey,
javascript,
豆瓣
2008年9月14日
有备无患:Google Reader 风的豆瓣广播 widget 更新
今天为 Google Reader 风的豆瓣广播 widget 新增了三个选项:手动设置宽度 width,隐藏标题 hidetitle 和隐藏主页链接 hidefooter。谢谢 cow 和 asiapan 的建议先。
使用方法和以前的说明一样,只需要将下面这段代码插入到你的 blog 模板中,并替换其中的参数。
隐藏标题和主页的效果如下
使用方法和以前的说明一样,只需要将下面这段代码插入到你的 blog 模板中,并替换其中的参数。
<script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?username=wyt&maxresults=16&style=blue"></script>
参数列表如下
- username: 用户名,或者用户 id。(必填)
- maxresults: 输出广播条目的数量。最大值为50,默认为10。
- style: widget 的配色样式,默认为绿色。其他样式包括 black(黑)、blue(蓝)、gray(灰)、green(绿)、khaki(卡其色)、pink(粉红)、slate(蓝灰) 和 none(无)。其中,none 为不带任何样式的原始 HTML,适合希望自定义广播风格的 bloggers。
- width: 手动设定 widget 的宽度,输入应为宽度的像素值,如 width=300。默认为自适应宽度(推荐)。
- hidetitle: 如果设 hidetitle=true,将隐藏顶部标题。默认为 false。
- hidefooter: 如果设 hidefooter=true,将隐藏底部豆瓣主页的链接。默认为 false。
隐藏标题和主页的效果如下
2008年9月12日
有备无患:升级到 Git 1.6.01 之后无法启动 git-daemon 的解决办法
git 升级到 1.6 之后,发现 git-daemon 不能启动,自然也不能作 clone 或 push 之类的操作。原因是原来的 /usr/bin/git-daemon 搬到了 /usr/libexec/git-core 目录,但是 Gentoo 的启动脚本没有随之更新。不过有人已经提交了这个 bug。
自己也可动手,将启动脚本 /etc/init.d/git-daemon 中的 start 函数修改如下。
另外,我用来架设 Git 版本库的 gitosis 也和 git 1.6 有一点兼容问题,原因是 /usr/bin/git-shell 也搬到了 /usr/libexec/git-core 目录。好在已经有 patch 推出,只需要修改 /usr/lib/python2.5/site-packages/gitosis/serve.py,将其中调用 git-shell 的指令,替换成调用 git shell 即可。
自己也可动手,将启动脚本 /etc/init.d/git-daemon 中的 start 函数修改如下。
start() {
ebegin "Starting git-daemon"
start-stop-daemon --start --quiet --background \
--exec /usr/libexec/git-core/git-daemon -- ${GITDAEMON_OPTS}
eend $?
}
另外,我用来架设 Git 版本库的 gitosis 也和 git 1.6 有一点兼容问题,原因是 /usr/bin/git-shell 也搬到了 /usr/libexec/git-core 目录。好在已经有 patch 推出,只需要修改 /usr/lib/python2.5/site-packages/gitosis/serve.py,将其中调用 git-shell 的指令,替换成调用 git shell 即可。
--- 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)
2008年9月10日
南言北哲:Battier or Artest,谁将首发?
1,Battier or Artest,谁将首发?
Artest。联盟最好的人盯人防守者之一,上赛季场均20.5分的进攻水准,以及38%的三分球命中率。加上曾在主教练 Rick Adelman 手底下打过一年球,对火箭现在这套动态进攻的体系了然在胸。如果在训练营和季前赛中一切顺利,他极有可能会取代 Shane Battier 成为火箭的正印前锋。
有同学认为,Artest 应妨 Luis Scola 之例,先从替补打起,走循序渐进的路线。可是你要知道,Scola 虽然在 FIBA 摸爬滚打了多年,但是他也需要时间来适应 NBA,另一方面主教练也需要时间来了解他的技术特点,所以 Scola 必须从替补打起。Artest 却是已经在联盟混迹了整九年,别说 Adelman,就是联盟其余的29位主教练也同样对他知根知底。
另外,虽然 Artest 曾经表过态,说愿意从替补打起。可是,以史为鉴,Artest 不是甘居替补之人——05年他炮轰 Jermaine O'Neal 出手权多和进攻效率低,被交易到 Sacramento 之后,又与 Mike Bibby 争当“国王”。(wyt: 最终都以阿泰“抢班夺权”失败而告终。)
2,如果 Battier 做替补,能不能胜任第六人的角色?
像 Battier 这样的防守型球员也一样可以胜任第六人,这甚至还有可能成为一股潮流。
联盟的大多数第六人都是长于进攻的球员,比如被交易回国王的 Bobby Jackson,湖人签下肥约的 Sasha Vujacic,小牛老将 Jerry Stackhouse,还有掘金的 J.R. Smith 等,他们的主要作用是在主力下场的时候带领其他替补球员,提供持续的进攻力,并为主力争取更多的休息时间。(wyt:我不喜欢把 Manu Ginobili 和 Jason Terry 归类到第六人。如果非要说,我更愿意称他们为“伪第六人”,或者,“联盟第六人奖项欺诈师”)
但是对于拥有所谓的“三巨头”的球队,这个角色常常是由“三巨头”的其中之一所担任,比如凯尔特人的 Ray Allen 和马刺的 Manu Ginobili。因而这些球队对第六人在进攻端的要求也有所不同。以 Boston 为例,上赛季第六人就是场均只有7.4分进帐的防守悍将 James Posey。总决赛中出色的表现,也让黄蜂弃用原来的第六人 Jannero Pargo,花重金将 Posey 挖来作为下赛季他们的第六人。另外,湖人下赛季也有可能让 Odom 出任第六人。
Battier 之于火箭,就像 Posey 之于 Boston 和 New Olean。而且 Battier 有更出色的协防能力和不错的背身技巧,可以想见新赛季的第四节,我们经常会看到 Battier 和 Artest 一起打完最后一分钟。
3,如果 Artest 做先发,出手次数会不会不够?
这很可能是杞人忧天。
上赛季,有三支球队的核心球员被称作“三巨头”,分别是凯尔特人的 Garnett、Pierce 和 Allen,马刺的 Duncan、Parker 和 Ginobili,以及湖人的 Kobe、Odom 和 Gasol。我们首先来看看他们的出手分配情况,并和火箭作一下对比。
表格中的 FGA 为出手次数 (Field Goal Attempts),FTA 为罚球次数 (Free Throw Attempts),TP 为球队节奏 (Team Pace),TSA 为每100次球权的真实出手次数 (True Shooting Attempts),所使用的统计公式为 TSA = (FGA + 0.44 * FTA) / TP * 100。要说明的,湖人的数据是“打劫” Gasol 以后的全明星之后的数据,而其他球队是整个赛季的数据。

从图表中,我们可以看出光是姚明,T-Mac 和 Battier 三个人的出手次数,就已经和其他几个三巨头基本持平。所以即使 Artest 取代 Battier 成为先发,只要姚明和 T-Mac 愿意牺牲自己的球权,火箭的出手次数对于 Artest 来说是绰绰有余的。
另外,由于伤病的关系,姚明和 T-Mac 都有相当长一段时间单独带队,所以也有同学质疑,这段时间他们的出手数是不是突增的,会不会影响到最后的统计结果。我也捎带统计了一下他们同时带队的10月、11月和2月份,姚明的出手数是20.0次,而 T-Mac 的是 24.4次。相比整个赛季并没有太大的变化,这主要是因为无论是姚明还是 T-Mac 带队,他们都是以火箭这个团队来进攻,谁也没有像 Kobe 在 Bynum 和 Gasol 缺席的时候那样贸然增加自己的出手。
Artest。联盟最好的人盯人防守者之一,上赛季场均20.5分的进攻水准,以及38%的三分球命中率。加上曾在主教练 Rick Adelman 手底下打过一年球,对火箭现在这套动态进攻的体系了然在胸。如果在训练营和季前赛中一切顺利,他极有可能会取代 Shane Battier 成为火箭的正印前锋。
有同学认为,Artest 应妨 Luis Scola 之例,先从替补打起,走循序渐进的路线。可是你要知道,Scola 虽然在 FIBA 摸爬滚打了多年,但是他也需要时间来适应 NBA,另一方面主教练也需要时间来了解他的技术特点,所以 Scola 必须从替补打起。Artest 却是已经在联盟混迹了整九年,别说 Adelman,就是联盟其余的29位主教练也同样对他知根知底。
另外,虽然 Artest 曾经表过态,说愿意从替补打起。可是,以史为鉴,Artest 不是甘居替补之人——05年他炮轰 Jermaine O'Neal 出手权多和进攻效率低,被交易到 Sacramento 之后,又与 Mike Bibby 争当“国王”。(wyt: 最终都以阿泰“抢班夺权”失败而告终。)
2,如果 Battier 做替补,能不能胜任第六人的角色?
像 Battier 这样的防守型球员也一样可以胜任第六人,这甚至还有可能成为一股潮流。
联盟的大多数第六人都是长于进攻的球员,比如被交易回国王的 Bobby Jackson,湖人签下肥约的 Sasha Vujacic,小牛老将 Jerry Stackhouse,还有掘金的 J.R. Smith 等,他们的主要作用是在主力下场的时候带领其他替补球员,提供持续的进攻力,并为主力争取更多的休息时间。(wyt:我不喜欢把 Manu Ginobili 和 Jason Terry 归类到第六人。如果非要说,我更愿意称他们为“伪第六人”,或者,“联盟第六人奖项欺诈师”)
但是对于拥有所谓的“三巨头”的球队,这个角色常常是由“三巨头”的其中之一所担任,比如凯尔特人的 Ray Allen 和马刺的 Manu Ginobili。因而这些球队对第六人在进攻端的要求也有所不同。以 Boston 为例,上赛季第六人就是场均只有7.4分进帐的防守悍将 James Posey。总决赛中出色的表现,也让黄蜂弃用原来的第六人 Jannero Pargo,花重金将 Posey 挖来作为下赛季他们的第六人。另外,湖人下赛季也有可能让 Odom 出任第六人。
Battier 之于火箭,就像 Posey 之于 Boston 和 New Olean。而且 Battier 有更出色的协防能力和不错的背身技巧,可以想见新赛季的第四节,我们经常会看到 Battier 和 Artest 一起打完最后一分钟。
3,如果 Artest 做先发,出手次数会不会不够?
这很可能是杞人忧天。
上赛季,有三支球队的核心球员被称作“三巨头”,分别是凯尔特人的 Garnett、Pierce 和 Allen,马刺的 Duncan、Parker 和 Ginobili,以及湖人的 Kobe、Odom 和 Gasol。我们首先来看看他们的出手分配情况,并和火箭作一下对比。
表格中的 FGA 为出手次数 (Field Goal Attempts),FTA 为罚球次数 (Free Throw Attempts),TP 为球队节奏 (Team Pace),TSA 为每100次球权的真实出手次数 (True Shooting Attempts),所使用的统计公式为 TSA = (FGA + 0.44 * FTA) / TP * 100。要说明的,湖人的数据是“打劫” Gasol 以后的全明星之后的数据,而其他球队是整个赛季的数据。

从图表中,我们可以看出光是姚明,T-Mac 和 Battier 三个人的出手次数,就已经和其他几个三巨头基本持平。所以即使 Artest 取代 Battier 成为先发,只要姚明和 T-Mac 愿意牺牲自己的球权,火箭的出手次数对于 Artest 来说是绰绰有余的。
另外,由于伤病的关系,姚明和 T-Mac 都有相当长一段时间单独带队,所以也有同学质疑,这段时间他们的出手数是不是突增的,会不会影响到最后的统计结果。我也捎带统计了一下他们同时带队的10月、11月和2月份,姚明的出手数是20.0次,而 T-Mac 的是 24.4次。相比整个赛季并没有太大的变化,这主要是因为无论是姚明还是 T-Mac 带队,他们都是以火箭这个团队来进攻,谁也没有像 Kobe 在 Bynum 和 Gasol 缺席的时候那样贸然增加自己的出手。
2008年9月5日
有备无患:用 Gaupol 生成文本字幕
Gaupol 是一个用 Python 编写的文本字幕文件的编辑工具,支持包括.ssa、.ass、.srt和.sub在内的多种字幕文件格式,并提供文本校正的方法以及时间处理。其用户界面基于 pyGTK,注重对多文档和翻译的批量处理。最新版本为 0.13。
下面的命令示范了利用 Gaupol 生成一个 SubRip 格式的字幕文件,以作备忘。
生成的 .srt 字幕文件如下
下面的命令示范了利用 Gaupol 生成一个 SubRip 格式的字幕文件,以作备忘。
$ 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'字幕测试三'])
生成的 .srt 字幕文件如下
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
字幕测试三
2008年8月10日
有备无患:truncatehanzi - 按字数截取中文的 Django template filter
Django 自带的一个模板过滤器(Template Filter)truncatewords,可以按单词数截取字符串生成摘要。不过,truncatewords 只能用空格来分隔单词,对汉字就无能为力了。所以,我写了另一个模板过滤器 truncatehanzi,它通过 unicode 编码来区分西方字符和 CJK 汉字,对包括英语字母在内的西方字符以空格或其他符号来分隔并计算单词数,而对 CJK 汉字则是按字数计算。
使用方法和 truncatewords 一样,
代码分为两个部分,截取汉字的脚本 truncate_hanzi.py 和生成过滤器的脚本 filters.py。
使用方法和 truncatewords 一样,
{{ value|truncatehanzi:6 }}
如果 value 为“截取English、Γρεεκ等字母语言和CJK汉字。”,输出“截取English、Γρεεκ等字...”。代码分为两个部分,截取汉字的脚本 truncate_hanzi.py 和生成过滤器的脚本 filters.py。
truncate_hanzi.py
# -*- coding: UTF-8 -*-
import re
from django.utils.encoding import force_unicode
from django.utils.functional import allow_lazy
def truncate_hanzi(s, num):
s = force_unicode(s)
length = int(num)
if length <= 0:
return u'...'
# Set up regex for alphanumeric characters
# \u00c0-\u02af: Latin
# \u0370-\u1fff: Greek and alphabet characters in other language
re_alnum = re.compile(ur'[a-zA-Z0-9_\-\u00c0-\u02af\u0370-\u1fff]', re.U)
# Set up regex for hanzi
# \u3040-\ufaff: CJK characters
re_hanzi = re.compile(ur'[\u3040-\ufaff]', re.U)
hanzi = u''
hanzi_len = 0
word_temp = u''
for char in s:
# Check for alphabet characters
if re_alnum.match(char):
word_temp += char
continue
if word_temp:
hanzi += word_temp
hanzi_len += 1
word_temp = ''
# Check for length
if hanzi_len >= length:
if not hanzi.endswith('...'):
hanzi += '...'
break
# Check for hanzi
if re_hanzi.match(char):
hanzi_len += 1
hanzi += char
hanzi += word_temp
return hanzi
truncate_hanzi = allow_lazy(truncate_hanzi, unicode)
def demo():
print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 6)
print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 11)
print truncate_hanzi('截取段落工具,支持English、Γρεεκ等字母语言和CJK汉字。', 20)
if __name__ == '__main__':
demo()
filters.py
# -*- coding: UTF-8 -*-
from django.template import Library
from django.template.defaultfilters import stringfilter
register = Library()
@stringfilter
def truncatehanzi(value, arg):
"""
Truncates a string after a certain number of words including
alphanumeric and CJK characters.
Argument: Number of words to truncate after.
"""
from truncate_hanzi import truncate_hanzi
try:
length = int(arg)
except ValueError: # Invalid literal for int().
return value # Fail silently.
return truncate_hanzi(value, length)
truncatehanzi.is_safe = True
register.filter('truncatehanzi', truncatehanzi)
2008年8月6日
有备无患:一个 jQuery 的时间选择插件
Yet another jQuery time picker plugin. Google 上兜了一圈,没找到中意的时间选择(Time Picker)插件,于是自己写了一个。timepicker 是一个弹出式的对话框,分为上下两个部分,上半部分是可以分别设定时分秒的计数器,下半部分提供一些常用的时间选项,比如“Now”,“Noon”,“8 p.m.”等。通过聚焦(focus)到相应的 input 控件,可以激活 timepicker。选择好时间后,点击对话框底部的 Close 按钮会将结果保存到 input 控件中,而点击 Clear 按钮会清除 input 控件中已存的时间。键盘上的 Tab 键和 ESC 键也有与之对应的功能。
这是我的第一个 jQuery 插件,欢迎大家使用,我也会继续完善这个插件。如果有任何的 Bug,建议和意见,欢迎在文章后留言,或者 Email 给我,或者在插件的主页写下你的评论。:-)

或者用 Git 从架设在 GitHub 的软件仓库中检出源代码使用。
这是我的第一个 jQuery 插件,欢迎大家使用,我也会继续完善这个插件。如果有任何的 Bug,建议和意见,欢迎在文章后留言,或者 Email 给我,或者在插件的主页写下你的评论。:-)
使用方法
$('#time-picker').timepicker();
截图
演示
演示网址下载
你可以选择下载压缩档。
或者用 Git 从架设在 GitHub 的软件仓库中检出源代码使用。
$ git clone git://github.com/wuyuntao/timepicker.git
标签:
javascript
2008年8月4日
有备无患:Google Reader 风的豆瓣广播 widget
最近,豆瓣连续发布了几个重要的 API 更新,包括了备受期待的友邻和广播相关的接口。特别是广播的 API,自从用豆瓣广播取代了 Twitter 当作吐口水的工具之后,我一直期待广播也有像 TwitterFox 一样的客户端,有个 widget 可以在 blog 上显示最近的状态,而这些都是要以广播的 API 为基础的。
言归正传,我写了一个类似 Google Reader 的分享阅读剪辑(Shared Item Clips)风格的豆瓣广播 widget,可以放在 blog 的侧边栏。使用的方法很简单,只需要将下面这段代码插入到你的 blog 模板中。(wyt:如果不知道怎么操作的话,可以参考豆瓣秀指南。)
注意上面代码中粗体的部分,首先我们要将``wyt``换成自己的用户名;``maxresults``为 widget 显示的广播条数(最大值为50,默认为10,可以省略);``style``表示 widget 的配色,和 Google Reader 分享阅读的配色基本一致,包括:black、blue、gray、green、khaki、pink、slate 和 none。默认为 green,如果输入的 style 不在上述配色中,则返回 none。none 为不带任何 style 的原始 HTML,适合希望自定义广播风格的 blogger。
最终效果如下图。想看看实物 demo 的话,可以点击这里。要说明的是,看得到漂亮的圆角效果的只有 Firefox 用户(wyt:这也是 Google Reader 的风格之一,:p),IE、Opera 和 Safari 用户对不住了。如果有任何的建议或意见,欢迎留言 :-)
言归正传,我写了一个类似 Google Reader 的分享阅读剪辑(Shared Item Clips)风格的豆瓣广播 widget,可以放在 blog 的侧边栏。使用的方法很简单,只需要将下面这段代码插入到你的 blog 模板中。(wyt:如果不知道怎么操作的话,可以参考豆瓣秀指南。)
<script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?username=wyt&maxresults=16&style=blue"></script>
注意上面代码中粗体的部分,首先我们要将``wyt``换成自己的用户名;``maxresults``为 widget 显示的广播条数(最大值为50,默认为10,可以省略);``style``表示 widget 的配色,和 Google Reader 分享阅读的配色基本一致,包括:black、blue、gray、green、khaki、pink、slate 和 none。默认为 green,如果输入的 style 不在上述配色中,则返回 none。none 为不带任何 style 的原始 HTML,适合希望自定义广播风格的 blogger。
最终效果如下图。想看看实物 demo 的话,可以点击这里。要说明的是,看得到漂亮的圆角效果的只有 Firefox 用户(wyt:这也是 Google Reader 的风格之一,:p),IE、Opera 和 Safari 用户对不住了。如果有任何的建议或意见,欢迎留言 :-)
2008年7月18日
有备无患:在 Django 上使用 reCAPTCHA 生成验证码
reCAPTCHA 是由 CAPTCHA 的原作者,也是人类计算学(Human Computation)的专家 Luis von Ahn 所设计一个免费的 anti-bot 服务,它在随机提取的单词上加上一些扭曲和可阻碍电脑自动识别的噪声,以利用人类远强于电脑的图形识别能力来检查屏幕前坐着的是人类,还是 bot 或自动脚本。因此,reCAPTCHA 经常被用于生成网站注册或者文章评论的验证码。
除此之外,reCAPTCHA 还有一项非常公益的设计。虽然 OCR (全称 Optical Character Recognitio,中译光学字元识别)早已被投入使用,但是对那些印刷模糊或分辨度不足的实体书籍而言,OCR 的识别率难说完美。而 reCAPTCHA 将这些上无法由电脑自动识别的单词扫描下来,以图片的形式存入数据库,并在网络上以验证码(CAPTCHA)的形式交由人类来破译,而破译的结果将被用于帮助世界上的实体书加快数字化的进程。
你可能会问,既然电脑无法识别这些单词,那么 reCAPTCHA 系统又怎么知道用户填写的是正确的验证码呢?reCAPTCHA 的网站上有详细的解释。
一个不能被 OCR 正确识别的新单词,总是会和另一个已知答案的单词一起提交给用户。接下来用户需要同时识别这两个单词。如果用户正确识别出其中已知答案的那个单词,系统即假设用户所识别出的未知答案的新单词也可能是正确的。如果其他的用户也识别出相同的答案,系统则逐步提高这种可能性。最终当这种可能性超过某个阈值时,系统就可以将其认作已知答案的单词了。
言归正传,reCAPTCHA 有丰富的 API,包括对 PHP、JAVA、Ruby,自然还有 Python 的支持。如果要在 Django 上使用 reCAPTCHA,我们可以从 PyPI 下载 reCAPTCHA 的 Python 客户端,或者直接用 easy_install 安装。
不知道为什么,虽然安装成功了,但是我不能导入 recaptcha 模块(wyt:import captcha 或 import recaptcha 都没有用,哪位如果顺利导入过的话,能不能分享一下你的经验呢?先谢了),所以我是把 egg 中的脚本直接复制到我的 Django 项目目录下了。
接下来,我们需要在 reCAPTCHA 注册并申请一对公钥/私钥,并将其保存在 settings.py 中。
然后,我们要为注册表单添加验证码了。在 Django Snippets 上,oggy 已经将 reCAPTCHA 抽象成一个 RecaptchaForm 类,所以我们只要让注册表单(Registration Form)继承这个类,就可以为注册表单添加验证码的功能了。由于 Django newform 默认是根据 field 的定义顺序来生成表单,所以在继承 RecaptchaForm 的时候,也应该把它放在最后继承。
除此之外,reCAPTCHA 还有一项非常公益的设计。虽然 OCR (全称 Optical Character Recognitio,中译光学字元识别)早已被投入使用,但是对那些印刷模糊或分辨度不足的实体书籍而言,OCR 的识别率难说完美。而 reCAPTCHA 将这些上无法由电脑自动识别的单词扫描下来,以图片的形式存入数据库,并在网络上以验证码(CAPTCHA)的形式交由人类来破译,而破译的结果将被用于帮助世界上的实体书加快数字化的进程。
你可能会问,既然电脑无法识别这些单词,那么 reCAPTCHA 系统又怎么知道用户填写的是正确的验证码呢?reCAPTCHA 的网站上有详细的解释。
一个不能被 OCR 正确识别的新单词,总是会和另一个已知答案的单词一起提交给用户。接下来用户需要同时识别这两个单词。如果用户正确识别出其中已知答案的那个单词,系统即假设用户所识别出的未知答案的新单词也可能是正确的。如果其他的用户也识别出相同的答案,系统则逐步提高这种可能性。最终当这种可能性超过某个阈值时,系统就可以将其认作已知答案的单词了。
言归正传,reCAPTCHA 有丰富的 API,包括对 PHP、JAVA、Ruby,自然还有 Python 的支持。如果要在 Django 上使用 reCAPTCHA,我们可以从 PyPI 下载 reCAPTCHA 的 Python 客户端,或者直接用 easy_install 安装。
# easy_install recaptcha-client
不知道为什么,虽然安装成功了,但是我不能导入 recaptcha 模块(wyt:import captcha 或 import recaptcha 都没有用,哪位如果顺利导入过的话,能不能分享一下你的经验呢?先谢了),所以我是把 egg 中的脚本直接复制到我的 Django 项目目录下了。
接下来,我们需要在 reCAPTCHA 注册并申请一对公钥/私钥,并将其保存在 settings.py 中。
settings.py
1 # reCAPTCHA keys
2 RECAPTCHA_PUBLIC_KEY = "6LdqgAIAAAUSHDmo4IIBmsjUsduAUMUoBDZc3J_T"
3 RECAPTCHA_PRIVATE_KEY = "6LdqgAIAAbjsJKLbj2KOjPO6D4isfJ_AzLSO_256"
然后,我们要为注册表单添加验证码了。在 Django Snippets 上,oggy 已经将 reCAPTCHA 抽象成一个 RecaptchaForm 类,所以我们只要让注册表单(Registration Form)继承这个类,就可以为注册表单添加验证码的功能了。由于 Django newform 默认是根据 field 的定义顺序来生成表单,所以在继承 RecaptchaForm 的时候,也应该把它放在最后继承。
recaptcha/forms.py
01 from django import newforms as forms
02 from django.newforms import ValidationError
03 from django.conf import settings
04 from recaptcha import captcha
05
06
07 class RecaptchaWidget(forms.Widget):
08 """ A Widget which "renders" the output of captcha.displayhtml """
09 def render(self, *args, **kwargs):
10 return captcha.displayhtml(settings.RECAPTCHA_PUBLIC_KEY)
11
12 class DummyWidget(forms.Widget):
13 """
14 A dummy Widget class for a placeholder input field which will
15 be created by captcha.displayhtml
16
17 """
18 # make sure that labels are not displayed either
19 is_hidden=True
20 def render(self, *args, **kwargs):
21 return ''
22
23 class RecaptchaForm(forms.Form):
24 """
25 A form class which uses reCAPTCHA for user validation.
26
27 If the captcha is not guessed correctly, a ValidationError is raised
28 for the appropriate field
29 """
30 recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
31 recaptcha_response_field = forms.CharField(widget=RecaptchaWidget, label='')
32
33 def __init__(self, request, *args, **kwargs):
34 super(RecaptchaForm, self).__init__(*args, **kwargs)
35 self._request = request
36
37 def clean_recaptcha_response_field(self):
38 if 'recaptcha_challenge_field' in self.cleaned_data:
39 self.validate_captcha()
40 return self.cleaned_data['recaptcha_response_field']
41
42 def clean_recaptcha_challenge_field(self):
43 if 'recaptcha_response_field' in self.cleaned_data:
44 self.validate_captcha()
45 return self.cleaned_data['recaptcha_challenge_field']
46
47 def validate_captcha(self):
48 rcf = self.cleaned_data['recaptcha_challenge_field']
49 rrf = self.cleaned_data['recaptcha_response_field']
50 ip_address = self._request.META['REMOTE_ADDR']
51 check = captcha.submit(rcf, rrf, settings.RECAPTCHA_PRIVATE_KEY, ip_address)
52 if not check.is_valid:
53 raise ValidationError('You have not entered the correct words')
user/forms.py
01 from myproject.recaptcha.forms import RecaptchaForm
02
03 class RegistrationForm(forms.Form):
04 """
05 Form for registering a new user account.
06
07 """
08
09 class RegistrationFormWithRecaptcha(RegistrationForm, RecaptchaForm):
10 """
11 Subclass of ``RegistrationForm`` and ``RecaptchaForm`` which can tell whether its user
12 is a human or a computer.
13
14 """
订阅:
帖子 (Atom)









