對于有些喜歡修改源代碼的人,這里是一個指南的開端。我想到什么說什么,第一篇說說如何修改源代碼讓你用TM登錄。
你不想使用QQ登錄,想使用TM登錄么?通過修改源代碼,是很簡單的。
其實TM和QQ的協(xié)議是一樣的,雖然有些功能TM有QQ沒有,但是基本上都不是服務(wù)器的限制。舉個例子說,TM里面有個“隱身對某人可見”功能,命令是0x0024,雖然QQ里面沒有這個功能,但是你如果真的通過QQ發(fā)送這個命令,服務(wù)器目前是不會拒絕的。
PS: 所以我一直覺得TM不過是界面改改罷了,里面的東西,照舊。
我們看一下QQ.java, 有這么一個常量
public static final char QQ_CLIENT = QQ_CLIENT_0E1B;
你把他改成
public static final char QQ_CLIENT = 0x0F0A;
然后你再運行,LumaQQ就成了LumaTM了
基本上啥也沒干,是不是。QQ_CLIENT是版本標(biāo)識,0x0F0A表示的是TM 2006新春版。TM和QQ基本上就這點差別。也許夸張了點。
你怎么知道修改了這個常量就是TM了?假設(shè)你有兩個號碼A和B,你用騰訊TM把A設(shè)成隱身對B可見。然后嘗試用LumaQQ登錄A,看看修改前后在B的那邊有沒有區(qū)別,就知道了。
但是LumaTM還是QQ界面?這...,這不指南才開始嗎?哪有空把界面怎么改都一次說全了。
你解析了一個未知的包,不知道怎么添加到JQL么?如果你有這樣的疑問,我給你一些指南
一般來說,你要完成以下任務(wù):
1. 添加相應(yīng)的命令常量到QQ.java
2. 根據(jù)協(xié)議族不同,繼承不同的基類。以基本協(xié)議族為例,繼承BasicOutPacket,創(chuàng)建一個輸出包類。再繼承BasicInPacket,創(chuàng)建一個輸入包類。你可以看看現(xiàn)有的包,抄一個過來,然后修改putBody或者parseBody就可以了。
3. 根據(jù)協(xié)議族不同,找到不同的parser,以基本協(xié)議族為例,看看BasicParser.java,他負(fù)責(zé)解析所有基本協(xié)議族的包,把你的包加到parseIncoming和parseOutcoming的大switch里面吧
4. 你的包是否觸發(fā)一些事件?如果是,修改QQEvent.java,添加你自己的事件常量。千萬不要和其他的事件常量沖突了,我在里面標(biāo)明了下一個可用的事件ID。
5. 根據(jù)協(xié)議族不同,找到不同的包事件處理器。以基本協(xié)議族為例,看看BasicFamilyProcessor.java,那個packetArrived的大switch里面。加上你自己的代碼,觸發(fā)你的事件吧。
6. 找到QQClient.java,添加一個方便的方法可以發(fā)送你的包,不加其實沒事,但是加了好些,上層調(diào)用起來方便。
還好步驟不算很多,Just do it!
想把圖標(biāo)都換換?這個簡單
edu.tsinghua.lumaqq.resource.icon,程序的圖標(biāo)
edu.tsinghua.lumaqq.resource.head,用戶頭像
edu.tsinghua.lumaqq.resource.smallhead,用戶小頭像
edu.tsinghua.lumaqq.resource.clusterhead,群頭像
edu.tsinghua.lumaqq.resource.face,缺省表情
edu.tsinghua.lumaqq.resource.image,一些比較大的圖片,logo,背景什么的
看哪個不順眼,換吧。但是文件名和圖片尺寸不要亂變。
你嫌LumaQQ的機器人實現(xiàn)太簡單么?是簡單了點,我只處理了普通消息,你想讓機器人也回復(fù)群消息,也回復(fù)手機短信,等等。
不麻煩,稍微改改QQClient.java吧,找找qqEvent方法,有這么一塊
case QQEvent.QQ_RECEIVE_NORMAL_IM:
processNormalIM(e);
break;
processNormalIM很簡單:
// 先返回確認(rèn)
processReceiveIM(e);
// 得到消息包
ReceiveIMPacket packet = (ReceiveIMPacket)e.getSource();
doRobot(packet);
so,把下面加到qqEvent里面
case QQEvent.QQ_RECEIVE_CLUSTER_IM:
processClusterIM(e);
break;
再加一個processClusterIM方法:
// 先返回確認(rèn)
processReceiveIM(e);
// 得到消息包
ReceiveIMPacket packet = (ReceiveIMPacket)e.getSource();
doRobot(packet);
這樣就可以讓robot也收到群消息了,剩下的事,是你的Robot實現(xiàn)的事。doRobot方法里面只發(fā)送普通消息,所以要改一下,具體就不說了
想改變整個程序的樣式么? 工作有點多
我沒有實現(xiàn)什么皮膚,只是簡單的做了一些程序邊框的修飾,主要的工作都是在BorderStyler.java里面完成的。
BorderStyler
的工作就是你傳一個shell進去,它給shell加上邊框,標(biāo)題條,還有最大最小之類的按鈕,當(dāng)然還要添加一些事件監(jiān)聽器。你要改也可以,不過我覺得基
本上BorderStyler沒有太多需要改的。你可以換換顏色,換換那些按鈕的圖標(biāo),這樣界面風(fēng)格就可以變了。
大部分顏色都定義在Colors.java里面,你可以改。
最大化最小化按鈕的圖片在指南(2)里面有介紹,還有一些背景圖,也可以改了以便和按鈕的色調(diào)配合
光改了邊框恐怕還不行,其他控件的顏色方案也得改改吧,比如好友列表的顏色方案。
如果有些控件用到的顏色沒有在Colors里面定義,那你得稍微改改控件的代碼。
記得我在指南(2)里面說過的,改圖片的時候不要亂改圖片大小。
01-17版本改寫了聊天記錄導(dǎo)出的架構(gòu),稍微靈活了一些,不過實現(xiàn)的也不是太好,意思一下就是了啦
我缺省做了兩個簡單的聊天記錄導(dǎo)出模板,文本的和HTML的,如果你想添加自己的模板,或者修改現(xiàn)有的模板,可以看看LumaQQ_template工程,這是一個剛創(chuàng)建的新工程,為了正確的使用這個工程,你需要裝EMF,我用了JET來做模板
template下面就是jet的模板定義,這個工程文件不多,除了RecordExporterFactory是手寫的,其他的代碼都是從jet模板生成的。
RecordExporterFactory很簡單,隨便看看也就明白了,如果你想加個自己的模板上去,照著樣子加點代碼,然后寫一下自己的模板文件,然后執(zhí)行build.xml的jar目標(biāo),然后把jar拷貝到LumaQQ_2005的lib里面,然后沒有了
實現(xiàn)的不是太強,夠用就行。對JET感興趣的,安裝完EMF之后,eclipse的幫助里面會有兩篇JET的文章。Eclipse主頁也有。
Windows下面沒有熱鍵,等著你們來實現(xiàn)
其實Windows下面實現(xiàn)熱鍵很簡單,比linux簡單。有人愿意寫就寫吧,這個我接受你的contribution,并且會加你到About對話框的,當(dāng)然你實現(xiàn)的要有質(zhì)量。
自然了,這一塊不得不用JNI,接口是很簡單的,就三個方法,具體去看看edu.tsinghua.lumaqq.hotkey.linux.edu_tsinghua_lumaqq_hotkey_KeyBinder.h文件,三個方法的原型在那里。
簡單說一下這三個方法:
init: 初始化
bind: 綁定一個熱鍵,熱鍵用字符串的形式描述,比如Z,綁定成功返回true,否則返回false
unbind: 取消綁定,參數(shù)和bind一樣,沒有返回值。
接口很簡單,能用就行。怎么實現(xiàn)熱鍵我是指導(dǎo)不了你了,反正Windows就是鉤子嘛
LumaQQ有個Debugger,還是很好玩的吧?你有沒有想自己寫一個debugger UI,或者讓LumaQQ的debugger功能更強?
Debug的支持是嵌入在JQL協(xié)議層的,而界面怎么實現(xiàn)是留給你去完成的。所以這部分代碼分成兩個部分:
1. jql_protocol: edu.tsinghua.lumaqq.qq.debug包: Debug的核心支持
2. LumaQQ: edu.tsinghua.lumaqq.ui.debug包: LumaQQ自帶的debugger UI實現(xiàn)
看起來很牛B其實debug功能很有限,也就是讓你看看收發(fā)了些什么包而已吧。如果你還想要監(jiān)控核心層其他的活動,那這個就要你去擴展了,不過我覺得能看看包也就行了,其他的功能,因為我自己沒這個需求,自然就沒做。
Debug
在核心層的支持是可以通過DebugSwitch.java來切換的,唯一增加的開銷是一個if檢查,所以這點我還是比較滿意的。核心層在開通了
Debug功能后,會把所有的收到和發(fā)送的包都傳給你,當(dāng)然你必須要添加個IDebugListener才能收到這些包。現(xiàn)在找到
edu.tsinghua.lumaqq.qq包,查看Packet.java和OutPacket.java,你可以發(fā)現(xiàn)一些Debug功能的相關(guān)代
碼,這是為什么所有的包都可以被監(jiān)視的原因。
至于UI的實現(xiàn),那可以很靈活,具體的就看主頁文檔,看看缺省的調(diào)試器是怎么做的。
對于寫界面來說,很大的功夫都花在了寫組件上。為了界面盡量好看點,我寫了一些自繪的組件。之所以沒去實現(xiàn)皮膚,那是因為太繁瑣了,沒必要。
在edu.tsinghua.lumaqq.widgets下面,都是LumaQQ用到的一些組件。這些組件你可以直接拿去用。
edu.tsinghua.lumaqq.widgets: 一些尚未歸類的組件,主要是表情頭像選擇的那個窗口: ImageSelector.java。通過實現(xiàn)IImageSelectorAdvisor接口,可以指定窗口的一些樣式和可選擇的圖片內(nèi)容。
edu.tsinghua.lumaqq.widgets.mac:
這下面目前只有一個組件,Ring.java。從包名來看,這下面的組件是一些Mac風(fēng)格的組件,如果你用過Mac,也許你對Mac的那個轉(zhuǎn)來轉(zhuǎn)去的
Busy指示器有點印象,Ring就是這個玩意。Ring.java負(fù)責(zé)處理通用的邏輯,轉(zhuǎn)圈的方式是可以擴展的。缺省的實現(xiàn)是圓形的,如果你想要個其他
形狀的,比如說,正方形;那么擴展IBorderPainter和ISignPainter就可以了。具體可以參考缺省的圓形實現(xiàn)。
edu.tsinghua.lumaqq.widgets.menu:
這里面是一個自定義的菜單實現(xiàn),沒辦法的產(chǎn)物。在Linux下面窗口置頂時,菜單出不來,只好自己寫了一個簡單菜單的實現(xiàn)。優(yōu)點是想怎么畫就怎么畫,美觀
有所提高。缺點是帶來了一個bug,不點一下窗口其他地方菜單不會消息,很難處理,現(xiàn)在也沒解決,不過想來想去,這個bug不是太嚴(yán)重,所以也就忍了。
edu.tsinghua.lumaqq.widgets.qstyle:
看名字可知,這是QQ樣式的組件。QQ什么樣式?比如QQ那個好友列表,QQ的button,等等。最主要的是QQ的好友列表,為了獲得和QQ一樣的視覺
效果,這個好友列表是最重要的部分。首先windows不提供這樣的控件,SWT也沒有,只好自己寫。在2004之前,用的叫Shutter,由于速度上
不行,后來改寫了,叫做Blind,這兩個單詞都是百葉窗的意思。Shutter已經(jīng)拋棄了,就不提了。稍微說一下Blind。Blind實際上是一個組
合組件,并不是自繪的。Blind上面的按鈕是Slat,實際的內(nèi)容區(qū)域是Composite子類。在LumaQQ里面,Blind里面的內(nèi)容是
QTree組件,QTree是自繪的,它有QItem組成,QTreeViewer呢,是QTree的MVC封裝。主要的重繪工作在QItem里面,一個
Item(形象一點說,一個好友)分成了很多部分,好友的頭像是主圖標(biāo),好友的昵稱是文本,好友離開的時候,頭像的右下有個小圖標(biāo),這叫裝飾,好友如果是
綁定手機用戶,頭像旁邊會有手機圖標(biāo),這叫附件,在群里面,還可以看到群下面有組織,可以收起展開,圖標(biāo)的前面會有加號減號標(biāo)明是否展開,這叫前綴。是不
是有點暈了。QItem從QTree哪里得到一個重繪的起始位置,它自己負(fù)責(zé)計算出裝飾,附件,前綴,文本的位置,然后重畫。為了支持動畫,提供了
IEffect接口讓用戶可以自定義重畫的行為,可以參考IEffect的實現(xiàn)類查看具體的實現(xiàn)。Slat呢,就是QQ樣式的button,也是自繪的,
比較簡單。LevelBar是一個等級條,用來顯示好友的等級,缺省使用太遠月亮星星來表示,圖標(biāo)是可以換的,提供了API設(shè)置。Bubble是一個
MSN那樣的冒泡窗口的簡單實現(xiàn),還沒有在LumaQQ里面用到,DieAway呢,是一個淡入淡出的窗口,QQ上線提示那樣的,不過也沒有用到。
By luma at 01/25/2006 - 16:51 | 發(fā)表評論 | 更多 | 435 reads
2005的結(jié)束
這
幾天想想還有什么可以做的,想不太出來,除了文件傳輸這一塊。Bug報告的數(shù)量緩慢,難道bug越來越少?Eclipse
3.2猶抱琵琶,何時才能讓我體驗新特性,提交了幾個Eclipse
bug現(xiàn)在也不解,真難等哪。所以,還是結(jié)束吧,繼續(xù)開始下一個Buggy的版本吧。
雖然讓我無語的人不少,還是要感謝大家對我的支持。每個星期10多萬的點擊數(shù)也不少,可惜不是點廣告的次數(shù),哈,肯定是比不上如來神掌威風(fēng)的時期了,回想起來還真是好玩捏。
很
幸運,一些經(jīng)典bug得到了解決,使2005的穩(wěn)定性超越了2004T,對這點我很滿意。2006的任務(wù)是代碼重整和文件傳輸,自定義頭像和表情,還有
MSN集成。說任務(wù)是言重了,這只是我的平臺,是體驗Eclipse新特性的工具,是我的玩具,也是各位的玩具,抱著輕松的心情研究使用它吧。
如來神掌,終究是浮云,QQ,遲早是浮云。一切就不要當(dāng)真了,沒有什么是我承諾給你的,也沒有什么是你肯定會獲得的,除了那些源代碼。
LumaQQ里面用到了一些Wizard,比如在搜索的時候,在創(chuàng)建群的時候。JFace是帶了一個Wizard框架的,用的就是它的,只不過稍微改了一下。
查看edu.tsinghua.lumaqq.ui.wizard,看到IModelBasedWizard.java,這里對IWizard做了兩個擴展,第一個是采用一個model對象來保存wizard中的信息,所以,你看到了,MVC模式是無處不在的。
第
二個擴展是加了個preNext,用來在點Next按鈕之前做一些事情,這個需求源自對QQ的搜索功能的調(diào)查。QQ搜索的時候,有時候下一步并不是到下一
個page,而是打開一個瀏覽器去網(wǎng)頁上找了。JFace的wizard框架似乎是不能這樣做,所以我加了一個preNext方法。為了讓這個方法能夠被
觸發(fā),使用了WizardWindow類繼承WizardDialog以便能插入preNext方法,WizardWindow還有一個目的:使
Wizard窗口成為獨立窗口,而不是對話框(如果是對話框的話,主窗口最小化,它也會不見,所以要改成獨立窗口的),這個主要是重載
getParentShell方法,讓它返回null,雖然這個方法不是很推薦,但是似乎簡單又方便。
其他的內(nèi)容就沒有什么奇怪了,按照J(rèn)Face的標(biāo)準(zhǔn)框架走下來就可以了。可能有人覺得wizard那些按鈕都是英文的不爽,這個可以通過修改jface的jar包里面的properties文件來改,你可以試試。
QQ的協(xié)議非常龐大,怎么統(tǒng)一的描述包的結(jié)構(gòu),是一個很麻煩的問題。騰訊的包是不是有個統(tǒng)一的準(zhǔn)則,不清楚,當(dāng)然從設(shè)計上來說,統(tǒng)一的當(dāng)然好。
以前以為QQ的包就那么多,設(shè)計的簡單化了。后來功能越加越多,就接觸了一些沒見過的包,發(fā)現(xiàn)類結(jié)構(gòu)描述不了,所以只好改寫,目前的結(jié)構(gòu)是一個4層的模型,真的夠用嗎?目前來說還湊合
首先我們得了解一下QQ那么多包的共同點和不同點,才好了解JQL中的類結(jié)構(gòu)。
1. 目前看來,QQ的包有包頭,包體,包尾之分,但是包尾在某些協(xié)議族里面是個可選的玩意兒
2. 在某些協(xié)議族里面,包格式又有TCP和UDP之分,有些沒有。抽象一點來說呢,UDP是簡單的,隨便定義什么格式都可以滿足要求,TCP是連續(xù)的數(shù)據(jù)流,在連續(xù)的數(shù)據(jù)流里面解析包,就需要有包長度的描述。
3. 包體有全加密,不加密和部分加密的區(qū)別
4. 包有輸入和輸出包之分,不過在有些協(xié)議族里面這個區(qū)別不明顯,甚至可以說輸入和輸出包是一樣的格式
為了能夠描述現(xiàn)有的包,并且能夠為未知的新包提供兼容,目前類的層次有這樣4層,相關(guān)源代碼請查看edu.tsinghua.lumaqq.qq.packets包和子包
1.
一個頂層的基類Packet。它提供最基本的描述和很多抽象的方法。比如一些通用的字段,還有一些通用的方法,但是這個基類的大部分內(nèi)容還是抽象方法,留
待子類實現(xiàn)。Packet最重要的地方在于提供了一個基本的模型,就是把包的構(gòu)造和解析過程分成了頭,體,尾三部分。你可以找到putHead,
putBody, putTail方法用于構(gòu)造一個包,可以找到parseHead, parseBody, parseTail方法用于解析一個包。
2.
兩個Packet的子類InPacket和OutPacket提供對輸入和輸出包的最基本封裝。Packet類是通用的模型,其不涉及輸入和輸出包有什么
不同,所以我們需要這兩個子類來提供更具體的描述。InPacket很簡單,沒什么太多內(nèi)容,因為解析包的過程是通用的,封裝在了Packet中,所以
InPacket沒有多少事做。主要是OutPacket多了不少專用于輸出包的方法,比如重發(fā)次數(shù),超時時間等等,還封裝了輸出包的填充過程。查看
OutPacket的fill方法,可以看到輸出包的生成過程。
在對網(wǎng)絡(luò)硬盤協(xié)議的分析中,發(fā)現(xiàn)有必要對JQL進行重構(gòu),以實現(xiàn)更靈活的協(xié)議建模和調(diào)試
主要改變在
1. 增加了PortPolicy類,隔離網(wǎng)絡(luò)收發(fā)和包解析功能,簡化了Port和協(xié)議之間的綁定操作
2. 簡化了IParser接口,刪除了很多不必要的方法,更加簡潔
3. 細分調(diào)試包對象,使其對應(yīng)于特定協(xié)議族
4. 由于PortPolicy的引入,導(dǎo)致了其他一些類的相應(yīng)修改,比較瑣碎,不提了
5. 細分超時事件,使其對應(yīng)于特定協(xié)議族
6. 在Packet中增加getFamily方法,明確標(biāo)識包所屬協(xié)議族,拋棄以往使用header標(biāo)識包協(xié)議族的方式
7. 不再允許Port直接觸發(fā)QQ事件,降低了他們之間的耦合
重構(gòu)無止境,但是目前這樣已經(jīng)能滿足我增加網(wǎng)絡(luò)硬盤功能的需要,所以暫時就這樣。
我們來了解一下自2006 M2之后,核心層有了什么樣的變化
JQL里面有很多關(guān)鍵部件,先介紹一些概念
1. Porter,啟動一個異步I/O循環(huán),直接監(jiān)聽網(wǎng)絡(luò)事件
2. Port, 代表了一個端口,或者說代表了一個連接,Port需要注冊到Porter中,才能獲得網(wǎng)絡(luò)事件
3. Parser,代表了一個解析器,用來解析某個協(xié)議族的包
4. PacketHistory, 是包的緩沖,用來檢測重復(fù)包
5. Processor,代表了一個處理器,用來處理某個協(xié)議族的包,繼而觸發(fā)QQ事件
6. QQClient,代表了一個QQ客戶端
在2006 M2之前
1. 一個QQClient只有一個Porter
2. 一個Porter可以有多個Port
3. 在一個QQClient中,一個協(xié)議族只有一個Parser
4. 在一個QQClient中,一個協(xié)議族一個Processor
5. 一個QQClient只有一個PacketHistory
那么在2006 M2之后
1. 不變
2. 不變
3. 在一個Port中,一個協(xié)議族一個Parser,一個Port支持哪些協(xié)議族,通過PortPolicy指定
4. 不變
5. 一個Parser一個PacketHistory
所以關(guān)鍵的改變在3,5。通過3,使Port更加靈活。通過5,使包重復(fù)檢測的機制更加靈活。其實,4也應(yīng)該變一下,但是目前沒需求,所以暫時不變。
曾經(jīng)提過,這些改變是為了實現(xiàn)網(wǎng)絡(luò)硬盤功能的關(guān)系。網(wǎng)絡(luò)硬盤的協(xié)議和其他協(xié)議族有一些不同
1. 網(wǎng)絡(luò)硬盤協(xié)議包并不完美匹配之前的包層次假設(shè)
2. 網(wǎng)絡(luò)硬盤協(xié)議包可以非常長,帶來了解析上和處理上的新需求
再細的我就不說了,自己領(lǐng)會