阿不

不抛弃,不放弃

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  158 随笔 :: 0 文章 :: 2069 评论 :: 57 Trackbacks

自从博客园闪存发布了QQ机器人以后,闪存数量一下子就上升了很多。很多人也一直都在询问QQ机器人是如何开发的,这足以说明QQ在中国不仅仅是普通人使用的IM工具,在程序员圈子里也相当有人缘的,其老大地位不容怀疑啊。我这篇也算是给解答一些朋友的疑惑吧。smile_wink

所谓的IM机器人(QQ,MSN),其实就是一个简化版的IM客户端(QQ,MSN)。利用这个简单的客户端连接到IM服务器,接收和发送消息来达到自动回复的目的。可惜,.NET中并没有开源的QQ机器人的开发包(MSN,有DotMSN,详见《使用DotMSN 2.0开发MSN机器人》)。博客园使用的是商业组件,而对大部分人来说更希望是免费的。.NET中没有开源开发包,但是JAVA中却有。在Liunx等其它非Windows平台下,会有很多的开源QQ,其中的LumaQQ也算是比较有名的开源QQ了,你可以从它的官方主页上了解更多的信息。在网络上也已经有人根据LumaQQ的协议,使用C#来开发机器人了。遗憾的是,没有一个是开源的。没有也罢,那我们就直接使用JAVA版本来的LumaQQ来开发自己的机器人吧。

其实使用JAVA语法,对于我们来说并不是一件难事。我想大家主要的麻烦就在于如何去使用JAVA的开发工具,引用LumaQQ的包,以及编译,调试,打包和部署。但这些在这里都不是难事,我会提供引用好了点整个的Eclipse解决方案(工作空间)。你下载了,直接在这个空间下开发编辑源代码即可。下面先来说说简单的原理。

原理部分

这个机器人,我们直接开发一个一直运行的机器人即可。如果你对JAVA本身并不是很了解的话,那么我建议QQ机器人本身只提供一个QQ客户端收发信息的作用,并不将机器人逻辑写在这个机器人客户端里面,把机器人逻辑写到一个WebService中。一方面你可以用你最擅长的语言来开发WebService;一方面,如果你需要多种平台的机器人(MSN机器人等)这部分的逻辑是可以公用的,而不需再次去开发测试这部分。

代码部分

关于LumaQQ接口开发机器人,网上已经有很多的代码了,我也是从网上直接拷贝的代码下来的。最关键的代码有以下两部分:

CODE 1:设置参数,登录代码

   1: private void connect()
   2: {
   3:      try
   4:      {  
   5:          client = new QQClient();
   6:          client.addQQListener(this);
   7:          client.setConnectionPoolFactory(new PortGateFactory());
   8:          user = new QQUser(739330764, "qqrobot");
   9:          user.setStatus(QQ.QQ_LOGIN_MODE_NORMAL);             
  10:          
  11:          client.setUser(user);
  12:          //TCP登录
  13:          user.setUdp(false);
  14:          client.setTcpLoginPort(8000);
  15:          client.setLoginServer("219.133.48.103");
  16:          //UDP登录
  17:          //user.setUdp(true);
  18:          //clent.setLoginServer("sz.tencent.com");
  19:          
  20:          //client.setProxyType("Socks5");
  21:          // client.setProxy(new InetSocketAddress("AF25",1080));
  22:          
  23:          client.login();
  24:      }
  25:      catch (Exception ex)
  26:      {
  27:          ex.printStackTrace();
  28:          //client.release();
  29:      }
  30: }

这部分代码里面提供了两种方式:TCP和UDP来登录到服务器。大家都知道QQ直接这两种方式的登录,但是需要使用不同的服务器地址。

CDOE 2:事件处理代码

   1: public void qqEvent(QQEvent e)
   2: {
   3:     switch (e.type)
   4:     {
   5:         case QQEvent.QQ_LOGIN_SUCCESS:
   6:             msg("QQ_LOGIN_SUCCESS");
   7:             break;
   8:         case QQEvent.QQ_LOGIN_FAIL:
   9:             msg("QQ_LOGIN_FAIL");
  10:             msg("reconnect");
  11:             connect();
  12:             // client.release();
  13:             //System.exit(0);
  14:             break;
  15:         case QQEvent.QQ_LOGIN_UNKNOWN_ERROR:
  16:             msg("QQ_LOGIN_UNKNOWN_ERROR");
  17:             msg("reconnect");
  18:             connect();
  19:             // client.release();
  20:             //System.exit(0);
  21:             break;
  22:         case QQEvent.QQ_LOGIN_REDIRECT_NULL:
  23:             msg("QQ_LOGIN_REDIRECT_NULL");
  24:             msg("reconnect");
  25:             connect();
  26:             // client.release();
  27:             //System.exit(0);
  28:             break;
  29:         case QQEvent.QQ_CONNECTION_LOST:
  30:             msg("QQ_CONNECTION_LOST");
  31:             msg("reconnect");
  32:             connect();
  33:             // client.release();
  34:             //System.exit(0);
  35:             break;
  36:         case QQEvent.QQ_NETWORK_ERROR:
  37:             msg("QQ_NETWORK_ERROR");
  38:             msg("reconnect");
  39:             connect();
  40:             // client.release();
  41:             //System.exit(0);
  42:             break;
  43:         case QQEvent.QQ_CONNECTION_BROKEN:
  44:             msg("QQ_CONNECTION_BROKEN");
  45:             msg("reconnect");
  46:             connect();
  47:             // client.release();
  48:             //System.exit(0);
  49:             break;
  50:         case QQEvent.QQ_RECEIVE_TEMP_SESSION_IM:
  51:             SimpleDateFormat tempDate = new SimpleDateFormat("MM-dd HH:mm");
  52:             impacket = (ReceiveIMPacket) e.getSource();
  53:             qqnum = impacket.tempSessionIM.sender;
  54:             immsg = new String(impacket.tempSessionIM.message);
  55:             msg("["
  56:                     + tempDate.format(new Date(impacket.tempSessionIM.time))
  57:                     + "] " + qqnum + ":" + immsg);
  58:             addFriend(qqnum);
  59:                 msg("临时回复");
  60:                 client.sendIM(qqnum, Util.getBytes("对不起:" + impacket.tempSessionIM.nick + ",GK助手暂时还不支持临时会话,请先将我加为好友,然后在正常聊天窗体与我聊天,这样我才能帮助你。:)"));
  61:             break;
  62:         case QQEvent.QQ_RECEIVE_NORMAL_IM:// 收到正常消息�?
  63:             SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm");
  64:             impacket = (ReceiveIMPacket) e.getSource();
  65:             qqnum = impacket.normalHeader.sender;
  66:             immsg = new String(impacket.normalIM.messageBytes);
  67:             msg("["
  68:                     + sdf.format(new Date(impacket.normalHeader.sendTime))
  69:                     + "] " + qqnum + ":" + immsg);
  70:             if (impacket.normalIM.replyType != QQ.QQ_IM_AUTO_REPLY)
  71:             {
  72:                 msg("好友:" + qqnum +"请求信息:" + immsg );
  73:                 client.sendIM(qqnum, Util.getBytes(immsg));
  74:             }
  75:             //if (immsg.trim().equalsIgnoreCase("exit"))
  76:            // {
  77:            //     System.out.println(qqnum + "命令你�退出");
  78:            //     client.logout();
  79:            //     client.release();
  80:             //    System.exit(0);
  81:            // }
  82:             break;
  83:         case QQEvent.QQ_ADDED_BY_OTHERS:// 事件发生在有人将我加为好友时
  84:         case QQEvent.QQ_ADDED_BY_OTHERS_EX:// 事件发生在有人将我加为好友时
  85:             msg("QQ_ADDED_BY_OTHERS_EX");
  86:             snpacket = (SystemNotificationPacket) e.getSource();
  87:             qqnum = snpacket.from;
  88:             msg(qqnum + "把我加为了好友�?");
  89:             client.sendIM(qqnum, Util.getBytes("hello."));
  90:             break;
  91:         case QQEvent.QQ_REQUEST_ADD_ME:// 事件发生在有人请求加我为好友�?
  92:         case QQEvent.QQ_REQUEST_ADD_ME_EX:// 事件发生在有人请求加我为好友�?
  93:             msg("QQ_REQUEST_ADD_ME_EX");
  94:             snpacket = (SystemNotificationPacket) e.getSource();
  95:             qqnum = snpacket.from;
  96:             msg(qqnum + "想加我为好友�");// 1675103
  97:             client.approveAddMe(qqnum);
  98:             addFriend(qqnum);
  99:             break;
 100:         case QQEvent.QQ_ADD_FRIEND_NEED_AUTH:
 101:             AddFriendExReplyPacket packet = (AddFriendExReplyPacket)e.getSource();
 102:             qqnum = packet.friendQQ;
 103:             sendAddFriendAuth(qqnum);
 104:             break;
 105:         case QQEvent.QQ_REQUEST_ADD_OTHER_APPROVED:// 事件发生在有人请求加我为好友时,我同意并且加他为好友
 106:             msg("QQ_REQUEST_ADD_OTHER_APPROVED");
 107:             break;
 108:         case QQEvent.QQ_REQUEST_ADD_OTHER_APPROVED_AND_ADD:// 事件发生在有人请求加我为好友时,我同意并且加他为好友
 109:             msg("QQ_REQUEST_ADD_OTHER_APPROVED_AND_ADD");
 110:             break;
 111:         case QQEvent.QQ_REQUEST_ADD_OTHER_REJECTED:// 事件发生在我请求加一个人,那个人拒绝�?
 112:             snpacket = (SystemNotificationPacket) e.getSource();
 113:             msg(snpacket.from
 114:                     + "拒绝加我为好友�?理由为:"
 115:                     + ((snpacket.message == null || snpacket.message
 116:                             .equals("")) ? "�?" : snpacket.message));
 117:             break;
 118:         case QQEvent.QQ_KICKED_OUT_BY_SYSTEM:
 119:             msg("QQ在别处登录了,重新登录.");
 120:             connect();
 121:             break;
 122:         default :
 123:                 msg(e.type);
 124:             break;
 125:     }
 126: }

大家看到了LumaQQ里面的事件处理看起来似乎比较原始了一点。但是没关系,它是确实可用的。LumaQQ里面支持的QQ事件协议都在QQEvent中已经有定义了,同时不同的事件,它的事件参数e.getSource()都是不同类型的对象。比如接收到正常消息,它的事件枚举是QQEvent.QQ_RECEIVE_NORMAL_IM,e.getSource()的类型是ReceiveIMPacket。你把这个对象转换成ReceiveIMPacket类型后,就可以得知是谁发送的什么样的消息了。这时候你就可以调用client.sendIM方法来回复消息了。至于回复什么,就是你的机器人要做的事件了,它里可以调用WebService,也可以把业务逻辑直接写在这边。

还有就是断点重连,LumaQQ已经可以保证长时间在线了。但是我们也要有断线重连的功能,这个在例子中也已经有了。还有其它的事件和接口我就不详细介绍了,因为我个人对JAVA的了解也不够多。下面再来介绍一下Eclipse的打包吧,这也是一个比较麻烦的地方,没有同事的帮忙我也是一时半会儿也搞不定。

编译,打包部分

同事给我的是装有ObjectWeb Lomboz插件的eclipse,我还必须要说明一下,我的eclipse目录是在:D:\Program Files\ecplise ,因为它有可能影响到一些包的引用和编译。它的启动界面是这样的:

图一:

pic1

大家下载完附件的示例代码后,在文件菜单下点击"Switch Workspace"选择解压后的目录。就可以打开解决方案了(工作空间),里面会有三个工程:LumaQQ是QQ协议工程,LumaQQ.net 是LumaQQ负责网络连接部分的工程代码,robot是QQ机器人工程。如果你要在eclipse里面运行或调试机器人,点击QQRobot.java右键在菜单中选择RunAs或DebugAs Java Applcation就可以运行或调试了:

图二:

pic2

更多的调试技巧我就不多介绍了。下面来介绍打包吧。eclipse要打包成控制台程序那也不是一件容易的事情。要先将这个解决方案导出成jar包:File ---  Export 选择Java目录下的JAR File:

图三:

pic3

把三个工程都选择起来,选择包存放的路径和包的文件名:

图四:

pic4

一路Next或者直接Finish,可能是弹出警告提示,看不懂也不用管它。转到你刚才包的保存路径,正常情况下,你可以看到你刚才保存的文件名.jar这么一个文件。接下来的工作就是把这个jar打包成exe控制台程序了。这还得借助于另一个工具的帮忙,我使用的是exe4j,你从网络上去下载就可以了。不过它是共享软件,非注册版本打包的exe在运行前会弹出一个提示,告诉你是这个exe是用什么打包的。宣传一下,有点讨厌。

打包exe,需要创建exe4j的工程文件。还有一个麻烦的就是要指定它所引用的所有第三方包的路径,而且设置输出路径,版本,运行环境等等这么信息。为了方便起见,我也把这个文件放在附件的示例中了。安装了exe4j后就可以打开这个文件了,打开了点击Finish就在编译了。

图五:exe4j工程文件

 

pic5

图六:引用的第三方包

pic6

图七:编译中

pic7

经过这一系列的步骤后,你所得到的exe文件,就是一个可用的控制台程序了。这时候除了JRE外,不需要其它的插件的支持了。

写在最后

做为一个.NET平台的开发人员,以上的步骤对我们来说确实是太过于烦杂了。在寒冬季节我写这样的一篇文章都快要满头大汗了,我相信各位看官如果能坚持看到这里那么你一定是非常有耐力了。但是没有办法,我们需要忍受。如果有时间,有精力,我还是很愿意以LumaQQ为样本,开发一个开源的QQ开发包,这样大家就不用再这么麻烦了。

PS:以上的代码开发的QQ机器人基本上可以保持7 * 24 在线服务。smile_teeth

附件下载

阿不 http://hjf1223.cnblogs.com
posted on 2008-01-22 19:41 阿不 阅读(5100) 评论(66)  编辑 收藏 所属分类: .Net相关技术

评论

学习了,晚上测试一下
  回复  引用    

#2楼  2008-01-22 19:54 Solog      
和C#有啥关系。只看到LZ说JAVA了
  回复  引用  查看    

#3楼  2008-01-22 20:00 航天奇侠      
不错,和我用同一个模板。
  回复  引用  查看    

#4楼  2008-01-22 20:11 没剑      
强烈建议楼主开发.net开发包!!
  回复  引用  查看    

#5楼  2008-01-22 23:27 TT.Net      
好东西啊。晚上测试下能不能用
  回复  引用  查看    

#6楼  2008-01-22 23:36 怪虎      
支持楼主把它弄成.net类库
  回复  引用  查看    

#7楼  2008-01-23 02:32 偶卖糕的      
为啥lumaqq不能在osx10.3上面跑呢,搞得我都不能聊天
  回复  引用  查看    

#8楼 [楼主] 2008-01-23 08:36 阿不      
@Solog
这篇文章说的就是JAVA
  回复  引用  查看    

#9楼 [楼主] 2008-01-23 08:36 阿不      
@偶卖糕的
这个不清楚喔,你要去问Luma了

  回复  引用  查看    

#10楼  2008-01-23 08:57 巫云      
哦,java,据说很恐怖的东东。
等把它翻译成.net,呵呵。
  回复  引用  查看    

#11楼  2008-01-23 09:03 平静中的疯狂      
不错,晚上回去试试。
机器人还是挺有用的,尤其QQ在国内还是非常普遍的。
  回复  引用  查看    

#12楼  2008-01-23 09:18 overred      
不错 先支持下

lumaQQ也得分析协议
TX改他也得改(改的最快的也许就是他那个版本字节协议,不过可以伪装)

不过他的那个JQL还是不错的


目前比较出名的还是pidgin(基于Gtk+)
中文网:http://www.gaimcn.com/
英文:http://www.pidgin.im/
如果能调他的组件。。。msn qq 啥得就全解决了。。。
  回复  引用  查看    

#13楼  2008-01-23 09:24 overred      
不过也可以用效率低下的钩子
  回复  引用  查看    

#14楼 [楼主] 2008-01-23 09:57 阿不      
@overred
我也用过pidgin的软件,但是源码看不懂。
  回复  引用  查看    

#15楼  2008-01-23 10:09 overred      
@阿不
其实分析他的协议和使用udp发送和接受包才是关键
呵呵
  回复  引用  查看    

#16楼 [楼主] 2008-01-23 11:03 阿不      
@overred
那是肯定的。
  回复  引用  查看    

#17楼  2008-01-23 11:12 克己复礼      
先问一下,有没有“珊瑚虫”的危险
  回复  引用  查看    

#18楼 [楼主] 2008-01-23 11:20 阿不      
@克己复礼
这个不敢保证喔。
得问有关的法律专家了。:(
  回复  引用  查看    

#19楼  2008-01-23 11:22 Zeng      
收藏学习。
将来估计要研究一下。
thanks :)
  回复  引用  查看    

#20楼  2008-01-23 13:15 坏人      
pidgin其实可以包装嘛,不一定非要看得很懂...
  回复  引用  查看    

#21楼 [楼主] 2008-01-23 13:18 阿不      
@坏人
你的留言怎么删了?我加你怎么没有回音?
你是说对程序本身进行拦截?
  回复  引用  查看    

#22楼  2008-01-23 16:05 坏人      
很多人疯狂的加了MSN,就找我要机器人的代码- -!

可我又没有,所以赶紧删掉联系方式...

对程序拦截似乎也是个办法,但和直接分析协议,我觉得也没有什么好处,假如升级,也一样得改吧。。。

我说的就是pidgin,他用C++做的,我的意思是假如能把他包装一下,让C#直接可以调用的话,工作量或许会比重新参照LUMAQQ写一个要省事很多,并且一下把N个IM全给实现了,统一的架构,也挺不错,不然搞套机器人,还得DOTMSN跑一边,QQ跑一边,也挺麻烦的呢。

你加我了?恩,大概居然有30多个人加我,全都是要代码的,但我哪来的代码呢,搞得我一上午没消停过。。。后来很无奈,我就干脆全删了。。。不知道有没有删错你。。。
  回复  引用  查看    

#23楼  2008-01-23 16:08 坏人      
删除虫类似的危险应该是没有的,腾讯当初也提醒过珊瑚虫的作者多次,他不听劝,这个没办法,只能法庭上见了,再加上珊瑚虫捆绑不少东西,又去掉广告,既伤害了腾讯的利益,也损坏了QQ的名声(不少普通用户只以为那些捆绑是QQ干的,却分不清楚是哪个版本的QQ),我们搞这个机器人,只能是帮QQ扩大使用范围,也不会损坏他的利益,更不会实施流氓捆绑行为,先不说法律问题,就商业利益问题,腾讯也犯不着来招惹咱。
  回复  引用  查看    

#24楼 [楼主] 2008-01-23 16:12 阿不      
@坏人
搞pidgin可不是一件容易的事喔。
  回复  引用  查看    

#25楼  2008-01-23 16:34 坏人      
恩,要转过去,也挺麻烦的,不过包装好了,挺不错。
  回复  引用  查看    

#26楼  2008-01-23 17:33