饭否定时提醒工具新增飞信提醒

缘由

为饭否提醒增加短信下行功能,一直是我的愿望。

子非鱼的博客话桑麻了解到,飞信API简单易用。又从张宴那里搜索到一些资料,觉得为饭否提醒工具增加飞信提醒,不是什么难事了。于是我就新写了一页代码,本地调试无误,于是上传到GAE。

使用方法

  1. 加我的飞信243596811好友;(无需验证)
  2. 在已经加@timers为好友的前提下,向@timers发送格式为”fx 12345678″的私信。如果您更改了飞信号,可再次发送相同格式、不同飞信号码的私信。
  3. 等待10分钟,或手动刷新本页面,您的号码就存到数据库中了。
  4. 向@timers 发送带f选项,这样就会收到飞信提醒。例如:
    @timers pdf 20:00 提醒我做某事。
    其中的pdf是指private私信、direct reply 直接@回复,以及fexin飞信,这三种提醒模式。
    至于为什么会形成PDF这样好记的模式,请看这条饭否消息
  5. 本文所描述的使用方法,是在饭否定时提醒工具一文的基础之上作的补充。如果您第一次了解到@timers,还请参看原文。

原理

  1. 新写了fx.py脚本,每10分钟执行一次;
  2. 收取@timers的饭否私信,解析格式为”fx 12345678″的私信,将对应发送者的饭否ID、发送时间、消息号码、飞信号码记录到数据库中。
  3. 下个10分钟再次执行本程序,先从数据库中找到上次解析的终点,然后只读取新的饭否私信,避免重复。
  4. 之所以每10分钟执行一次,是因为注册飞信的行为,每人只有一次,是个不频繁发生的事件。当然,如果您心急,也可以手动刷新本页面,这样能立即触发刷新功能,而没有其它副作用。(现在好像google的appspot又临时被绿Bra。使用翻坝软件刷新即可。)

请各位试用、提意见、建议。谢谢。

2009年7月3日14:13

使用AutoIt脚本为福昕阅读器增加批注快捷键

缘由

打开一本排版精良的PDF,随手批注,其阅读快感几乎可以与读纸质书媲美。在Windows下,我最常使用福昕阅读器来阅读PDF。除了尺寸精简、绿色环保,福昕最可称道的功能是具有完善的批注功能。美中不足的是,不能使用快捷键在批注功能之间切换,只能使用鼠标点来点去。如果能够左手使用快捷键选中批注工具类型,右手使用鼠标点选需要批注的文字行,效率应该提高数倍。这对于Foxit团队来说应该是投资少、见效快的提高用户满意率和回头率的小措施,可是一直不见有人做。求人不如求已,我决定自己实现。

 PhotobucketPhotobucket

实现途径有二:

  1. 对Foxit进行逆向工程。优点是对用户来说环保绿色,不需额外文件。缺点是较难实现。
  2. 使用AutoIt脚本模拟按键。正好相反,它需要额外的文件支持,但是容易实现。

我选择了方案2。

什么是AutoIT

最早是在feelinglucky的博客上知道autoit的。可以参见这篇文章:《使用 AutoIt 隐藏部分 QQGame 广告》。baidu百科上autoit的条目在这里,请自行点击。

简单定义:一个使用类似BASIC脚本语言的免费软件,它设计用于Windows GUI(图形用户界面)中进行自动化操作。它利用模拟键盘按键,鼠标移动和窗口/控件的组合来实现自动化任务。其官网在这里

李笑来老师也在使用AutoIt写脚本。可参见《我的一些必备工具》一文。

思路

  1. 定位到Foxit窗口。
  2. 注册快捷键。
  3. 为快捷键添加处理函数。
  4. 保持程序运行。

设定

因为在Foxit中,alt和ctrl键已有部分快捷键定义,所以这里使用shift作为快捷键辅助键。

功能

快捷键

注释

下划线 shift+u

shift+1
underline。为方便左手按键,增加shift+1(数字1)
文本高亮 shift+h

shift+2
highlight。增加shift+2。
下划波浪线 shift+w wave
删除线 shift+d delete
文本替换 shift+r replace
文本插入 shift+i

shift+3
insert。增加shift+3。
添加批注 shift+c comment
恢复到“文本选择” Esc 以免在批注状态下,所点击的文字都被格式化。

程序打开后自动运行,可以在托盘点击图标菜单的Exit来退出程序。

    下载

    下面是已经编译好的可执行文件以及au3源代码,保存在skydrive。如果下面的两个链接都失效,请留言告诉我。谢谢。
    页面链接 直接链接
2009年6月27日11:18

沁园春·北国封光

北国封光,
微软bing封,
谷歌血飘。
望长F城内外,
惟余莽莽,
大河上下,
顿失QQ。
五毛银蛇,
专家蜡象,
欲与春哥试比高。
须晴日,
看红装素裹,
分外妖娆(男)。

绿bra如此多娇,
将无数低俗都滤掉。
惜儿子母亲,
略输文采;
父亲女儿,
稍逊风骚。
一代天娇,
芙蓉姐姐,
为显S形努肥腰。
俱往矣,
数封流人物,
还看也·高。

2009年6月26日09:16

贴一篇旧文:《金鱼》

rex:本文原于2006-03-30发布于旧博客。近来LP大人买了两尾金鱼,我负责喂食换水,一来一去,鱼瘾爆发。于是一发不可收拾。这个周末颇买了几尾,观鱼至于忘饭。找出旧文,重贴于此。

回家时经常可以看见有人在卖金鱼。今天又遇见了。忍不住凑上前,临“渊”羡鱼。与前来打量鱼的其它人攀谈,听取他们的养鱼经验:多长时间换水、喂食,什么鱼怎么养。但始终没有下决心,最后还是空手而归。

对于金鱼,我还是很喜欢的。金鱼很好地符合了我养宠物的前三个原则:

  • 一.安静,不乱叫唤扰人扰己;
  • 二.干净,不随地便溺,无不良气味;
  • 三.美丽,富于观赏性。
  • 四.好养活,不娇贵。

看着金鱼在水里游弋,欣赏它们的泳姿和色彩,无疑会变得心平气和。金鱼的优雅常常让人忘记自己看的究竟是不是金鱼。它的鳍从半透明到完全透明,渐变得很自然。鳍与尾的自然褶皱就像舞女的百褶裙。我甚至爱屋及乌到喜欢看它硕大的肚子。原来我对这一点是颇为抱憾的,后来联想到直升飞机,当即豁然。重新审视,越看越好。我不是鱼,不知道鱼是否快乐。我只知道,自己看鱼这件事,让自己快乐。

第四个原则是难以符合了:好养活,不娇贵。

看来第四条是天生与前三条作对的:安静乖巧,一尘不染,美丽优雅,很明显的小资情调。这好像与坚韧的生命力是不协调的,就好像想让黛玉长命一样不现实。用这些自相矛盾的描述来限定一个集合,注定是个空集。

所以路过的时候我会很在意地欣赏,但无论如何下不了决心带回家。担心搬家太勤,担心照料无方而戕害生命,担心没有时间照料它们。我不想看到活生生的鱼死在我的照料之下。一位老鱼友倒是很豁达,“一两块钱的事。”果真这样想,确实能越过第四条原则。可惜我不能苟同。或许我是太爱鱼了,认为我所具备的条件不适合我心目中的鱼的生活条件,而舍不得将其请入家中,而且见不得其生老病死。或许只要是发自内心的喜爱,就能克服一切困难,来不遗余力地实现这一目标。或许我是理想主义者,容不得一丁点儿的瑕疵。但水至清则无鱼,爱鱼的我应该明白这个道理呀。或许我真的是叶公好龙,只是在概念上喜欢。

也曾想过养电子宠物。譬如养几条屏保鱼,闲来无事逗逗鱼,喂喂鱼,看它长大。同样是安静乖巧,一尘不染,美丽优雅,而且我确切知道这种鱼的本质无非是有序列的01码,所有的生老病死都只不过是预先设计好的随机事件。触发了何种事件,自然有相应的switch和case来进行处理。对于它的死,我可以毫无内疚感地不必黯然。就跟暴力摩托游戏一样,可以在虚拟世界中充满快感地撞行人、踹警察,而不必有任何负罪感。我在虚拟中可以无所顾忌,为什么在现实中这么顾虑重重?焉知所谓的现实不是真正的虚拟呢?何谓现实?何谓真正?因为参照系的不同。可以解释吗?

我不是不喜欢屏保鱼。唯一的不满足在于,游戏设计得不够逼真,太望梅止渴。我也知道,只要人类愿意,一定能实现让人身临其境的虚拟世界,能通过图灵测试的人工智能。再疯狂一点,想象一下自己是上帝养的小宠物之一,他老人家其实正在透过穹顶欣赏。人类模拟现实,上帝缔造人类。人类现有的科技只是还不够成熟罢了。或许等我老了,科技就能进步到这种地步了吧。

到了那个时候,或许我会拥有一套山间别墅。虚拟还是现实,这不是问题。我的后花园里,四季如春。小溪从泉眼涌出,蜿蜒曲折流向东方。两边是布局雅致的各色花木。中间是一个大水潭。因为有源头活水来,所以潭水清澈,泛着幽幽的绿色。里面有百十条鱼,都是一尺左右的金鲤。我摘一把它们爱吃的青菜,伸手池中,它们毫不谦让地你争我夺,将青菜咬得只剩菜茎。这时鱼匠温和地告诉我,“够了。再喂就该把它们撑坏了。”于是我心满意足地直起身,晾干手,慵懒地陷进藤椅里,就着温暖的夕阳,读一本意味深长的书。读到了一个难懂的地方,我随手摘下一片行将枯萎的菩提叶夹在书中当书签。合上书,闭目沉思,做梦到了遥不可及的地方……

2009年6月22日11:37

饭否定时提醒工具

深柳堂|饭否应用:定时提醒工具

是什么

使用GAE写了一款饭否提醒工具,@timers其作用是在规定的时间给饭否以提醒。当然,内容可以自定义,比如几点几分去偷人家的菜,等等,方便一直泡在饭否上的饭友使用全新的方式来提醒自己。当然了,如果饭否支持将@消息和私信发到手机上的话,这款应用会更有用。

怎么用

  1. @timers为好友。

  2. @timers发送格式如"@timers dp 10 任意提醒"消息。消息可以分4段:回复段,模式段,时间段,内容段。段之间以1个或多个半角空格隔开。

  3. 回复段:@timers 固定。之所以要加@timers为好友,是为了让@timers能收到您的消息。(如果您没有加@timers为好友,其实也可以点击@timers的任意一条消息点“回复”,@timers也能收到你的消息的。

  4. 模式段:即提醒方式,d为直接回复(direct reply)给您;p为私信回复(private)给您;可用模式为d或p或dp。
    2009.6.19更新:新增消息确认功能,@timers 收到消息后会及时回复一条“收到,我将于xxxx-xx-xx xx:xx提醒您。谢谢使用!”此功能默认开启。如果不需要此功能,只需要在模式段加入q即可。q为quiet之意。现在可用的模式有:dpq,其中dp选一或二,q可选可不选。
  5. 时间段:即何时提醒您。它支持如下方式的时间格式:
    • 正整数时间格式: @timers dp 20 告诉我做某事。#此处的20可以是任意的正整数。
    • 指定当天的几点几分:@timers dp 23:40 告诉我做某事。#程序会在当天的23:40向您发送提醒。(如果发送消息的当前时间是18:00,而发给@timers的提醒时间为11:00,则它收到消息后,1分钟内会提醒您。因为时间已经过了。这是一个特例。)
    • 指定当月某日的几点几分:@timers dp 20 23:40 告诉我做某事。#程序会在当月20日的23:40向您发送提醒。
    • 指定某月某日的几点几分:@timers dp 12-20 23:40 告诉我做某事。#程序会在12月20日的23:40向您发送提醒。
    • 指定具体的某年某月某日的几点几分:@timers dp 2010-12-20 23:40 告诉我做某事。#程序会在2010年12月20日的23:40向您发送提醒。
    • 需要说明的是,dp、数字、空格、中划线、冒号,全部都是半角字符。否则程序无法识别时间、提醒格式。
  6. 消息段。这段即是提醒正文部分。没有特殊要求,只要不超过饭否规定的字数即可。

    怎样,不算太复杂吧?

为什么

这里说一下该程序的实现思路。与上一篇《GAE牛刀小试:从twitter到9911同步消息》类似,此程序同样使用GAE,使用cron。

  • 程序每分钟执行一次。
  • 每次执行,先查询尚未提醒的任务。
  • 对于每一条未执行的任务,如果提醒时间小于或等于当前时间,则执行提醒(@或私信),同时将消息的状态设置为已经执行;如果大于,则略过。
  • 查询数据库中最新消息的ID。
  • 以此ID为参数,通过API读取@timers新收到的所有消息,保存在数据库中。

帮助我

如果您有任何建议、意见,请告诉我,我会修改程序,争取让它更好用。

源代码

公布源代码是个好习惯。比起自己的硬盘来,我相信网络空间更持久。虽然they say nothing lasts forever。 点击下面右侧的向下的▲查看源码。

?View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#latest edit: 2009.06.19 
 
#to ensure the utf8 encoding environment
import sys
default_encoding = 'utf-8'
if sys.getdefaultencoding() != default_encoding:
    reload(sys)
    sys.setdefaultencoding(default_encoding)
 
import urllib
import base64
import re
import time                                 #time zone convertion
 
from google.appengine.api import urlfetch
from google.appengine.ext import db
 
def time_from_0_to_8(timestr,timezone=8): 
 
    TIMEFORMAT="%a %b %d %X +0000 %Y" 
    #Sat Jan 03 23:08:54 +0000 2009 
    ISOTIMEFORMAT='%Y-%m-%d %X' 
    x=time.strptime(timestr, TIMEFORMAT) 
    m=time.mktime(x)+60*60*timezone 
    p=time.strftime(ISOTIMEFORMAT,time.localtime(m)) 
    return p
 
def timestr_add_mins(timestr,mins=0,future=""):
    '''
    receive time str, format like: 2009-06-10 13:00:33
    and integer mins
    return seconds;
    '''
    ISOTIMEFORMAT='%Y-%m-%d %X'
    x=time.strptime(timestr, ISOTIMEFORMAT)
    m=time.mktime(x)+60*mins
    p=time.strftime(ISOTIMEFORMAT,time.localtime(m))
    return p
 
def add_time(p_time="",send_time=""):
    ISOTIMEFORMAT='%Y-%m-%d %X'
 
    now=time.localtime()
    #print now.tm_year
    p=""
    match = re.search(
	    r"""(?:(?P<year>\d{4})-(?=\d+))?
	(?:(?:(?P<month>\d{1,2})-)?(?P<date>\d{1,2})\s+)?
	(?P<hour>\d{1,2}):(?P<mins>\d{1,2}) |
	(?P<min3>\d+)
	    """, p_time, re.VERBOSE)
    if match:
        min3 = match.group("min3")
        if min3:
            x=time.strptime(send_time, ISOTIMEFORMAT)
            x=time.mktime(x)+int(min3)*60
            x=time.strftime(ISOTIMEFORMAT,time.localtime(x))
            return x
        year = match.group("year")
        if year==None:
            year=now.tm_year
        month = match.group("month")
        date = match.group("date")
        if month==None :
            month=now.tm_mon
        if date==None:
            date=now.tm_mday
        hour=match.group("hour")
        mins=match.group("mins")
        return """%s-%02d-%02d %02d:%02d:00"""%(year,int(month),int(date),int(hour),int(mins))
 
 
class Msgs(db.Model):
    msg_id = db.StringProperty()
    time = time = db.StringProperty() #the excution time; format: 2009-01-31 23:59
    send_time = db.StringProperty()
    d = db.BooleanProperty(default=False)
    p = db.BooleanProperty(default=False)
    q = db.BooleanProperty(default=False)
    task= db.TextProperty()
    user_id = db.StringProperty()
    user_name = db.StringProperty()
    done = db.BooleanProperty(default=False)
    def print_all(self):
        print "ID:%s in %s mins was %s, DPD:%d%d%d, by %s(%s). Task:%s" % \
        (self.msg_id,self.time,self.send_time,self.d,self.p,\
        self.done,self.user_name,self.user_id,self.task)
 
def reply_msg(user,sn,msg,reply_id=""):
 
    auth=base64.b64encode(user+":"+sn)        
    auth='Basic '+auth   
    form_fields = {
        "status": msg,
        "in_reply_to_status_id":reply_id        
    }
    form_data = urllib.urlencode(form_fields)
 
    result = urlfetch.fetch(url="""http://api.fanfou.com/statuses/update.xml""",\
            payload=form_data,
            headers={'Authorization':auth},
            method=urlfetch.POST
            )
 
    if result.status_code != 200:
        print "Send Msg:%s Error!"%msg
 
 
def private_msg(user,sn,msg,to_id):
    auth=base64.b64encode(user+":"+sn)        
    auth='Basic '+auth   
    form_fields = {
        "text": msg,
        "user":to_id
    }
    form_data = urllib.urlencode(form_fields)
 
    result = urlfetch.fetch(url="""http://api.fanfou.com/direct_messages/new.xml""",\
            payload=form_data,
            headers={'Authorization':auth},
            method=urlfetch.POST
            )
 
    if result.status_code != 200:
        print "Send Private Msg:%s Error!"%msg
 
 
#get one page of to user's replies
def getPage(user,sn,since="",page=1):
    auth=base64.b64encode(user+":"+sn)
    auth='Basic '+auth   
    result = urlfetch.fetch(url="http://api.fanfou.com/statuses/replies.xml?since_id=%s&page=%d" % (since,int(page)),headers={'Authorization':auth})
 
    if result.status_code == 200:        
        return result.content
    else:
        return False
 
#parse replies
#this is a private function used only by parse.
def parseMsg(msg,send_time):
    #the msg is formated as: "@... d[pq] \d+ \w+"
    #return tuple (d,p,q,time,content), the first 3 are boolean type; time :integer;content:task
    match = re.search(r"@\S+\s+(?P<mode>\w+)\s+(?P<time>[-\d\s:]+)\s+(?P<todo>.*)", msg, re.DOTALL | re.VERBOSE)
    d=False
    p=False
    q=False
    time=0
    content=""
    if match:
        #print msg
        mode = match.group("mode")
        #print "mode: %s"%mode
        if mode.find("d")!=-1:
            d=True
        if mode.find("p")!=-1:
            p=True
        if mode.find("q")!=-1:
            q=True
        time=add_time(match.group("time"),send_time)
        content=match.group("todo")
        content=db.Text(content,"utf_8")
        return (d,p,q,time,content)
    else:
        return ()
 
 
def parse(content):
    #regex to parse the content 
    matches = re.findall(
    r"""(?sx)<created_at>([^<]+)</created_at>\s*        #0 msg created time
        <id>([^<]+)</id>\s*                                #1 msg id
        <text><!\[CDATA\[(.*?)\]\]></text>.*?            #2 msg content
        <id>([^<]+)</id>\s*                                #3 msg sender's id
        <name>([^<]+)</name>                            #4 msg sender's name
        """,content)
    numbers =0    
    for m in matches:      
        msg=Msgs()
        msg.msg_id=m[1]
        msg.send_time=time_from_0_to_8(m[0])
        msg.user_id=m[3]
        msg.user_name=m[4]
        x=parseMsg(m[2],msg.send_time) 
        if x:
            try:
                (msg.d,msg.p,msg.q,msg.time,msg.task)=x
                msg.put()
                numbers+=1
 
                if not msg.q:#q=True:
                    receipt='''@%s 收到,我将于%s提醒您。谢谢使用!'''%\
                        (msg.user_name,msg.time[:-3])                
                    reply_msg("timers","jianjian",receipt,msg.msg_id)
 
                #print msg.msg_id
            except:
                print "asign value error" 
                return numbers
    return numbers                
 
def get_latest_id():
    msg=db.GqlQuery("SELECT * FROM Msgs ORDER BY send_time DESC LIMIT 1")#send_time
    for m in msg:
        if m.msg_id!=None:
            return m.msg_id
        else:
            return ""
 
 
 
def display_all():
    msg=db.GqlQuery("SELECT * FROM Msgs ORDER BY send_time DESC")#send_time
    if not msg.count():
        return 
    for m in msg:
        m.print_all()
 
def get_records():
    msg=db.GqlQuery("SELECT * FROM Msgs")#send_time
    return msg.count()
 
#condition= WHERE....
def delete_all(condition=""):
    msg=db.GqlQuery("SELECT * FROM Msgs %s"%condition)#send_time
    for m in msg:
        db.delete(m)
 
 
 
def count_down(user,sn):
    msg=db.GqlQuery("SELECT * FROM Msgs WHERE done= :1",False)
    index=0
    now=time.gmtime()
    now=time.mktime(now)+3600*8
    ISOTIMEFORMAT='%Y-%m-%d %X'
    x=time.strftime(ISOTIMEFORMAT,time.gmtime(now))
 
    for m in msg:
        index+=1
        #m.time-=1
        print "task id: %s, exe time:%s vs now:%s; result: %s"%(m.msg_id,m.time,x,m.time<=x)
        if (m.time<=x):
            #m.done=True
            #d,p
            if m.d:                
                reply_msg(user,sn,'''@%s %s'''%(m.user_name,m.task),m.msg_id)
            if m.p:                
                private_msg(user,sn,m.task,m.user_id)
 
            m.done=True
            m.put()
#        m.print_all()
    print "there are %d tasks to do."%index
    #display_all()
 
 
def mainloop(user,sn,latest): 
    page=1
    while(1):
        msg=getPage(user,sn,latest,page)
        #print msg
        if msg:
            num=parse(msg)
            page+=1
            #page=page+1
            if num==0:
                break    
 
 
 
print ""
 
user="timers"
sn="jianjian"
 
#delete_all()
 
#display_all()
#rutine check
count_down(user,sn)
 
latest=get_latest_id()
if latest==None:
    latest=""
 
mainloop(user,sn,latest)
2009年6月19日00:50