Widened. Allow the chat mogrification to be turned off.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4494 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -63,16 +63,15 @@ import com.threerings.crowd.chat.data.UserMessage;
|
|||||||
import com.threerings.crowd.chat.data.UserSystemMessage;
|
import com.threerings.crowd.chat.data.UserSystemMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The chat director is the client side coordinator of all chat related
|
* The chat director is the client side coordinator of all chat related services. It handles both
|
||||||
* services. It handles both place constrained chat as well as direct
|
* place constrained chat as well as direct messaging.
|
||||||
* messaging.
|
|
||||||
*/
|
*/
|
||||||
public class ChatDirector extends BasicDirector
|
public class ChatDirector extends BasicDirector
|
||||||
implements ChatCodes, LocationObserver, MessageListener
|
implements ChatCodes, LocationObserver, MessageListener
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* An interface to receive information about the {@link #MAX_CHATTERS}
|
* An interface to receive information about the {@link #MAX_CHATTERS} most recent users that
|
||||||
* most recent users that we've been chatting with.
|
* we've been chatting with.
|
||||||
*/
|
*/
|
||||||
public static interface ChatterObserver
|
public static interface ChatterObserver
|
||||||
{
|
{
|
||||||
@@ -83,8 +82,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for those who would like to validate whether usernames
|
* An interface for those who would like to validate whether usernames may be added to the
|
||||||
* may be added to the chatter list.
|
* chatter list.
|
||||||
*/
|
*/
|
||||||
public static interface ChatterValidator
|
public static interface ChatterValidator
|
||||||
{
|
{
|
||||||
@@ -102,28 +101,23 @@ public class ChatDirector extends BasicDirector
|
|||||||
/**
|
/**
|
||||||
* Handles the specified chat command.
|
* Handles the specified chat command.
|
||||||
*
|
*
|
||||||
* @param speakSvc an optional SpeakService object representing
|
* @param speakSvc an optional SpeakService object representing the object to send the chat
|
||||||
* the object to send the chat message on.
|
* message on.
|
||||||
* @param command the slash command that was used to invoke this
|
* @param command the slash command that was used to invoke this handler
|
||||||
* handler (e.g. <code>/tell</code>).
|
* (e.g. <code>/tell</code>).
|
||||||
* @param args the arguments provided along with the command (e.g.
|
* @param args the arguments provided along with the command (e.g. <code>Bob hello</code>)
|
||||||
* <code>Bob hello</code>) or <code>null</code> if no arguments
|
* or <code>null</code> if no arguments were supplied.
|
||||||
* were supplied.
|
* @param history an in/out parameter that allows the command to modify the text that will
|
||||||
* @param history an in/out parameter that allows the command to
|
* be appended to the chat history. If this is set to null, nothing will be appended.
|
||||||
* modify the text that will be appended to the chat history. If
|
|
||||||
* this is set to null, nothing will be appended.
|
|
||||||
*
|
*
|
||||||
* @return an untranslated string that will be reported to the
|
* @return an untranslated string that will be reported to the chat box to convey an error
|
||||||
* chat box to convey an error response to the user, or {@link
|
* response to the user, or {@link ChatCodes#SUCCESS}.
|
||||||
* ChatCodes#SUCCESS}.
|
|
||||||
*/
|
*/
|
||||||
public abstract String handleCommand (
|
public abstract String handleCommand (SpeakService speakSvc, String command, String args,
|
||||||
SpeakService speakSvc, String command, String args,
|
String[] history);
|
||||||
String[] history);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this user should have access to this chat
|
* Returns true if this user should have access to this chat command.
|
||||||
* command.
|
|
||||||
*/
|
*/
|
||||||
public boolean checkAccess (BodyObject user)
|
public boolean checkAccess (BodyObject user)
|
||||||
{
|
{
|
||||||
@@ -132,14 +126,12 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a chat director and initializes it with the supplied
|
* Creates a chat director and initializes it with the supplied context. The chat director will
|
||||||
* context. The chat director will register itself as a location
|
* register itself as a location observer so that it can automatically process place
|
||||||
* observer so that it can automatically process place constrained
|
* constrained chat.
|
||||||
* chat.
|
|
||||||
*
|
*
|
||||||
* @param msgmgr the message manager via which we do our translations.
|
* @param msgmgr the message manager via which we do our translations.
|
||||||
* @param bundle the message bundle from which we obtain our
|
* @param bundle the message bundle from which we obtain our chat-related translation strings.
|
||||||
* chat-related translation strings.
|
|
||||||
*/
|
*/
|
||||||
public ChatDirector (CrowdContext ctx, MessageManager msgmgr, String bundle)
|
public ChatDirector (CrowdContext ctx, MessageManager msgmgr, String bundle)
|
||||||
{
|
{
|
||||||
@@ -169,9 +161,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the supplied chat display to the front of the chat display list. It
|
* Adds the supplied chat display to the front of the chat display list. It will subsequently
|
||||||
* will subsequently be notified of incoming chat messages as well as tell
|
* be notified of incoming chat messages as well as tell responses.
|
||||||
* responses.
|
|
||||||
*/
|
*/
|
||||||
public void pushChatDisplay (ChatDisplay display)
|
public void pushChatDisplay (ChatDisplay display)
|
||||||
{
|
{
|
||||||
@@ -179,9 +170,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the supplied chat display to the end of the chat display list. It
|
* Adds the supplied chat display to the end of the chat display list. It will subsequently be
|
||||||
* will subsequently be notified of incoming chat messages as well as tell
|
* notified of incoming chat messages as well as tell responses.
|
||||||
* responses.
|
|
||||||
*/
|
*/
|
||||||
public void addChatDisplay (ChatDisplay display)
|
public void addChatDisplay (ChatDisplay display)
|
||||||
{
|
{
|
||||||
@@ -189,8 +179,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the specified chat display from the chat display list. The
|
* Removes the specified chat display from the chat display list. The display will no longer
|
||||||
* display will no longer receive chat related notifications.
|
* receive chat related notifications.
|
||||||
*/
|
*/
|
||||||
public void removeChatDisplay (ChatDisplay display)
|
public void removeChatDisplay (ChatDisplay display)
|
||||||
{
|
{
|
||||||
@@ -198,9 +188,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the specified chat filter to the list of filters. All
|
* Adds the specified chat filter to the list of filters. All chat requests and receipts will
|
||||||
* chat requests and receipts will be filtered with all filters
|
* be filtered with all filters before they being sent or dispatched locally.
|
||||||
* before they being sent or dispatched locally.
|
|
||||||
*/
|
*/
|
||||||
public void addChatFilter (ChatFilter filter)
|
public void addChatFilter (ChatFilter filter)
|
||||||
{
|
{
|
||||||
@@ -216,8 +205,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an observer that watches the chatters list, and updates it
|
* Adds an observer that watches the chatters list, and updates it immediately.
|
||||||
* immediately.
|
|
||||||
*/
|
*/
|
||||||
public void addChatterObserver (ChatterObserver co)
|
public void addChatterObserver (ChatterObserver co)
|
||||||
{
|
{
|
||||||
@@ -234,27 +222,36 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the validator that decides if a username is valid to be
|
* Sets the validator that decides if a username is valid to be added to the chatter list, or
|
||||||
* added to the chatter list, or null if no such filtering is desired.
|
* null if no such filtering is desired.
|
||||||
*/
|
*/
|
||||||
public void setChatterValidator (ChatterValidator validator)
|
public void setChatterValidator (ChatterValidator validator)
|
||||||
{
|
{
|
||||||
_chatterValidator = validator;
|
_chatterValidator = validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the chat mogrifier. The mogrifier converts chat speak like LOL, WTF,
|
||||||
|
* etc. into phrases, words and can also transform them into emotes. The mogrifier is
|
||||||
|
* configured via the <code>x.mogrifies</code> and <code>x.transforms</code> translation
|
||||||
|
* properties.
|
||||||
|
*/
|
||||||
|
public void setMogrifyChat (boolean mogrifyChat)
|
||||||
|
{
|
||||||
|
_mogrifyChat = mogrifyChat;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a chat command handler.
|
* Registers a chat command handler.
|
||||||
*
|
*
|
||||||
* @param msg the message bundle via which the slash command will be
|
* @param msg the message bundle via which the slash command will be translated (as
|
||||||
* translated (as <code>c.</code><i>command</i>). If no translation
|
* <code>c.</code><i>command</i>). If no translation exists the command will be
|
||||||
* exists the command will be <code>/</code><i>command</i>.
|
* <code>/</code><i>command</i>.
|
||||||
* @param command the name of the command that will be used to invoke
|
* @param command the name of the command that will be used to invoke this handler
|
||||||
* this handler (e.g. <code>tell</code> if the command will be invoked
|
* (e.g. <code>tell</code> if the command will be invoked as <code>/tell</code>).
|
||||||
* as <code>/tell</code>).
|
|
||||||
* @param handler the chat command handler itself.
|
* @param handler the chat command handler itself.
|
||||||
*/
|
*/
|
||||||
public void registerCommandHandler (
|
public void registerCommandHandler (MessageBundle msg, String command, CommandHandler handler)
|
||||||
MessageBundle msg, String command, CommandHandler handler)
|
|
||||||
{
|
{
|
||||||
String key = "c." + command;
|
String key = "c." + command;
|
||||||
if (msg.exists(key)) {
|
if (msg.exists(key)) {
|
||||||
@@ -277,8 +274,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the chat history entry at the specified index,
|
* Get the chat history entry at the specified index, with 0 being the oldest.
|
||||||
* with 0 being the oldest.
|
|
||||||
*/
|
*/
|
||||||
public String getCommandHistory (int index)
|
public String getCommandHistory (int index)
|
||||||
{
|
{
|
||||||
@@ -307,11 +303,11 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a system INFO message as if it had come from the server.
|
* Display a system INFO message as if it had come from the server. The localtype of the
|
||||||
* The localtype of the message will be PLACE_CHAT_TYPE.
|
* message will be PLACE_CHAT_TYPE.
|
||||||
*
|
*
|
||||||
* Info messages are sent when something happens that was neither
|
* Info messages are sent when something happens that was neither directly triggered by the
|
||||||
* directly triggered by the user, nor requires direct action.
|
* user, nor requires direct action.
|
||||||
*/
|
*/
|
||||||
public void displayInfo (String bundle, String message)
|
public void displayInfo (String bundle, String message)
|
||||||
{
|
{
|
||||||
@@ -321,8 +317,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
/**
|
/**
|
||||||
* Display a system INFO message as if it had come from the server.
|
* Display a system INFO message as if it had come from the server.
|
||||||
*
|
*
|
||||||
* Info messages are sent when something happens that was neither
|
* Info messages are sent when something happens that was neither directly triggered by the
|
||||||
* directly triggered by the user, nor requires direct action.
|
* user, nor requires direct action.
|
||||||
*/
|
*/
|
||||||
public void displayInfo (String bundle, String message, String localtype)
|
public void displayInfo (String bundle, String message, String localtype)
|
||||||
{
|
{
|
||||||
@@ -330,29 +326,27 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a system FEEDBACK message as if it had come from the server.
|
* Display a system FEEDBACK message as if it had come from the server. The localtype of the
|
||||||
* The localtype of the message will be PLACE_CHAT_TYPE.
|
* message will be PLACE_CHAT_TYPE.
|
||||||
*
|
*
|
||||||
* Feedback messages are sent in direct response to a user action,
|
* Feedback messages are sent in direct response to a user action, usually to indicate success
|
||||||
* usually to indicate success or failure of the user's action.
|
* or failure of the user's action.
|
||||||
*/
|
*/
|
||||||
public void displayFeedback (String bundle, String message)
|
public void displayFeedback (String bundle, String message)
|
||||||
{
|
{
|
||||||
displaySystem(
|
displaySystem(bundle, message, SystemMessage.FEEDBACK, PLACE_CHAT_TYPE);
|
||||||
bundle, message, SystemMessage.FEEDBACK, PLACE_CHAT_TYPE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a system ATTENTION message as if it had come from the server.
|
* Display a system ATTENTION message as if it had come from the server. The localtype of the
|
||||||
* The localtype of the message will be PLACE_CHAT_TYPE.
|
* message will be PLACE_CHAT_TYPE.
|
||||||
*
|
*
|
||||||
* Attention messages are sent when something requires user action
|
* Attention messages are sent when something requires user action that did not result from
|
||||||
* that did not result from direct action by the user.
|
* direct action by the user.
|
||||||
*/
|
*/
|
||||||
public void displayAttention (String bundle, String message)
|
public void displayAttention (String bundle, String message)
|
||||||
{
|
{
|
||||||
displaySystem(
|
displaySystem(bundle, message, SystemMessage.ATTENTION, PLACE_CHAT_TYPE);
|
||||||
bundle, message, SystemMessage.ATTENTION, PLACE_CHAT_TYPE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -365,21 +359,18 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and delivers the supplied chat message. Slash command
|
* Parses and delivers the supplied chat message. Slash command processing and mogrification
|
||||||
* processing and mogrification are performed and the message is added
|
* are performed and the message is added to the chat history if appropriate.
|
||||||
* to the chat history if appropriate.
|
|
||||||
*
|
*
|
||||||
* @param speakSvc the SpeakService representing the target dobj of
|
* @param speakSvc the SpeakService representing the target dobj of the speak or null if we
|
||||||
* the speak or null if we should speak in the "default" way.
|
* should speak in the "default" way.
|
||||||
* @param text the text to be parsed and sent.
|
* @param text the text to be parsed and sent.
|
||||||
* @param record if text is a command, should it be added to the history?
|
* @param record if text is a command, should it be added to the history?
|
||||||
*
|
*
|
||||||
* @return <code>ChatCodes#SUCCESS</code> if the message was parsed
|
* @return <code>ChatCodes#SUCCESS</code> if the message was parsed and sent correctly, a
|
||||||
* and sent correctly, a translatable error string if there was some
|
* translatable error string if there was some problem.
|
||||||
* problem.
|
|
||||||
*/
|
*/
|
||||||
public String requestChat (
|
public String requestChat (SpeakService speakSvc, String text, boolean record)
|
||||||
SpeakService speakSvc, String text, boolean record)
|
|
||||||
{
|
{
|
||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
// split the text up into a command and arguments
|
// split the text up into a command and arguments
|
||||||
@@ -392,13 +383,11 @@ public class ChatDirector extends BasicDirector
|
|||||||
args = text.substring(sidx+1).trim();
|
args = text.substring(sidx+1).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
HashMap<String,CommandHandler> possibleCommands =
|
HashMap<String,CommandHandler> possibleCommands = getCommandHandlers(command);
|
||||||
getCommandHandlers(command);
|
|
||||||
switch (possibleCommands.size()) {
|
switch (possibleCommands.size()) {
|
||||||
case 0:
|
case 0:
|
||||||
StringTokenizer tok = new StringTokenizer(text);
|
StringTokenizer tok = new StringTokenizer(text);
|
||||||
return MessageBundle.tcompose(
|
return MessageBundle.tcompose("m.unknown_command", tok.nextToken());
|
||||||
"m.unknown_command", tok.nextToken());
|
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
Map.Entry<String,CommandHandler> entry =
|
Map.Entry<String,CommandHandler> entry =
|
||||||
@@ -406,8 +395,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
String cmdName = entry.getKey();
|
String cmdName = entry.getKey();
|
||||||
CommandHandler cmd = entry.getValue();
|
CommandHandler cmd = entry.getValue();
|
||||||
|
|
||||||
String result = cmd.handleCommand(
|
String result = cmd.handleCommand(speakSvc, cmdName, args, hist);
|
||||||
speakSvc, cmdName, args, hist);
|
|
||||||
if (!result.equals(ChatCodes.SUCCESS)) {
|
if (!result.equals(ChatCodes.SUCCESS)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -416,8 +404,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
// get the final history-ready command string
|
// get the final history-ready command string
|
||||||
hist[0] = "/" + ((hist[0] == null) ? command : hist[0]);
|
hist[0] = "/" + ((hist[0] == null) ? command : hist[0]);
|
||||||
|
|
||||||
// remove from history if it was present and
|
// remove from history if it was present and add it to the end
|
||||||
// add it to the end
|
|
||||||
addToHistory(hist[0]);
|
addToHistory(hist[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,13 +412,11 @@ public class ChatDirector extends BasicDirector
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
String alternativeCommands = "";
|
String alternativeCommands = "";
|
||||||
Iterator<String> itr = Collections.getSortedIterator(
|
Iterator<String> itr = Collections.getSortedIterator(possibleCommands.keySet());
|
||||||
possibleCommands.keySet());
|
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
alternativeCommands += " /" + itr.next();
|
alternativeCommands += " /" + itr.next();
|
||||||
}
|
}
|
||||||
return MessageBundle.tcompose(
|
return MessageBundle.tcompose("m.unspecific_command", alternativeCommands);
|
||||||
"m.unspecific_command", alternativeCommands);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,21 +431,18 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests that a speak message with the specified mode be generated
|
* Requests that a speak message with the specified mode be generated and delivered via the
|
||||||
* and delivered via the supplied speak service instance (which will
|
* supplied speak service instance (which will be associated with a particular "speak
|
||||||
* be associated with a particular "speak object"). The message will
|
* object"). The message will first be validated by all registered {@link ChatFilter}s (and
|
||||||
* first be validated by all registered {@link ChatFilter}s (and
|
|
||||||
* possibly vetoed) before being dispatched.
|
* possibly vetoed) before being dispatched.
|
||||||
*
|
*
|
||||||
* @param speakService the speak service to use when generating the
|
* @param speakService the speak service to use when generating the speak request or null if we
|
||||||
* speak request or null if we should speak in the current "place".
|
* should speak in the current "place".
|
||||||
* @param message the contents of the speak message.
|
* @param message the contents of the speak message.
|
||||||
* @param mode a speech mode that will be interpreted by the {@link
|
* @param mode a speech mode that will be interpreted by the {@link ChatDisplay}
|
||||||
* ChatDisplay} implementations that eventually display this speak
|
* implementations that eventually display this speak message.
|
||||||
* message.
|
|
||||||
*/
|
*/
|
||||||
public void requestSpeak (
|
public void requestSpeak (SpeakService speakService, String message, byte mode)
|
||||||
SpeakService speakService, String message, byte mode)
|
|
||||||
{
|
{
|
||||||
if (speakService == null) {
|
if (speakService == null) {
|
||||||
if (_place == null) {
|
if (_place == null) {
|
||||||
@@ -488,33 +470,25 @@ public class ChatDirector extends BasicDirector
|
|||||||
{
|
{
|
||||||
message = filter(message, null, true);
|
message = filter(message, null, true);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
displayFeedback(_bundle,
|
displayFeedback(_bundle, MessageBundle.compose("m.broadcast_failed", "m.filtered"));
|
||||||
MessageBundle.compose("m.broadcast_failed", "m.filtered"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_cservice.broadcast(_ctx.getClient(), message, new ChatService.InvocationListener() {
|
||||||
_cservice.broadcast(
|
public void requestFailed (String reason) {
|
||||||
_ctx.getClient(), message, new ChatService.InvocationListener() {
|
reason = MessageBundle.compose("m.broadcast_failed", reason);
|
||||||
public void requestFailed (String reason) {
|
displayFeedback(_bundle, reason);
|
||||||
reason = MessageBundle.compose(
|
}
|
||||||
"m.broadcast_failed", reason);
|
});
|
||||||
displayFeedback(_bundle, reason);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests that a tell message be delivered to the specified target
|
* Requests that a tell message be delivered to the specified target user.
|
||||||
* user.
|
|
||||||
*
|
*
|
||||||
* @param target the username of the user to which the tell message
|
* @param target the username of the user to which the tell message should be delivered.
|
||||||
* should be delivered.
|
|
||||||
* @param msg the contents of the tell message.
|
* @param msg the contents of the tell message.
|
||||||
* @param rl an optional result listener if you'd like to be notified
|
* @param rl an optional result listener if you'd like to be notified of success or failure.
|
||||||
* of success or failure.
|
|
||||||
*/
|
*/
|
||||||
public void requestTell (final Name target, String msg,
|
public void requestTell (final Name target, String msg, final ResultListener<Name> rl)
|
||||||
final ResultListener<Name> rl)
|
|
||||||
{
|
{
|
||||||
// make sure they can say what they want to say
|
// make sure they can say what they want to say
|
||||||
final String message = filter(msg, target, true);
|
final String message = filter(msg, target, true);
|
||||||
@@ -528,15 +502,13 @@ public class ChatDirector extends BasicDirector
|
|||||||
// create a listener that will report success or failure
|
// create a listener that will report success or failure
|
||||||
ChatService.TellListener listener = new ChatService.TellListener() {
|
ChatService.TellListener listener = new ChatService.TellListener() {
|
||||||
public void tellSucceeded (long idletime, String awayMessage) {
|
public void tellSucceeded (long idletime, String awayMessage) {
|
||||||
success(xlate(_bundle, MessageBundle.tcompose(
|
success(xlate(_bundle, MessageBundle.tcompose("m.told_format", target, message)));
|
||||||
"m.told_format", target, message)));
|
|
||||||
|
|
||||||
// if they have an away message, report that
|
// if they have an away message, report that
|
||||||
if (awayMessage != null) {
|
if (awayMessage != null) {
|
||||||
awayMessage = filter(awayMessage, target, false);
|
awayMessage = filter(awayMessage, target, false);
|
||||||
if (awayMessage != null) {
|
if (awayMessage != null) {
|
||||||
String msg = MessageBundle.tcompose(
|
String msg = MessageBundle.tcompose("m.recipient_afk", target, awayMessage);
|
||||||
"m.recipient_afk", target, awayMessage);
|
|
||||||
displayFeedback(_bundle, msg);
|
displayFeedback(_bundle, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -544,8 +516,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
// if they are idle, report that
|
// if they are idle, report that
|
||||||
if (idletime > 0L) {
|
if (idletime > 0L) {
|
||||||
// adjust by the time it took them to become idle
|
// adjust by the time it took them to become idle
|
||||||
idletime += _ctx.getConfig().getValue(
|
idletime += _ctx.getConfig().getValue(IDLE_TIME_KEY, DEFAULT_IDLE_TIME);
|
||||||
IDLE_TIME_KEY, DEFAULT_IDLE_TIME);
|
|
||||||
String msg = MessageBundle.compose(
|
String msg = MessageBundle.compose(
|
||||||
"m.recipient_idle", MessageBundle.taint(target),
|
"m.recipient_idle", MessageBundle.taint(target),
|
||||||
TimeUtil.getTimeOrderString(idletime, TimeUtil.MINUTE));
|
TimeUtil.getTimeOrderString(idletime, TimeUtil.MINUTE));
|
||||||
@@ -575,17 +546,15 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures a message that will be automatically reported to anyone
|
* Configures a message that will be automatically reported to anyone that sends a tell message
|
||||||
* that sends a tell message to this client to indicate that we are
|
* to this client to indicate that we are busy or away from the keyboard.
|
||||||
* busy or away from the keyboard.
|
|
||||||
*/
|
*/
|
||||||
public void setAwayMessage (String message)
|
public void setAwayMessage (String message)
|
||||||
{
|
{
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
message = filter(message, null, true);
|
message = filter(message, null, true);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
// they filtered away their own away message..
|
// they filtered away their own away message.. change it to something
|
||||||
// change it to something
|
|
||||||
message = "...";
|
message = "...";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -594,13 +563,12 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an additional object via which chat messages may arrive. The
|
* Adds an additional object via which chat messages may arrive. The chat director assumes the
|
||||||
* chat director assumes the caller will be managing the subscription
|
* caller will be managing the subscription to this object and will remain subscribed to it for
|
||||||
* to this object and will remain subscribed to it for as long as it
|
* as long as it remains in effect as an auxiliary chat source.
|
||||||
* remains in effect as an auxiliary chat source.
|
|
||||||
*
|
*
|
||||||
* @param localtype a type to be associated with all chat messages
|
* @param localtype a type to be associated with all chat messages that arrive on the specified
|
||||||
* that arrive on the specified DObject.
|
* DObject.
|
||||||
*/
|
*/
|
||||||
public void addAuxiliarySource (DObject source, String localtype)
|
public void addAuxiliarySource (DObject source, String localtype)
|
||||||
{
|
{
|
||||||
@@ -699,8 +667,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
addChatter(speaker);
|
addChatter(speaker);
|
||||||
|
|
||||||
// note whether or not we have an auto-response
|
// note whether or not we have an auto-response
|
||||||
BodyObject self = (BodyObject)
|
BodyObject self = (BodyObject)_ctx.getClient().getClientObject();
|
||||||
_ctx.getClient().getClientObject();
|
|
||||||
if (!StringUtil.isBlank(self.awayMessage)) {
|
if (!StringUtil.isBlank(self.awayMessage)) {
|
||||||
autoResponse = self.awayMessage;
|
autoResponse = self.awayMessage;
|
||||||
}
|
}
|
||||||
@@ -770,43 +737,38 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to determine whether we are permitted to post the supplied
|
* Called to determine whether we are permitted to post the supplied chat message. Derived
|
||||||
* chat message. Derived classes may wish to throttle chat or restrict
|
* classes may wish to throttle chat or restrict certain types in certain circumstances for
|
||||||
* certain types in certain circumstances for whatever reason.
|
* whatever reason.
|
||||||
*
|
*
|
||||||
* @return null if the chat is permitted, SUCCESS if the chat is permitted
|
* @return null if the chat is permitted, SUCCESS if the chat is permitted and has already been
|
||||||
* and has already been dealt with, or a translatable string
|
* dealt with, or a translatable string indicating the reason for rejection if not.
|
||||||
* indicating the reason for rejection if not.
|
|
||||||
*/
|
*/
|
||||||
protected String checkCanChat (
|
protected String checkCanChat (SpeakService speakSvc, String message, byte mode)
|
||||||
SpeakService speakSvc, String message, byte mode)
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delivers a plain chat message (not a slash command) on the
|
* Delivers a plain chat message (not a slash command) on the specified speak service in the
|
||||||
* specified speak service in the specified mode. The message will be
|
* specified mode. The message will be mogrified and filtered prior to delivery.
|
||||||
* mogrified and filtered prior to delivery.
|
|
||||||
*
|
*
|
||||||
* @return {@link ChatCodes#SUCCESS} if the message was delivered or a
|
* @return {@link ChatCodes#SUCCESS} if the message was delivered or a string indicating why it
|
||||||
* string indicating why it failed.
|
* failed.
|
||||||
*/
|
*/
|
||||||
protected String deliverChat (
|
protected String deliverChat (SpeakService speakSvc, String message, byte mode)
|
||||||
SpeakService speakSvc, String message, byte mode)
|
|
||||||
{
|
{
|
||||||
// run the message through our mogrification process
|
// run the message through our mogrification process
|
||||||
message = mogrifyChat(message, true, mode != ChatCodes.EMOTE_MODE);
|
message = mogrifyChat(message, true, mode != ChatCodes.EMOTE_MODE);
|
||||||
|
|
||||||
// mogrification may result in something being turned into a slash
|
// mogrification may result in something being turned into a slash command, in which case
|
||||||
// command, in which case we have to run everything through again
|
// we have to run everything through again from the start
|
||||||
// from the start
|
|
||||||
if (message.startsWith("/")) {
|
if (message.startsWith("/")) {
|
||||||
return requestChat(speakSvc, message, false);
|
return requestChat(speakSvc, message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure this client is not restricted from performing this
|
// make sure this client is not restricted from performing this chat message for some
|
||||||
// chat message for some reason or other
|
// reason or other
|
||||||
String errmsg = checkCanChat(speakSvc, message, mode);
|
String errmsg = checkCanChat(speakSvc, message, mode);
|
||||||
if (errmsg != null) {
|
if (errmsg != null) {
|
||||||
return errmsg;
|
return errmsg;
|
||||||
@@ -819,7 +781,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the specified command to the history.
|
* Adds the specified command to the history.
|
||||||
*/
|
*/
|
||||||
protected void addToHistory (String cmd)
|
protected void addToHistory (String cmd)
|
||||||
{
|
{
|
||||||
@@ -836,13 +798,12 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mogrify common literary crutches into more appealing chat or
|
* Mogrifies common literary crutches into more appealing chat or commands.
|
||||||
* commands.
|
|
||||||
*
|
*
|
||||||
* @param transformsAllowed if true, the chat may transformed into a
|
* @param transformsAllowed if true, the chat may transformed into a different mode. (lol ->
|
||||||
* different mode. (lol -> /emote laughs)
|
* /emote laughs)
|
||||||
* @param capFirst if true, the first letter of the text is
|
* @param capFirst if true, the first letter of the text is capitalized. This is not desired if
|
||||||
* capitalized. This is not desired if the chat is already an emote.
|
* the chat is already an emote.
|
||||||
*/
|
*/
|
||||||
protected String mogrifyChat (
|
protected String mogrifyChat (
|
||||||
String text, boolean transformsAllowed, boolean capFirst)
|
String text, boolean transformsAllowed, boolean capFirst)
|
||||||
@@ -876,12 +837,14 @@ public class ChatDirector extends BasicDirector
|
|||||||
protected StringBuffer mogrifyChat (
|
protected StringBuffer mogrifyChat (
|
||||||
StringBuffer buf, boolean transformsAllowed, boolean capFirst)
|
StringBuffer buf, boolean transformsAllowed, boolean capFirst)
|
||||||
{
|
{
|
||||||
// do the generic mogrifications and translations
|
if (_mogrifyChat) {
|
||||||
buf = translatedReplacements("x.mogrifies", buf);
|
// do the generic mogrifications and translations
|
||||||
|
buf = translatedReplacements("x.mogrifies", buf);
|
||||||
|
|
||||||
// perform themed expansions and transformations
|
// perform themed expansions and transformations
|
||||||
if (transformsAllowed) {
|
if (transformsAllowed) {
|
||||||
buf = translatedReplacements("x.transforms", buf);
|
buf = translatedReplacements("x.transforms", buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -906,8 +869,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do all the replacements (mogrifications) specified in the
|
* Do all the replacements (mogrifications) specified in the translation string specified by
|
||||||
* translation string specified by the key.
|
* the key.
|
||||||
*/
|
*/
|
||||||
protected StringBuffer translatedReplacements (String key, StringBuffer buf)
|
protected StringBuffer translatedReplacements (String key, StringBuffer buf)
|
||||||
{
|
{
|
||||||
@@ -920,8 +883,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
while (st.hasMoreTokens()) {
|
while (st.hasMoreTokens()) {
|
||||||
String pattern = st.nextToken();
|
String pattern = st.nextToken();
|
||||||
String replace = st.nextToken();
|
String replace = st.nextToken();
|
||||||
Matcher m = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).
|
Matcher m = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(buf);
|
||||||
matcher(buf);
|
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
buf = new StringBuffer();
|
buf = new StringBuffer();
|
||||||
m.appendReplacement(buf, replace);
|
m.appendReplacement(buf, replace);
|
||||||
@@ -945,14 +907,12 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a hashmap containing all command handlers that match the
|
* Returns a hashmap containing all command handlers that match the specified command (i.e. the
|
||||||
* specified command (i.e. the specified command is a prefix of their
|
* specified command is a prefix of their registered command string).
|
||||||
* registered command string).
|
|
||||||
*/
|
*/
|
||||||
protected HashMap<String,CommandHandler> getCommandHandlers (String command)
|
protected HashMap<String,CommandHandler> getCommandHandlers (String command)
|
||||||
{
|
{
|
||||||
HashMap<String,CommandHandler> matches =
|
HashMap<String,CommandHandler> matches = new HashMap<String,CommandHandler>();
|
||||||
new HashMap<String,CommandHandler>();
|
|
||||||
BodyObject user = (BodyObject)_ctx.getClient().getClientObject();
|
BodyObject user = (BodyObject)_ctx.getClient().getClientObject();
|
||||||
for (Map.Entry<String,CommandHandler> entry : _handlers.entrySet()) {
|
for (Map.Entry<String,CommandHandler> entry : _handlers.entrySet()) {
|
||||||
String cmd = entry.getKey();
|
String cmd = entry.getKey();
|
||||||
@@ -974,26 +934,22 @@ public class ChatDirector extends BasicDirector
|
|||||||
protected void addChatter (Name name)
|
protected void addChatter (Name name)
|
||||||
{
|
{
|
||||||
// check to see if the chatter validator approves..
|
// check to see if the chatter validator approves..
|
||||||
if ((_chatterValidator != null) &&
|
if ((_chatterValidator != null) && (!_chatterValidator.isChatterValid(name))) {
|
||||||
(!_chatterValidator.isChatterValid(name))) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean wasthere = _chatters.remove(name);
|
boolean wasthere = _chatters.remove(name);
|
||||||
_chatters.addFirst(name);
|
_chatters.addFirst(name);
|
||||||
|
|
||||||
if (!wasthere) {
|
if (!wasthere) {
|
||||||
if (_chatters.size() > MAX_CHATTERS) {
|
if (_chatters.size() > MAX_CHATTERS) {
|
||||||
_chatters.removeLast();
|
_chatters.removeLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyChatterObservers();
|
notifyChatterObservers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies all registered {@link ChatterObserver}s that the list of
|
* Notifies all registered {@link ChatterObserver}s that the list of chatters has changed.
|
||||||
* chatters has changed.
|
|
||||||
*/
|
*/
|
||||||
protected void notifyChatterObservers ()
|
protected void notifyChatterObservers ()
|
||||||
{
|
{
|
||||||
@@ -1013,9 +969,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
if (bundle != null && _msgmgr != null) {
|
if (bundle != null && _msgmgr != null) {
|
||||||
MessageBundle msgb = _msgmgr.getBundle(bundle);
|
MessageBundle msgb = _msgmgr.getBundle(bundle);
|
||||||
if (msgb == null) {
|
if (msgb == null) {
|
||||||
Log.warning(
|
Log.warning("No message bundle available to translate message " +
|
||||||
"No message bundle available to translate message " +
|
"[bundle=" + bundle + ", message=" + message + "].");
|
||||||
"[bundle=" + bundle + ", message=" + message + "].");
|
|
||||||
} else {
|
} else {
|
||||||
message = msgb.xlate(message);
|
message = msgb.xlate(message);
|
||||||
}
|
}
|
||||||
@@ -1026,11 +981,9 @@ public class ChatDirector extends BasicDirector
|
|||||||
/**
|
/**
|
||||||
* Display the specified system message as if it had come from the server.
|
* Display the specified system message as if it had come from the server.
|
||||||
*/
|
*/
|
||||||
protected void displaySystem (
|
protected void displaySystem (String bundle, String message, byte attLevel, String localtype)
|
||||||
String bundle, String message, byte attLevel, String localtype)
|
|
||||||
{
|
{
|
||||||
// nothing should be untranslated, so pass the default bundle if need
|
// nothing should be untranslated, so pass the default bundle if need be.
|
||||||
// be.
|
|
||||||
if (bundle == null) {
|
if (bundle == null) {
|
||||||
bundle = _bundle;
|
bundle = _bundle;
|
||||||
}
|
}
|
||||||
@@ -1041,8 +994,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks up and returns the message type associated with the specified
|
* Looks up and returns the message type associated with the specified oid.
|
||||||
* oid.
|
|
||||||
*/
|
*/
|
||||||
protected String getLocalType (int oid)
|
protected String getLocalType (int oid)
|
||||||
{
|
{
|
||||||
@@ -1058,8 +1010,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An operation that checks with all chat filters to properly filter
|
* An operation that checks with all chat filters to properly filter a message prior to sending
|
||||||
* a message prior to sending to the server or displaying.
|
* to the server or displaying.
|
||||||
*/
|
*/
|
||||||
protected static class FilterMessageOp
|
protected static class FilterMessageOp
|
||||||
implements ObserverList.ObserverOp<ChatFilter>
|
implements ObserverList.ObserverOp<ChatFilter>
|
||||||
@@ -1136,8 +1088,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle "/help help" and "/help someboguscommand"
|
// handle "/help help" and "/help someboguscommand"
|
||||||
HashMap<String,CommandHandler> possibleCommands =
|
HashMap<String,CommandHandler> possibleCommands = getCommandHandlers(hcmd);
|
||||||
getCommandHandlers(hcmd);
|
|
||||||
if (hcmd.equals("help") || possibleCommands.isEmpty()) {
|
if (hcmd.equals("help") || possibleCommands.isEmpty()) {
|
||||||
possibleCommands = getCommandHandlers("");
|
possibleCommands = getCommandHandlers("");
|
||||||
possibleCommands.remove("help"); // remove help from the list
|
possibleCommands.remove("help"); // remove help from the list
|
||||||
@@ -1147,9 +1098,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
switch (possibleCommands.size()) {
|
switch (possibleCommands.size()) {
|
||||||
case 1:
|
case 1:
|
||||||
Iterator<String> itr = possibleCommands.keySet().iterator();
|
Iterator<String> itr = possibleCommands.keySet().iterator();
|
||||||
// this is a little funny, but we display the feeback
|
// this is a little funny, but we display the feeback message by hand and return
|
||||||
// message by hand and return SUCCESS so that the chat
|
// SUCCESS so that the chat entry field doesn't think that we've failed and
|
||||||
// entry field doesn't think that we've failed and
|
|
||||||
// preserve our command text
|
// preserve our command text
|
||||||
displayFeedback(null, "m.usage_" + itr.next());
|
displayFeedback(null, "m.usage_" + itr.next());
|
||||||
return ChatCodes.SUCCESS;
|
return ChatCodes.SUCCESS;
|
||||||
@@ -1227,9 +1177,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
/** Implements <code>/tell</code>. */
|
/** Implements <code>/tell</code>. */
|
||||||
protected class TellHandler extends CommandHandler
|
protected class TellHandler extends CommandHandler
|
||||||
{
|
{
|
||||||
public String handleCommand (
|
public String handleCommand (SpeakService speakSvc, final String command,
|
||||||
SpeakService speakSvc, final String command, String args,
|
String args, String[] history)
|
||||||
String[] history)
|
|
||||||
{
|
{
|
||||||
if (StringUtil.isBlank(args)) {
|
if (StringUtil.isBlank(args)) {
|
||||||
return "m.usage_tell";
|
return "m.usage_tell";
|
||||||
@@ -1275,19 +1224,15 @@ public class ChatDirector extends BasicDirector
|
|||||||
|
|
||||||
// store the full command in the history, even if it was mistyped
|
// store the full command in the history, even if it was mistyped
|
||||||
final String histEntry = command + " " +
|
final String histEntry = command + " " +
|
||||||
(useQuotes ? ("\"" + target + "\"") : target.toString()) +
|
(useQuotes ? ("\"" + target + "\"") : target.toString()) + " " + message;
|
||||||
" " + message;
|
|
||||||
history[0] = histEntry;
|
history[0] = histEntry;
|
||||||
|
|
||||||
// request to send this text as a tell message
|
// request to send this text as a tell message
|
||||||
requestTell(target, escapeMessage(message),
|
requestTell(target, escapeMessage(message), new ResultListener<Name>() {
|
||||||
new ResultListener<Name>() {
|
|
||||||
public void requestCompleted (Name target) {
|
public void requestCompleted (Name target) {
|
||||||
// replace the full one in the history with just:
|
// replace the full one in the history with just: /tell "<handle>"
|
||||||
// /tell "<handle>"
|
|
||||||
String newEntry = "/" + command + " " +
|
String newEntry = "/" + command + " " +
|
||||||
(useQuotes ? ("\"" + target + "\"")
|
(useQuotes ? ("\"" + target + "\"") : String.valueOf(target)) + " ";
|
||||||
: String.valueOf(target)) + " ";
|
|
||||||
_history.remove(newEntry);
|
_history.remove(newEntry);
|
||||||
int dex = _history.lastIndexOf("/" + histEntry);
|
int dex = _history.lastIndexOf("/" + histEntry);
|
||||||
if (dex >= 0) {
|
if (dex >= 0) {
|
||||||
@@ -1305,8 +1250,8 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the tell into two strings, handle and message. If either
|
* Parse the tell into two strings, handle and message. If either one is null then the
|
||||||
* one is null then the parsing did not succeed.
|
* parsing did not succeed.
|
||||||
*/
|
*/
|
||||||
protected String[] parseTell (String args)
|
protected String[] parseTell (String args)
|
||||||
{
|
{
|
||||||
@@ -1331,9 +1276,9 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turn the user-entered string into a Name object, doing
|
* Turn the user-entered string into a Name object, doing any particular normalization we
|
||||||
* any particular normalization we want to do along the way
|
* want to do along the way so that "/tell Bob" and "/tell BoB" don't both show up in
|
||||||
* so that "/tell Bob" and "/tell BoB" don't both show up in history.
|
* history.
|
||||||
*/
|
*/
|
||||||
protected Name normalizeAsName (String handle)
|
protected Name normalizeAsName (String handle)
|
||||||
{
|
{
|
||||||
@@ -1341,8 +1286,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape or otherwise do any final processing on the message
|
* Escape or otherwise do any final processing on the message prior to sending it.
|
||||||
* prior to sending it.
|
|
||||||
*/
|
*/
|
||||||
protected String escapeMessage (String msg)
|
protected String escapeMessage (String msg)
|
||||||
{
|
{
|
||||||
@@ -1400,6 +1344,9 @@ public class ChatDirector extends BasicDirector
|
|||||||
/** The client object that we're listening to for tells. */
|
/** The client object that we're listening to for tells. */
|
||||||
protected ClientObject _clobj;
|
protected ClientObject _clobj;
|
||||||
|
|
||||||
|
/** Whether or not to run chat through the mogrifier. */
|
||||||
|
protected boolean _mogrifyChat= true;
|
||||||
|
|
||||||
/** A list of registered chat displays. */
|
/** A list of registered chat displays. */
|
||||||
protected ObserverList<ChatDisplay> _displays =
|
protected ObserverList<ChatDisplay> _displays =
|
||||||
new ObserverList<ChatDisplay>(ObserverList.SAFE_IN_ORDER_NOTIFY);
|
new ObserverList<ChatDisplay>(ObserverList.SAFE_IN_ORDER_NOTIFY);
|
||||||
|
|||||||
Reference in New Issue
Block a user