十月 282011
 

這篇文章說明如何用 Plurk API 2.0 自己寫一個噗浪機器人

(本文同步發表於 噗浪官方部落格)

噗浪大部分的機器人仍然使用 Plurk 1.0 撰寫,甚至有一部分機器人使用的是非官方的 API,這篇文章將簡介如何使用 Plurk API 2.0 OAuth 的方式開發機器人程式,同時,這個範例也使用 Plurk Realtime API (Comet Push) 的方式來追蹤時間軸,避免給伺服器帶來過多的負擔

由於 Plurk API 2.0 使用 OAuth 方式,所以啟動機器人之前,您必須先獲得以下四個參數

  • App Key
  • App Secret
  • Access Token
  • Access Token Secret

獲得 App Key 及 App Secret 的方式很簡單,首先先註冊一個新的噗浪帳號用來跑這個機器人,然後登入新帳號後,開啟以下這個連結:

http://www.plurk.com/PlurkApp/

按下「註冊新的應用服務」,填寫關於你的程式的資料,其中 OAuth callback 保持空白即可

註冊完畢後就可以看到你的應用程式列表

然後按下「編輯」這個按鈕,就可以看到以下畫面:

這邊我們就可以得到 App Key 以及 App Secret 了

接下來要進行 OAuth 的授權驗證來取得 Access Token 及 Access Token Secret

按下「測試工具」來開啟 OAuth 的 Test Console

首先按下「Get Request Token」來取得暫時的 Request Token,
接下來按下「Open Authorization URL」來開啟授權頁面:

按下「是,我要授權」後,會得到一個認證碼:

把這個六位數的認證碼記下來,然後回到 Test Console,
按下「Get Access Token」,這時會提示您輸入認證碼

把您剛剛記下來的數字填進去,按下「確定」後,
就可以得到永久有效的 Access Token 及 Access Token Secret 了

然後你就可以開始寫程式了,下面是一個用 Python 寫的噗浪機器人,
把其中 APP_KEY, APP_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET 置換掉即可

這個程式需要使用到 plurk-oauth 這個 Python library,
請把 plurk_oauth/ 這個目錄下的檔案下載回來跟你的程式放在一起就可以了

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
#!/usr/bin/python
# -*- coding:utf-8 -*-
 
import re
import json
import urllib2
 
from PlurkAPI import PlurkAPI
 
plurk = PlurkAPI('APP_KEY', 'APP_SECRET')
plurk.authorize('ACCEESS_TOKEN', 'ACCESS_TOKEN_SECRET')
 
comet = plurk.callAPI('/APP/Realtime/getUserChannel')
comet_channel = comet.get('comet_server') + "&new_offset=%d"
jsonp_re = re.compile('CometChannel.scriptCallback\((.+)\);\s*');
new_offset = -1
while True:
    plurk.callAPI('/APP/Alerts/addAllAsFriends')
    req = urllib2.urlopen(comet_channel % new_offset, timeout=80)
    rawdata = req.read()
    match = jsonp_re.match(rawdata)
    if match:
        rawdata = match.group(1)
    data = json.loads(rawdata)
    new_offset = data.get('new_offset', -1)
    msgs = data.get('data')
    if not msgs:
        continue
    for msg in msgs:
        if msg.get('type') == 'new_plurk':
            pid = msg.get('plurk_id')
            content = msg.get('content_raw')
            if content.find("hello") != -1:
                plurk.callAPI('/APP/Responses/responseAdd',
                              {'plurk_id': pid,
                               'content': 'world',
                               'qualifier': ':' })

執行方式:

# python my-robot.py

這個範例程式作的事情很簡單,就是一個無窮迴圈,首先每次都會先接受所有成為朋友的請求,然後看看有沒有新的噗,如果有新的噗,而且內容有 ‘hello’ 字串的話,就會自動回覆一個 ‘world’ 字串

提醒大家,噗浪並不反對機器人的存在,但使用這個範例程式請注意以下幾點:

  1. 建議使用新的帳號,不要用原有的帳號
  2. 機器人請勿主動去加網友為朋友
  3. 機器人請勿去關注(追蹤)任何其他網友
  4. 請勿自動回覆未成為機器人的朋友所發的噗
  5. 請勿張貼廣告訊息
  6. 請注意回覆頻率,以不過度干擾使用者的方式為原則
 Posted by at 17:04
三月 022009
 

最近已經快滿三年的 Thinkpad X60 跑得越來越慢,又常常當機,所以找個比較有空的時間就把它重灌 Windows XP Professional,順便作一下筆記

灌好作業系統後,可以先把 ThinkVantage System Update 裝起來,這樣 Thinkpad 相關的驅動程式及軟體都可以直接從這邊更新回來。對了,記得不要裝 Client Security Solution 因為裝了真的是自找麻煩! 另外 System Migration Assistant 和 Rescue and Recovery 我也沒裝..

接下來就是安裝應用程式了,儘可能都以免費的軟體為主:

PicPick 是一個很強大的截圖軟體
XnView 看圖及簡單的圖形處理
Paint.NET 是一個短小精幹的繪圖軟體
FileZilla 支援 FTP 及 SFTP 的檔案傳輸軟體
7-Zip 檔案解壓縮工具
K-Lite Codec Pack 播放各種影音檔案
Adobe Reader 閱讀 PDF 文件

Alcohol 52% Free Edition 虛擬光碟 (裝免費版的即可, 可選擇不裝贊助軟體)
Avira AntiVir 掃毒軟體
Unlocker 強制解鎖
RocketDock 像蘋果的工具列
新酷音輸入法 (最新版: 0.3.4.8)

其他網路工具包含:

Google 提供的工具:

另外微軟的 PowerToys for Windows XP 也有幾個不錯的小工具:

  • CmdHere
  • PowerToy Calculator
  • Tweak UI

還有之前微軟有提供一個 Microsoft Chinese Date and Time,是一個我很愛用的農民曆及世界時間工具,但現在微軟似乎把連結拿掉了,不過網路上搜尋一下就找到了

 Posted by at 22:53
十一月 112008
 

This is a simple Mantis Mail Gateway for mail-in tickets.
It can be used for alert management for monitoring system.

For installation, add the line below to /etc/aliases:

bug: “|/usr/local/bin/mantis-mail-gateway.pl   PROJECT   REPORTER”

and then run newaliases.

The incoming mail will be posted to a Mantis project named ‘PROJECT’ on behave of user ‘REPORTER’. The mail subject will become the bug summary and the mail content will become the bug description.

Note that this script does not understand MIME multipart encoding and may have problems on mail contents besides plain-ASCII encoding.

You are welcome to add more features.

mantis-mail-gateway.pl: (can be downloaded here)

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
#!/usr/bin/perl
 
# mantis-mail-gateway.pl
 
# Mantis Mail Gateway
# Shen Cheng-Da (cdsheen AT gmail.com)
# require DBI to connect MySQL database
# http://blog.urdada.net/2008/11/11/95/
 
use DBI;
use POSIX qw(strftime);
 
my $db_host    = 'localhost';
my $db_name    = 'mantis';
my $db_user    = 'monitor';
my $db_pass    = 'monitorpass';
 
my $debug      = 0;
 
my $db = "dbi:mysql:dbname=${db_name};host=${db_host}";
 
die "Usage: $0 [project-name] [reporter]\n" unless @ARGV > 1;
 
my $project_name  = $ARGV[0];
my $reporter_name = $ARGV[1];
 
my $dbh;
my $sql;
my $sth;
my $project  = -1;
my $reporter = -1;
 
$dbh = DBI->connect($db, $db_user, $db_pass)
        || die 'ERROR: '.$dbh->errstr;
 
$sql = "SELECT id,name FROM mantis_project_table
        WHERE name LIKE '$project_name'";
 
$sth = $dbh->prepare($sql) || die 'ERROR: '.$dbh->errstr;
$sth->execute() || die 'ERROR: '.$dbh->errstr;
while( @data = $sth->fetchrow_array() ) {
        $project = $data[0];
}
$sth->finish;
 
die "ERROR: project \`$project_name' does not exist\n" unless $project > 0;
 
print "project: $project_name ($project)\n" if $debug;
 
$sql = "SELECT id,username FROM mantis_user_table
        WHERE username = '$reporter_name'";
 
$sth = $dbh->prepare($sql) || die 'ERROR: '.$dbh->errstr;
$sth->execute() || die 'ERROR: '.$dbh->errstr;
while( @data = $sth->fetchrow_array() ) {
        $reporter = $data[0];
}
$sth->finish;
 
die "ERROR: user \`$reporter_name' does not exist\n" unless $reporter > 0;
 
print "reporter: $reporter_name ($reporter)\n" if $debug;
 
my $subject = '';
my $content = '';
 
while(<STDIN>) {
        s/\s+$//;
        last if $_ eq '';
        $subject = $1 if /^Subject: (.+)$/;
}
while(<STDIN>) {
        $content .= $_;
}
 
$sql = 'INSERT INTO mantis_bug_text_table (description) VALUES (?)';
 
$sth = $dbh->prepare($sql) || die 'ERROR: '.$dbh->errstr;
$sth->execute($content) || die 'ERROR: '.$dbh->errstr;
$sth->finish;
 
my $textid = $dbh->{ q{mysql_insertid} };
 
print "bug text id: $textid\n" if $debug;
 
$sql = 'INSERT INTO mantis_bug_table
                ( project_id, reporter_id,
                  date_submitted, last_updated,
                  bug_text_id, summary )
        VALUES (?,?,?,?,?,?)';
 
my $now = strftime('%Y-%m-%d %H:%M:%S', localtime(time));
 
$sth = $dbh->prepare($sql) || die 'ERROR: '.$dbh->errstr;
$sth->execute($project, $reporter, $now, $now, $textid, $subject)
        || die 'ERROR: '.$dbh->errstr;
$sth->finish;
 
$dbh->disconnect;
 Posted by at 23:09
七月 302008
 

很多人寫完程式要 commit 的時候會偷懶不寫 log,導致有時候要追問題時,
很難得知到底別人改了什麼,這時候裝個 pre-commit hook 還是蠻有用的

下面這個 pre-commit hook 只是很簡單的不允許空白或不含字母的 commit log,
如果需要的話可以很容易擴充加上更多的判斷.. 記得要 chmod 755 喔

[Subversion pre-commit hook to reject commit with empty log]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/perl
 
# pre-commit hook to reject commit with empty log
# remember to chmod 755 on this file
 
die "Usage: $0 [REPOS] [TXN]\n" unless @ARGV > 1;
 
$REPOS=$ARGV[0];
$TXN=$ARGV[1];
 
$svnlook = '/usr/local/bin/svnlook';
 
chomp($author=`$svnlook author -t $TXN $REPOS`);
chomp($log=`$svnlook log -t $TXN $REPOS`);
 
if( $log eq '' || $log =~ /^\W+$/ ) {
    die "\nHello, $author. Empty commit log is not permitted!\n";
}
 
exit(0);

除此之外,對於 pre-commit hook,我還加上了檢查不允許跨 branch 的 commit,
因為一旦有了跨 branch 的 commit,會使得未來需要作 merge 的時候遇到許多麻煩

例如常用的 repository 結構如下:

/trunk/
/branches/helloworld-1.0/
/branches/helloworld-1.1/

要防止使用者同時對兩個以上的 branch (包含 main trunk) 作 commit,
可以把下面這一段程式再加進去 pre-commit 的 hook 中

[Subversion pre-commit hook to reject cross-branch commit]

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
#!/usr/bin/perl
 
# pre-commit hook to reject empty commit log and cross-branch commit
# remember to chmod 755 on this file
 
die "Usage: $0 [REPOS] [TXN]\n" unless @ARGV > 1;
 
$REPOS=$ARGV[0];
$TXN=$ARGV[1];
 
$svnlook = '/usr/local/bin/svnlook';
 
chomp($author=`$svnlook author -t $TXN $REPOS`);
chomp($log=`$svnlook log -t $TXN $REPOS`);
 
if( $log eq '' || $log =~ /^\W+$/ ) {
    die "\nHello, $author. Empty commit log is not permitted!\n";
}
 
open(DIRS,"$svnlook dirs-changed -t $TXN $REPOS|");
while(<DIRS>) {
    if( /^(trunk)\// ) {
        $branches{$1}++;
    }
    elsif( /^branches\/([^\/]+)/ ) {
         $branches{$1}++;
    }
}
close(DIRS);
 
$c = %branches;
 
if( $c > 1 ) {
    die "\nHello, $author. You can't commit to $c branches at the same time!\n";
}
 
exit(0);
 Posted by at 00:54
四月 182008
 

Size of long integer may be different between 64-bit systems (一個長整數各自表述)

不知道是不是我太落伍了…

我一直以為 C/C++ 下面 short, long, long long 三種資料型態都固定是 2, 4, 8 個 bytes 大小。只有 int 這個資料型態會因為 16-bit/32-bit 系統的不同而變成 2 或 4 bytes 的大小,所以理所當然 int 在 64-bit 的電腦也應該會變成 8 bytes (64-bit) 的大小囉 ?!

在整理前一篇文章《Bypass the 2GB file size limit on 32-bit Linux》的時候,讓我驚覺在 64-bit 的系統下,long 的長度也是各自表述的!

首先,int 的大小即使到了 64-bit 的機器上,大部分的系統仍然使用 4 bytes 的大小而已,這主要是為了避免程式從 32-bit 系統轉換到 64-bit 系統需要修改太多地方

再來,請參考 Wikipedia: 64-bit data models 的說明

絕大多數的 UNIX 系統在 64-bit 下面採用 LP64 這種 data model,這時候 long 就不再是固定為 4 bytes 大小,而是變成 8 bytes 的大小了!

然而,Win64 卻不是使用 LP64,而是採用 LLP64 這個 data model,這時候 long 的大小仍然還是 4 bytes

Many 64-bit compilers today use the LP64 model (including Solaris, AIX, HP, Linux, Mac OS X, and IBM z/OS native compilers). Microsoft’s VC++ compiler uses the LLP64 model.

兩種 data model 的最大差異點就是 long 這個資料型態的大小,LP64 是 64-bit,而 LLP64 則是 32-bit

LLP64 data model 基本上可以說跟 32-bit 的系統一樣,唯一差別只有位址(pointer)改成了 64-bit 而已。資料物件(class, structure) 等如果沒有包含 pointer 的成員的話,整個物件的大小是與 32-bit 系統一樣的!

而 LP64 則是除了位址(pointer)改成 64-bit 之外,long 的大小也變成了 64-bit 大小。所以在 UNIX 下面,要把 32-bit 程式 porting 到 64-bit 可能要比 Windows 多花費多一點功夫。

所以呢,我們觀察到兩個問題影響著程式的相容性

  1. 在 UNIX 下面,long 的大小在 32-bit 與 64-bit 的系統下是不一樣的
  2. 同樣是 64-bit 系統,UNIX 與 Windows 對於 long 的大小看法是不一致的

為了使程式在 32-bit 與 64-bit 之間以及 UNIX 與 Windows 之間的相容性提昇,改用固定長度的資料型態是寫程式的一個好習慣

在 UNIX 下面,我們可以改用 stdint.h 這個 header file 中對於資料型態的定義:

int8_t     8-bit signed interger
int16_t    16-bit signed interger
int32_t    32-bit signed interger
int64_t    64-bit signed interger
uint8_t    8-bit unsigned interger
uint16_t   16-bit unsigned interger
uint32_t   32-bit unsigned interger
uint64_t   64-bit unsigned interger

在 Windows 下面,則改用下面這些整數固定大小的資料型態

INT8       8-bit signed integer
INT16      16-bit signed integer
INT32      32-bit signed integer
INT64      64-bit signed integer
UINT8      8-bit unsigned integer
UINT16     16-bit unsigned integer
UINT32     32-bit unsigned integer
UINT64     64-bit unsigned integer

絕對不要再使用 int 和 long 了!

尤其是寫網路程式時,很可能 client 是 Windows 而 server 是 UNIX,然後又有 32-bit 及 64-bit 系統混在裡面,一不小心就發生不相容的問題了…

當然,在 64-bit 的系統下寫程式,要考慮的絕對不只上面這些基本的資料型態。除了 pointer 的大小變成 64-bit 外,許多系統內建函式會用到的 size_t 及 off_t 的大小也變成 64-bit 了…. 寫程式時若有用到這些資料型態,需特別注意,尤其是 casting 時,千萬不要用 32-bit 的整數去裝這些資料,免得造成不可預期的結果!

最後提供一個小程式讓你得知你的系統主要資料型態的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <sys/types.h>
int main()
{
        printf("sizeof(short)     = %d\n", sizeof(short));
        printf("sizeof(int)       = %d\n", sizeof(int));
        printf("sizeof(long)      = %d\n", sizeof(long));
        printf("sizeof(long long) = %d\n\n", sizeof(long long));
 
        printf("sizeof(size_t)    = %d\n", sizeof(size_t));
        printf("sizeof(off_t)     = %d\n", sizeof(off_t));
        printf("sizeof(void *)    = %d\n", sizeof(void *));
}

參考資料:

  1. Wikipedia: 64-bit data models
  2. 64-Bit Programming Models: Why LP64?
  3. Introduction to Win32/Win64
  4. Porting 32-bit Applications to the Itanium® Architecture
  5. Preparing Code for the IA-64 Architecture (PDF)
 Posted by at 22:06
四月 182008
 

Bypass the 2GB file size limit on 32-bit Linux (在 Linux 上面突破 2GB 的檔案大小限制)

在 32 位元的 Linux 上面寫超過 2GB 的檔案會發生錯誤,甚至導致程式終止執行

這是因為 Linux 的系統內部處理檔案時用的指標定義為 long,而 long 在 32 位元的系統上的大小為 32 位元,因此最大只能支援 2^31-1 = 2,147,483,647 bytes 等於是 2GB 扣掉 1 byte 的檔案大小

64 位元的系統 (例如 AMD64IA64) 則因為 long 定義成 64 位元,所以不會有問題..

#  if __WORDSIZE == 64
typedef long int int64_t;
# endif

不過在 FreeBSD 上面,即使是 32 位元的系統,也不會有 2GB 檔案大小的限制,這是因為 FreeBSD 內部處理檔案時,本來就是使用 64 位元的數字當作指標,所以不會有問題

因此在 32 位元的 Linux 上面,程式需要作一些額外處理才能正確寫超過 2GB 的檔案

我們先寫一個小程式來測試一下 (large.c)

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
void sig_xfsz(int sig)
{
        printf("ERROR: SIGXFSZ (%d) signal received!\n", sig);
}
int main()
{
        int     i, fd;
        char    dummy[4096];
 
        signal( SIGXFSZ, sig_xfsz );
 
        unlink("large.log");
        fd = open("large.log", O_CREAT|O_WRONLY, 0644 );
 
        bzero( dummy, 4096 );
        /* 2GB = 4KB x 524288 */
        for( i = 0 ; i < 524287 ; i++ )
                write( fd, dummy, 4096 );
        write( fd, dummy, 4095 );
        printf("large.log: 2147483647 bytes\n");
 
        if( write( fd, dummy, 1 ) < 0 )
                printf("ERROR: %s [errno:%d]\n",strerror(errno),errno);
        else
                printf("large.log: 2147483648 bytes\n");
 
        close(fd);
        exit(0);
}

在 32 位元的 Linux 下面,以上程式編譯後若沒有特殊處理,執行結果如下:

# gcc -o large32 large.c
# ./large32
large.log: 2147483647 bytes
ERROR: SIGXFSZ (25) signal received!
ERROR: File too large [errno:27]

在寫第 2147483648 byte 的時候,程式會收到 signal SIGXFSZ,同時 write() 會回傳 -1 錯誤,errno 則為 27 (File too large)。更甚者,如果程式沒有像上面一樣去處理 SIGXFSZ 的話,內定的 signal handler 甚至會造成程式停止執行並產生 core dump

接下來,我們在編譯同一個程式的時候加入 -D_FILE_OFFSET_BITS=64 再試看看:

# gcc -D_FILE_OFFSET_BITS=64 -o large64 large.c
# ./large64
large.log: 2147483647 bytes
large.log: 2147483648 bytes

果然順利突破 2GB 的限制了!

而同樣的程式在 32 位元的 FreeBSD 下面,不論有沒有加這個定義,跑起來都是正確的

不過處理這些大檔案的時候,除了編譯程式時的參數不同外,有些函數的使用上也要作一些調整,例如 fseek() 與 ftell() 這兩個原本使用到 long integer 當作 offset 的函數:

1
2
int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);

只要系統是 32 位元,即使是在 FreeBSD 下面,都需要改為使用 off_t 的版本:

1
2
int fseeko(FILE *stream, off_t offset, int whence);
off_t ftello(FILE *stream);

在 Linux 下面,如果 _FILE_OFFSET_BITS 定義為 64,則 off_t 這個型態會自動轉成 64 位元的大小(在 FreeBSD 上面,off_t 本來就是 64 位元的大小)

每種系統支援大於 2GB 的檔案讀寫所需要的編譯選項都會有一些差異,即使是同樣是 Linux 也會因為 32 位元或 64 位元而有不同。有一個簡單的方法可以判斷,就是利用 glibc 提供的 getconf 來取得編譯(compile)以及連結(linking)時所需的參數:

# getconf LFS_CFLAGS
-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
# getconf LFS_LDFLAGS
 
#

上面是在 32 位元的 Redhat Linux 上面跑出來的結果,代表的是在這個系統上,若要讓程式支援 2GB 的檔案讀寫,編譯(compile)時需要加上 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 這兩個參數,連結(linking)時則不用加任何參數

參考資料:

 Posted by at 02:18