⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sendimpacket.java

📁 很多人都在开发的QQ代码
💻 JAVA
📖 第 1 页 / 共 2 页
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.qq.packets.out;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Date;

import edu.tsinghua.lumaqq.qq.PacketParseException;
import edu.tsinghua.lumaqq.qq.QQ;
import edu.tsinghua.lumaqq.qq.Utils;
import edu.tsinghua.lumaqq.qq.packets.OutPacket;


/**
 * <pre>
 * 发送消息的包,格式为
 * 1. 头部
 * 2. 发送者QQ号,4个字节
 * 3. 接收者的QQ号,4个字节
 * 4. 发送者QQ版本,2字节
 * 5. 发送者QQ号,4字节
 * 6. 接收者QQ号,4个字节(奇怪,为什么要搞两个在里面)
 * 7. 发送者QQ号和session key合在一起用md5处理一次的结果,16字节
 * 8. 消息类型,2字节
 * 9. 会话ID,2字节,如果是一个操作需要发送多个包才能完成,则这个id必须一致
 * 10. 发送时间,4字节
 * 11. 1字节,为0
 * 12. 发送者头像
 * 13. 3字节,为0
 * 14. 字体信息,1字节,设成0x1吧,不懂具体意思
 * 15. 4字节,为0
 * 16. 消息方式,是发送的,还是自动回复的,1字节
 * 17. 以空格和0x0结束的消息
 * 18. 消息的尾部,包含一些消息的参数,比如字体颜色啦,等等等等,顺序是
 *     1. 字体修饰属性,bold,italic之类的,1字节,具体的设置是
 *         i.   bit0-bit4用来表示字体大小,所以最大是32
 *         ii.  bit5表示是否bold
 *         iii. bit6表示是否italic
 *         iv.  bit7表示是否underline
 *     2. 颜色Red,1字节
 *     3. 颜色Green,1字节
 *     4. 颜色Blue,1字节
 *     5. 1个未知字节,置0先
 *     6. 消息编码,2字节,0x8602为GB,0x0000为EN,其他未知,好像可以自定义,因为服务器好像不干涉
 *     7. 可变长度的一段信息,字体名后面跟一个回车符,比如0xcb, 0xce, 0xcc, 0xe5, 0x0d,表示宋体 
 * 19. 包尾部
 *
 * 请求传送文件的包,这是这个包的另一种用法,其格式为
 * 1  - 15. 1到15部分均与发送消息包相同,只有第8部分不同,对于UDP的请求,8部分是0x0035,对于TCP,是0x0001
 * 16 - 18. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
 * 19. 一个固定字节 0x65,含义未知
 * 20. 连接方式字节,UDP是0, TCP是3
 * 21. 4个字节的发送者外部ip地址(也就是可能为代理地址)
 * 22. 2个字节的发送者端口
 * 23. 2个字节的端口,第一个监听端口,TCP没有这个部分
 * 24. 4个字节的地址,真实IP
 * 25. 2个字节的端口,第二个而监听端口
 * 26. 空格符号做为上述信息的结束,一个字节,0x20
 * 27. 分隔符0x1F
 * 28. 要传送的文件名
 * 29. 分隔符0x1F
 * 30. 字节数的字符串形式后跟“ 字节”,比如文件大小3字节的话,就是“3 字节”这个字符串的编码形式
 * 31. 尾部 
 * 
 * 同意传送文件的包,格式为
 * 1  - 25. 除了8部分,其他均与发送消息包相同。对于UDP的情况,8部分是0x0037,TCP是0x0003。
 *          UDP时,最后的本地ip和端口都是0;TCP时没有22部分
 * 26. 尾部
 * 
 * 拒绝接收文件的包,格式为
 * 1 - 20. 除了8部分,均与同意传送文件包相同。对于UDP的情况,8部分是0x0039,对于TCP,是0x0005
 * 
 * 通知我的IP信息,格式为
 * 1 - 25. 除了8部分,均与请求传送文件包相同。8部分是0x003B
 * 26. 尾部
 * 
 * 取消传送文件,格式为
 * 1 - 19. 除了8部分,均与请求传送文件包相同。8部分是0x0049
 * 20. 尾部
 * 
 * 要求别人主动连接我的包,格式为
 * 1 - 19. 除了8部分,均与请求传送文件包相同。8部分是0x003F
 * 20. 尾部
 * </pre>
 * 
 * @author 马若劼
 */
public class SendIMPacket extends OutPacket {
	// 下面为发送普通消息需要设置的变量
    private byte red, green, blue;
    private String fontName;
    private boolean bold, italic, underline;
    private byte fontSize;
    private byte fontFlag; // 用来表示bold, italic, underline, fontSize的组合结果
    private char encoding;
    private int receiver;
    private String message;
    private char messageType;
    private byte replyType;
    
    // 下面为发送文件时需要设置的变量
    private String fileName;
    private String fileSize;
    private char directPort;
    private char localPort;
    private byte[] localIp;
    private char sessionId;
    
    private static final byte DELIMIT = 0x1F;
    
    /** 字体属性 */
    private static final byte NONE = 0x00;
    private static final byte BOLD = 0x20;
    private static final byte ITALIC = 0x40;
    private static final byte UNDERLINE = (byte)0x80;

    /**
     * 构造函数
     */
    public SendIMPacket() {
        super(QQ.QQ_CMD_SEND_IM, true);
        encoding = QQ.QQ_IM_ENCODING_GB;
        fontName = "宋体";
        red = green = blue = 0;
        bold = italic = underline = false;
        fontSize = 0x9;
        fontFlag = 0x9;
        message = "";
        messageType = QQ.QQ_IM_NORMAL_TEXT;
        replyType = QQ.QQ_IM_NORMAL_REPLY;
    }

    /**
     * @param buf
     * @param length
     * @throws PacketParseException
     */
    public SendIMPacket(ByteBuffer buf, int length) throws PacketParseException {
        super(buf, length);
    }
    
    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.qq.packets.OutPacket#putBody(java.nio.ByteBuffer)
     */
    protected void putBody(ByteBuffer buf) {
	    // 发送者QQ号
	    buf.putInt(user.getQQ());
	    // 接收者QQ号
	    buf.putInt(receiver);
	    // 发送者QQ版本
	    buf.putChar(source);
	    // 发送者QQ号
	    buf.putInt(user.getQQ());
	    // 接收者QQ号
	    buf.putInt(receiver);
	    // 文件传输会话密钥
	    buf.put(user.getFileSessionKey());
	    // 消息类型
	    buf.putChar(messageType);
	    // 顺序号
	    if(sessionId == 0)
	    	buf.putChar(sequence);
	    else
	    	buf.putChar(sessionId);
	    // 发送时间
	    int time = (int)(System.currentTimeMillis() / 1000);
	    buf.putInt(time);
	    // 1字节为0
	    buf.put((byte)0);
	    // 发送者头像
	    byte face = Utils.getByte(user.getContactInfo().infos[user.getContactInfo().face], 0);
	    buf.put(face);
	    // 3字节为0
	    buf.putChar((char)0);
	    buf.put((byte)0);
	    // 字体信息,设成1
	    buf.put((byte)1);
	    // 4字节为0
	    buf.putInt(0);
	    
	    // 判断消息类型
	    switch(messageType) {
		    case QQ.QQ_IM_NORMAL_TEXT:
		    	initTextContent(buf);
		    	break;
		    case QQ.QQ_IM_UDP_REQUEST:
		    	initSendFileContent(buf);
		    	break;
		    case QQ.QQ_IM_ACCEPT_UDP_REQUEST:
		    	initSendFileAcceptContent(buf);
		    	break;
		    case QQ.QQ_IM_REJECT_UDP_REQUEST:
		    case QQ.QQ_IM_REJECT_TCP_REQUEST:
		        initSendFileRejectContent(buf);
		    	break;
		    case QQ.QQ_IM_NOTIFY_IP:
		        initNotifyFilePortUDP(buf);
		    	break;
		    case QQ.QQ_IM_REQUEST_CANCELED:
		        initConnectionCanceled(buf);
		    	break;
		    case QQ.QQ_IM_ARE_YOU_BEHIND_FIREWALL:
		        initPleaseConnectMe(buf);
		    	break;
	    }
    } 
    
    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.qq.packets.OutPacket#parseBody(java.nio.ByteBuffer)
     */
    protected void parseBody(ByteBuffer buf) throws PacketParseException {
        // sender
        buf.getInt();
        // receiver
        receiver = buf.getInt();
        // source
        buf.getChar();
        // sender
        buf.getInt();
        // receiver
        buf.getInt();
        // file session key
        buf.position(buf.position() + QQ.QQ_KEY_LENGTH);
        // message type
        messageType = buf.getChar();
        // session id
        sessionId = buf.getChar();
        // time
        buf.getInt();
        // unknown
        buf.get();
        // face
        buf.get();
        // unknown 3 bytes
        buf.getChar();
        buf.get();
        // font info
        buf.get();
        // unknown 4 bytes
        buf.getInt();
        
	    // 判断消息类型
	    switch(messageType) {
		    case QQ.QQ_IM_NORMAL_TEXT:
		    	parseTextContent(buf);
		    	break;
		    case QQ.QQ_IM_UDP_REQUEST:
		    	parseSendFileContent(buf);
		    	break;
		    case QQ.QQ_IM_ACCEPT_UDP_REQUEST:
		    	parseSendFileAcceptContent(buf);
		    	break;
		    case QQ.QQ_IM_REJECT_UDP_REQUEST:
		    case QQ.QQ_IM_REJECT_TCP_REQUEST:
		        parseSendFileRejectContent(buf);
		    	break;
		    case QQ.QQ_IM_NOTIFY_IP:
		        parseNotifyFilePortUDP(buf);
		    	break;
		    case QQ.QQ_IM_REQUEST_CANCELED:
		        parseConnectionCanceled(buf);
		    	break;
		    case QQ.QQ_IM_ARE_YOU_BEHIND_FIREWALL:
		        parsePleaseConnectMe(buf);
		    	break;
	    }
    }

    /**
     * @param buf
     */
    private void parsePleaseConnectMe(ByteBuffer buf) {
	    // 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
        buf.getLong();
        buf.getChar();
        buf.get();
	    // 我们先尝试UDP方式
        buf.get();
        buf.get();
	    // 四个字节的发送者IP,这是外部IP
        byte[] ip = new byte[4];
        buf.get(ip);
        user.setIp(ip);
	    // 发送者端口
        user.setPort(buf.getChar());
	    // 监听端口,含义未知,为连接服务器的端口,先随便写一个值
        directPort = buf.getChar();
	    // 后面全0
        buf.getInt();
        buf.getChar();
    }

    /**
     * @param buf
     */
    private void parseConnectionCanceled(ByteBuffer buf) {
    }

    /**
     * @param buf
     */
    private void parseNotifyFilePortUDP(ByteBuffer buf) {
	    // 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
        buf.getLong();
        buf.getChar();
        buf.get();
	    // 我们先尝试UDP方式
        buf.get();
        buf.get();
	    // 四个字节的发送者IP,这是外部IP
        byte[] ip = new byte[4];
        buf.get(ip);
        user.setIp(ip);
	    // 发送者端口
        user.setPort(buf.getChar());
	    // 监听端口,含义未知,为连接服务器的端口,先随便写一个值
        directPort = buf.getChar();
	    // 真实IP和第二个端口
        localIp = new byte[4];
        buf.get(localIp);
        localPort = buf.getChar();
    }

    /**
     * @param buf
     */
    private void parseSendFileRejectContent(ByteBuffer buf) {
    }

    /**
     * @param buf
     */
    private void parseSendFileAcceptContent(ByteBuffer buf) {
	    // 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
        buf.getLong();
        buf.getChar();
        buf.get();
	    // 我们先尝试UDP方式
        buf.get();
        buf.get();
	    // 四个字节的发送者IP,这是外部IP
        byte[] ip = new byte[4];
        buf.get(ip);
        user.setIp(ip);
	    // 发送者端口
        user.setPort(buf.getChar());
	    // 监听端口,含义未知,为连接服务器的端口,先随便写一个值
        directPort = buf.getChar();
	    // 后面全0
        buf.getInt();
        buf.getChar();
    }

    /**
     * 解析传送文件请求
     * 
     * @param buf
     */
    private void parseSendFileContent(ByteBuffer buf) {
	    // 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
        buf.getLong();
        buf.getChar();
        buf.get();
	    // 我们先尝试UDP方式
        buf.get();
        buf.get();
	    // 四个字节的发送者IP,这是外部IP
        byte[] ip = new byte[4];
        buf.get(ip);
        user.setIp(ip);
	    // 发送者端口
        user.setPort(buf.getChar());
	    // 直接端口
        directPort = buf.getChar();
        buf.getInt();
        buf.getChar();
        buf.get();
        buf.get();
        fileName = Utils.getString(buf, DELIMIT);
        fileSize = Utils.getString(buf);
    }

    /**
     * 解析文本内容
     * @param buf
     */
    private void parseTextContent(ByteBuffer buf) {
	    // 消息方式,是发送的,还是自动回复的,1字节
        replyType = buf.get();
        
        message = Utils.getString(buf, 0x0);
        fontFlag = buf.get();
        red = buf.get();
        green = buf.get();
        blue = buf.get();
        buf.get();
        encoding = buf.getChar();
        fontName = Utils.getString(buf, 0x0D);
        
        bold = (fontFlag & BOLD) != 0;
        italic = (fontFlag & ITALIC) != 0;
        underline = (fontFlag & UNDERLINE) != 0;
        fontSize = (byte)(fontFlag & 0x1F);
    }

    /**
     * 初始化请求对方主动连接的包
     * @param buf
     */
    private void initPleaseConnectMe(ByteBuffer buf) {
	    // 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节
        buf.putLong(0);
        buf.putChar((char)0);
        buf.put((byte)0);
	    // 我们先尝试UDP方式
        buf.put((byte)0x65);
        buf.put((byte)0x0);
	    // 四个字节的发送者IP,这是外部IP
        buf.put(user.getIp());
	    // 发送者端口
        buf.putChar((char)user.getPort());
	    // 监听端口,含义未知,为连接服务器的端口,先随便写一个值
        buf.putChar(directPort);
	    // 后面全0
        buf.putInt(0);
        buf.putChar((char)0);
    }

    /**
     * 初始化取消发送文件包
     * @param buf
     */
    private void initConnectionCanceled(ByteBuffer buf) {
	    // 17 - 19. 怀疑也和发送消息包相同,但是在这种情况中,这部分没有使用,为全0,一共11个0字节

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -