/** * Copyright (C) 2013 Alexander Szczuczko * * This file may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ package ca.szc.keratin.bot; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import net.engio.mbassy.listener.Handler; import org.pmw.tinylog.Logger; import ca.szc.keratin.bot.User.PrivLevel; import ca.szc.keratin.core.event.message.recieve.ReceiveChannelMode; import ca.szc.keratin.core.event.message.recieve.ReceiveJoin; import ca.szc.keratin.core.event.message.recieve.ReceivePart; import ca.szc.keratin.core.event.message.recieve.ReceiveReply; import ca.szc.keratin.core.net.message.IrcMessage; /** * Holds data for one channel */ public class Channel { private static final String OP_PREFIX = "@"; private static final String CODE_RPL_NAMREPLY = "353"; private final String name; private final String key; /** * Map with key of IRC nickname to corresponding User */ private final ConcurrentHashMap<String, User> nicks; private final Object nicksMutex = new Object(); /** * @param channelName The channel's name. Including the #. Cannot be null. * @param channelKey The channel's key. May be null if there is no key. */ public Channel( String channelName, String channelKey ) { this.name = channelName; this.key = channelKey; nicks = new ConcurrentHashMap<String, User>(); } /** * @return The channel's name. Including the #. Cannot be null. */ public String getName() { return name; } /** * @return The channel's key. May be null if there is no key. */ public String getKey() { return key; } /** * Get all of the nicks in the channel, regardless of privilege level. Requires an active connection. * * @return The NAMES list of this channel, or null on error. */ public List<String> getNicks() { LinkedList<String> nickList = new LinkedList<String>(); synchronized ( nicksMutex ) { for ( User user : nicks.values() ) { nickList.add( user.getNick() ); } } return nickList; } /** * Get the nicks of all regular users in the channel. Requires an active connection. * * @return The filtered NAMES list of this channel, or null on error. */ public List<String> getRegularNicks() { LinkedList<String> filteredList = new LinkedList<String>(); synchronized ( nicksMutex ) { for ( User user : nicks.values() ) { if ( user.getPrivLevel().equals( PrivLevel.Regular ) ) filteredList.add( user.getNick() ); } } return filteredList; } /** * Get the nicks of all operator users in the channel. Requires an active connection. * * @return The filtered NAMES list of this channel, or null on error. */ public List<String> getOperatorNicks() { LinkedList<String> filteredList = new LinkedList<String>(); synchronized ( nicksMutex ) { for ( User user : nicks.values() ) { if ( user.getPrivLevel().equals( PrivLevel.Op ) ) filteredList.add( user.getNick() ); } } return filteredList; } /** * @return true iff the nick is in the channel and is an op in the channel */ public boolean isOp( String nick ) { synchronized ( nicksMutex ) { if ( nicks.containsKey( nick ) ) { User user = nicks.get( nick ); if ( user.getPrivLevel().equals( PrivLevel.Op ) ) return true; } return false; } } /** * @return true iff the nick is in the channel and is a regular user (non-op) in the channel */ public boolean isRegular( String nick ) { synchronized ( nicksMutex ) { if ( nicks.containsKey( nick ) ) { User user = nicks.get( nick ); if ( user.getPrivLevel().equals( PrivLevel.Regular ) ) return true; } return false; } } @Override public String toString() { synchronized ( nicksMutex ) { return "Channel [name=" + name + ", key=" + key + ", nicks=" + nicks + "]"; } } private void setNickAs( String nick, PrivLevel privLevel ) { synchronized ( nicksMutex ) { if ( nicks.containsKey( nick ) ) { User user = nicks.get( nick ); if ( user.getPrivLevel().equals( privLevel ) ) { Logger.trace( getName() + " Nick already " + privLevel + ", doing nothing: " + nick ); } else { Logger.trace( getName() + " Changing nick privLevel to " + privLevel + ": " + nick ); user.setPrivLevel( privLevel ); } } else { Logger.trace( getName() + " New user, adding as " + privLevel + ": " + nick ); nicks.put( nick, new User( nick, privLevel ) ); } } } @Handler private void namesListing( ReceiveReply event ) { IrcMessage msg = event.getMessage(); String[] params = msg.getParams(); String replyNum = msg.getCommand(); if ( CODE_RPL_NAMREPLY.equals( replyNum ) ) { String channelName = params[2]; if ( name.equals( channelName ) ) { String nicksBlob = params[3]; if ( nicksBlob.startsWith( ":" ) ) nicksBlob = nicksBlob.substring( 1 ); String[] nicksArray = nicksBlob.split( " " ); synchronized ( nicksMutex ) { Logger.trace( "Processing names reply for channel: " + getName() ); for ( String nick : nicksArray ) { // Treating the NAMES listing as authoritative, may override existing values if ( nick.startsWith( OP_PREFIX ) ) { nick = nick.substring( 1 ); setNickAs( nick, PrivLevel.Op ); } else { setNickAs( nick, PrivLevel.Regular ); } } } } } } @Handler private void updateOnJoin( ReceiveJoin event ) { String nick = event.getJoiner(); // Only do something if the mode change is for this channel if ( name.equals( event.getChannel() ) ) { synchronized ( nicksMutex ) { Logger.trace( "Processing join in channel: " + getName() ); // Treating JOINs as non-authoritative, may not override existing values if ( !nicks.containsKey( nick ) ) { setNickAs( nick, PrivLevel.Regular ); } } } } @Handler private void updateOnPart( ReceivePart event ) { String nick = event.getParter(); // Only do something if the mode change is for this channel if ( name.equals( event.getChannel() ) ) { synchronized ( nicksMutex ) { Logger.trace( "Processing part in channel: " + getName() ); Logger.trace( "Removing nick: " + nick ); if ( nicks.remove( nick ) == null ) { Logger.trace( "Nick to be removed was not in the nicks collection: " + nick ); } } } } // TODO handle nick changes (transfer user data to new nick) @Handler private void updateOnMode( ReceiveChannelMode event ) { String flags = event.getFlags(); // Only do something if the mode change is for this channel if ( name.equals( event.getTarget() ) ) { boolean op = flags.startsWith( "+o" ); boolean deop = flags.startsWith( "-o" ); if ( op || deop ) { List<String> affectedNicks = event.getFlagParams(); synchronized ( nicksMutex ) { Logger.trace( "Processing mode update in channel: " + getName() ); for ( String nick : affectedNicks ) { if ( nick.startsWith( ":" ) ) nick = nick.substring( 1 ); if ( nicks.containsKey( nick ) ) { if ( op ) { setNickAs( nick, PrivLevel.Op ); } else if ( deop ) { setNickAs( nick, PrivLevel.Regular ); } } } } } } } }