📄 qqclient.java
字号:
/*
* 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
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.UnresolvedAddressException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Vector;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* QQ的客户端类,这个类为用户提供一个方便的接口,比如发送消息之类的,只需要调用一个
* 方法便可以完成了。
*
* LumaQQ网络层通过NIO实现,每个Client分配一个Selector,每个Selector可以监听多个
* Channel,channel包装在IPort的实现类中,对于一个Client,有一个Main Port,这个
* Main Port定义为连接QQ登陆服务器的那个Port。系统登陆之初之有一个Port即Main Port,
* 根据需要可以开启其他Port
*
* @author luma
*/
public class QQClient implements IQQListener {
/**
* <pre>
* 执行清理任务的Runnable类
* </pre>
*
* @author luma
*/
private class CleanRunnable implements Runnable {
private PortGate _gate;
private Porter _porter;
public CleanRunnable(PortGate gate, Porter porter) {
this._gate = gate;
this._porter = porter;
}
public void run() {
if(_gate != null) // 关闭所有port
_gate.disposeAll();
if(_porter != null) // 关闭porter
_porter.shutdown();
}
}
// Log对象
// private static Log log = LogFactory.getLog(QQClient.class);
// 登陆的服务器IP
private String loginServer;
// TCP方式登录时的服务器端口,UDP方式时无用处
private int tcpLoginPort;
// 包处理器路由器
private ProcessorRouter router;
// QQ监听器
private List<IQQListener> qqListeners, qqListenersBackup;
// QQ用户
private QQUser user;
// 包帮助类
private PacketHelper packetHelper;
// 当前是否正在登陆
private boolean logging;
// 当前是否在重定向登录
private boolean loginRedirect;
// Port管理类
private PortGate gate;
// 代理类型
private int proxyType;
// 代理服务器地址
private InetSocketAddress proxyAddress;
// 代理服务器验证用户名,null表示不需要验证
private String proxyUsername;
// 代理服务器验证密码
private String proxyPassword;
// 是否打开了机器人功能
private boolean robotMode;
// 机器人实例
private IRobot robot;
// 是否监听器有改变,这是为了处理同步问题而设置的,在实际fireQQEvent时,
// 不使用qqListeners,而是用backup,这样在事件触发期间如果要修改
// 监听器也是可以的,不然有可能造成并发修改异常。如果有更好的办法当
// 然,目前只想出这种办法
private boolean listenerChanged;
/**
* 单线程执行器
*/
protected static final SingleExecutor executor = new SingleExecutor();
/** 包事件触发过程 */
protected Callable<Object> packetEventTrigger;
/** 重发过程 */
protected ResendTrigger<Object> resendTrigger;
/** Keep Alive过程 */
protected Runnable keepAliveTrigger;
protected ScheduledFuture keepAliveFuture;
/** 接收队列 */
protected Queue<InPacket> receiveQueue;
/** Selector线程 */
protected Porter porter;
/** 主port名称 */
public static final String MAIN_PORT = "main";
/** 包处理器数目 */
protected static final int PROCESSOR_COUNT = 2;
/**
* 构造函数
*/
public QQClient() {
router = new ProcessorRouter(PROCESSOR_COUNT);
qqListeners = new Vector<IQQListener>();
qqListenersBackup = new Vector<IQQListener>();
receiveQueue = new LinkedList<InPacket>();
logging = false;
loginRedirect = false;
robotMode = false;
listenerChanged = false;
qqListeners.add(this);
qqListenersBackup.add(this);
packetHelper = new PacketHelper();
packetEventTrigger = new PacketEventTrigger<Object>(this);
keepAliveTrigger = new KeepAliveTrigger(this);
resendTrigger = new ResendTrigger<Object>(this);
router.installProcessor(new BasicFamilyProcessor(this));
router.installProcessor(new _05FamilyProcessor(this));
executor.increaseClient();
}
/**
* 设置代理类型
* @param type
*/
public void setProxyType(String type) {
if(type.equalsIgnoreCase("None"))
proxyType = QQ.QQ_PROXY_NONE;
else if(type.equalsIgnoreCase("Socks5"))
proxyType = QQ.QQ_PROXY_SOCKS5;
else if(type.equalsIgnoreCase("Http"))
proxyType = QQ.QQ_PROXY_HTTP;
else
proxyType = QQ.QQ_PROXY_NONE;
}
/**
* 设置代理服务器地址
* @param proxyAddress
*/
public void setProxy(InetSocketAddress proxyAddress) {
this.proxyAddress = proxyAddress;
}
/**
* 发送keep alive包
*/
public void keepAlive() {
if(user.isLoggedIn()) {
KeepAlivePacket packet = new KeepAlivePacket(user);
gate.send(MAIN_PORT, packet, false);
}
}
/**
* 共享我的地理位置
*/
public void shareMyGeography() {
if(user.isLoggedIn()) {
PrivacyDataOpPacket packet = new PrivacyDataOpPacket(user);
packet.setSubCommand(QQ.QQ_SUB_CMD_SHARE_GEOGRAPHY);
packet.setOpCode(QQ.QQ_VALUE_SET);
gate.send(MAIN_PORT, packet, false);
}
}
/**
* 不共享我的地理位置
*/
public void unshareMyGeography() {
if(user.isLoggedIn()) {
PrivacyDataOpPacket packet = new PrivacyDataOpPacket(user);
packet.setSubCommand(QQ.QQ_SUB_CMD_SHARE_GEOGRAPHY);
packet.setOpCode(QQ.QQ_VALUE_UNSET);
gate.send(MAIN_PORT, packet, false);
}
}
/**
* 设置仅能通过QQ号找到我
*/
public void setSearchMeByQQOnly() {
if(user.isLoggedIn()) {
PrivacyDataOpPacket packet = new PrivacyDataOpPacket(user);
packet.setSubCommand(QQ.QQ_SUB_CMD_SEARCH_ME_BY_QQ_ONLY);
packet.setOpCode(QQ.QQ_VALUE_SET);
gate.send(MAIN_PORT, packet, false);
}
}
/**
* 取消设置仅能通过QQ号找到我
*/
public void unsetSearchMeByQQOnly() {
if(user.isLoggedIn()) {
PrivacyDataOpPacket packet = new PrivacyDataOpPacket(user);
packet.setSubCommand(QQ.QQ_SUB_CMD_SEARCH_ME_BY_QQ_ONLY);
packet.setOpCode(QQ.QQ_VALUE_UNSET);
gate.send(MAIN_PORT, packet, false);
}
}
/**
* 请求天气预报
*
* @param ip
*/
public char getWeather(byte[] ip) {
if(user.isLoggedIn()) {
WeatherOpPacket packet = new WeatherOpPacket(user);
packet.setIp(ip);
gate.send(MAIN_PORT, packet, false);
return packet.getSequence();
}
return 0;
}
/**
* 请求自己这里的天气预报
*
* @param ip
*/
public char getWeather() {
if(user.isLoggedIn()) {
WeatherOpPacket packet = new WeatherOpPacket(user);
packet.setIp(user.getIp());
gate.send(MAIN_PORT, packet, false);
return packet.getSequence();
}
return 0;
}
/**
* 上传好友备注信息
*
* @param qqNum
* 好友的QQ号
* @param remark
* 备注类
*/
public void uploadFriendRemark(int qqNum, FriendRemark remark) {
if(user.isLoggedIn()) {
FriendDataOpPacket packet = new FriendDataOpPacket(user);
packet.setRemark(remark);
packet.setQQ(qqNum);
gate.send(MAIN_PORT, packet, true);
}
}
/**
* 下载好友备注信息
*
* @param qqNum
* 好友的QQ号
*/
public void downloadFriendRemark(int qqNum) {
if(user.isLoggedIn()) {
FriendDataOpPacket packet = new FriendDataOpPacket(user);
packet.setSubCommand(QQ.QQ_SUB_CMD_DOWNLOAD_FRIEND_REMARK);
packet.setQQ(qqNum);
gate.send(MAIN_PORT, packet, false);
}
}
/**
* 批量下载好友备注
*
* @param page
* 页号
*/
public void batchDownloadFriendRemark(int page) {
if(user.isLoggedIn()) {
FriendDataOpPacket packet = new FriendDataOpPacket(user);
packet.setSubCommand(QQ.QQ_SUB_CMD_BATCH_DOWNLOAD_FRIEND_REMARK);
packet.setPage(page);
gate.send(MAIN_PORT, packet, false);
}
}
/**
* @param page
* @param online
* @param hasCam
* @param provinceIndex
* @param cityIndex
* @param ageIndex
* @param genderIndex
* @return
*/
public char searchUserAdvanced(
int page,
boolean online,
boolean hasCam,
int provinceIndex,
int cityIndex,
int ageIndex,
int genderIndex) {
if(user.isLoggedIn()) {
AdvancedSearchUserPacket packet = new AdvancedSearchUserPacket(user);
packet.setPage((char)page);
packet.setSearchOnline(online);
packet.setHasCam(hasCam);
packet.setProvinceIndex((char)provinceIndex);
packet.setCityIndex((char)cityIndex);
packet.setAgeIndex((byte)ageIndex);
packet.setGenderIndex((byte)genderIndex);
gate.send(MAIN_PORT, packet, false);
return packet.getSequence();
} else
return 0;
}
/**
* 搜索所有的在线用户
* @param page 页号,从0开始
* @return
* 包序号
*/
public char searchUser(int page) {
if(user.isLoggedIn()) {
SearchUserPacket packet = new SearchUserPacket(user);
packet.setPage(page);
gate.send(MAIN_PORT, packet, false);
return packet.getSequence();
} else
return 0;
}
/**
* 自定义搜索用户
* @param page 页号
* @param qqNum 要搜索的QQ号字符串形式
* @param nick 要搜索的昵称
* @param email 要搜索的email
* @param matchEntire 字符串是否完全匹配
* @return
* 包序号
*/
public char searchUser(int page, String qqStr, String nick, String email) {
if(user.isLoggedIn()) {
SearchUserPacket packet = new SearchUserPacket(user);
packet.setSearchType(QQ.QQ_SEARCH_CUSTOM);
packet.setPage(page);
packet.setQQStr(qqStr);
packet.setNick(nick);
packet.setEmail(email);
gate.send(MAIN_PORT, packet, false);
return packet.getSequence();
} else
return 0;
}
/**
* 添加一个好友
* @param qqNum 要添加的人的QQ号
* @return 发送出的包的序号
*/
public char addFriend(int qqNum) {
if(user.isLoggedIn()) {
AddFriendExPacket packet = new AddFriendExPacket(user);
packet.setTo(qqNum);
gate.send(MAIN_PORT, packet, false);
return packet.getSequence();
}
return 0;
}
/**
* 删除一个好友
* @param qqNum 要删除的好友的QQ号
* @return 如果包发送成功则返回包序号,否则返回0
*/
public char deleteFriend(int qqNum) {
if(user.isLoggedIn()) {
DeleteFriendPacket packet = new DeleteFriendPacket(user);
packet.setTo(qqNum);
gate.send(MAIN_PORT, packet, true);
return packet.getSequence();
}
return 0;
}
/**
* 把某人的好友列表中的自己删除
* @param qqNum 我想把我自己删除的好友的QQ号
*/
public void removeSelfFrom(int qqNum) {
if(user.isLoggedIn()) {
RemoveSelfPacket packet = new RemoveSelfPacket(user);
packet.setRemoveFrom(qqNum);
gate.send(MAIN_PORT, packet, true);
}
}
/**
* 把好友从服务器端的好友列表中删除
*
* @param qqNum
* 要删除的好友QQ号
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -