/*
This file is part of leafdigital leafChat.
leafChat 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 3 of the License, or
(at your option) any later version.
leafChat 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 leafChat. If not, see <http://www.gnu.org/licenses/>.
Copyright 2012 Samuel Marshall.
*/
package com.leafdigital.irc;
import java.net.*;
import java.util.regex.*;
import com.leafdigital.irc.api.*;
import com.leafdigital.net.api.Network;
import com.leafdigital.prefs.api.*;
import leafchat.core.api.*;
/** Provides support for connection to IRC servers */
public class IRCPlugin implements Plugin, IRCPrefs
{
private PluginContext context;
private IgnoreListSingleton ignoreList;
private WatchListSingleton watchList;
private IRCEncodingSingleton ircEncoding;
private BasicCommands basicCommands;
IgnoreListSingleton getIgnoreListSingleton() { return ignoreList; }
@Override
public void init(PluginContext context, PluginLoadReporter plr) throws GeneralException
{
this.context = context;
new CommandListOwner(context);
context.registerSingleton(Connections.class, new ConnectionsSingleton(context, this));
context.registerSingleton(Commands.class, new CommandsSingleton(context));
new IRCMessageParser(context);
ignoreList = new IgnoreListSingleton(context);
context.registerSingleton(IgnoreList.class, ignoreList);
watchList = new WatchListSingleton(context);
context.registerSingleton(WatchList.class, watchList);
ircEncoding = new IRCEncodingSingleton(context);
context.registerSingleton(IRCEncoding.class, ircEncoding);
basicCommands = new BasicCommands(context);
updateOldIdentifyPrefs();
context.requestMessages(PingIRCMsg.class, this);
context.requestMessages(UserCTCPRequestIRCMsg.class, new CTCPHandler(context), Msg.PRIORITY_FIRST);
context.requestMessages(UserCommandMsg.class, this, Msg.PRIORITY_LAST);
context.requestMessages(UserCommandListMsg.class, this, Msg.PRIORITY_LAST);
context.requestMessages(ServerConnectedMsg.class, this, Msg.PRIORITY_FIRST);
context.requestMessages(NumericIRCMsg.class, this);
}
/** Pattern to find IRC-style address mask from welcome message */
private final static Pattern SPOTADDRESS=Pattern.compile(
"^(?:.*\\s)?\\S+!\\S+@(\\S+).*$");
/**
* Handle numerics to get welcome message which may inclue local IP address.
* @param m Message
*/
public void msg(NumericIRCMsg m)
{
// See if we can get the user's local address from welcome message, if
// so then tell the network plugin
if(m.getNumeric()==NumericIRCMsg.RPL_WELCOME)
{
String message=m.getParamISO(m.getParams().length-1);
Matcher check=SPOTADDRESS.matcher(message);
if(check.matches())
{
try
{
// Resolve our address
Network n=context.getSingle(Network.class);
n.reportPublicAddress(InetAddress.getByName(check.group(1)));
}
catch(UnknownHostException e)
{
// Ignore
}
}
}
}
/**
* Respond to server PING with PONG.
* @param m Message
*/
public void msg(PingIRCMsg m)
{
m.getServer().sendLine(
IRCMsg.constructBytes("PONG" + (m.getCode()!=null? " :" + m.getCode() : "")));
m.markHandled();
}
/**
* Fallback command handling that passes unknown commands directly to server.
* @param m Message
* @throws GeneralException
*/
public void msg(UserCommandMsg m) throws GeneralException
{
basicCommands.handle(m);
if(!(m.isHandled() || m.isStopped()))
{
// Fallback handling
if(m.getServer()!=null && m.getCommand()!=null)
{
if(!m.getServer().isConnected())
{
m.error("Not connected.");
}
else // Default just passes on to server
{
m.getServer().sendLine(IRCMsg.constructBytes(
m.getCommand().toUpperCase() + " ",
basicCommands.convertEncoding(m.getParams(), m.getServer(), null, null)));
}
}
else
{
m.error("Don't know how to handle this command.");
}
}
}
/**
* Fallback command listing that lists basic commands and standard IRC commands.
* @param m Message
* @throws GeneralException
*/
public void msg(UserCommandListMsg m) throws GeneralException
{
basicCommands.handle(m);
}
/**
* Sends password, nickname, and user in response to server connection.
* @param m Message
* @throws GeneralException
*/
public void msg(ServerConnectedMsg m) throws GeneralException
{
Server s = m.getServer();
// Send PASS line
String pass = s.getServerPassword();
if(pass != null)
{
s.sendLine(IRCMsg.constructBytes("PASS " + pass));
}
// Send NICK line
String nick = s.getDefaultNick();
if(nick != null)
{
s.sendLine(IRCMsg.constructBytes("NICK " + nick));
}
// Send USER line
String
user = s.getPreferences().getAnonHierarchical(PREF_USER, PREFDEFAULT_USER),
realName = s.getPreferences().getAnonHierarchical(PREF_REALNAME, PREFDEFAULT_REALNAME);
if(user != null)
{
s.sendLine(IRCMsg.constructBytes("USER " + user + " 0 * :",
basicCommands.convertEncoding(realName, s, null, null)));
}
}
@Override
public void close() throws GeneralException
{
watchList.close(); // Need to stop its timed event, though
((ConnectionsSingleton)context.getSingle(Connections.class)).closeAll();
}
@Override
public String toString()
{
return "IRC plugin";
}
/**
* Updates the format of identify command preferences, which changed in 2.2.
* @throws GeneralException
*/
private void updateOldIdentifyPrefs() throws GeneralException
{
Preferences p=context.getSingle(Preferences.class);
PreferencesGroup root=p.getGroup(p.getPluginOwner(this));
updateOldIdentifyPrefs(root.getChild(PREFGROUP_SERVERS));
}
private void updateOldIdentifyPrefs(PreferencesGroup group)
{
String old=group.get(PREF_IDENTIFYCOMMAND,null);
if(old!=null)
{
group.set(PREF_IDENTIFYPATTERN,old.trim()+" ${password}");
group.unset(PREF_IDENTIFYCOMMAND);
}
PreferencesGroup[] children=group.getAnon();
for(int i=0;i<children.length;i++)
{
updateOldIdentifyPrefs(children[i]);
}
}
}