/* * The Mana Server * Copyright (C) 2004-2010 The Mana World Development Team * * This file is part of The Mana Server. * * The Mana Server 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 * any later version. * * The Mana Server 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 The Mana Server. If not, see . */ #include #include #include #include #include "account-server/character.h" #include "account-server/storage.h" #include "chat-server/guildmanager.h" #include "chat-server/chatchannelmanager.h" #include "chat-server/chatclient.h" #include "chat-server/chathandler.h" #include "common/manaserv_protocol.h" #include "common/transaction.h" #include "net/connectionhandler.h" #include "net/messagein.h" #include "net/messageout.h" #include "net/netcomputer.h" #include "utils/logger.h" #include "utils/stringfilter.h" #include "utils/tokendispenser.h" using namespace ManaServ; void registerChatClient(const std::string &token, const std::string &name, int level) { auto p = new ChatHandler::Pending; p->character = name; p->level = level; chatHandler->mTokenCollector.addPendingConnect(token, p); } ChatHandler::ChatHandler(): mTokenCollector(this) { } bool ChatHandler::startListen(enet_uint16 port, const std::string &host) { LOG_INFO("Chat handler started:"); return ConnectionHandler::startListen(port, host); } void ChatHandler::deletePendingClient(ChatClient *c) { MessageOut msg(CPMSG_CONNECT_RESPONSE); msg.writeInt8(ERRMSG_TIME_OUT); // The computer will be deleted when the disconnect event is processed c->disconnect(msg); } void ChatHandler::deletePendingConnect(Pending *p) { delete p; } void ChatHandler::tokenMatched(ChatClient *client, Pending *p) { MessageOut msg(CPMSG_CONNECT_RESPONSE); client->characterName = p->character; client->accountLevel = p->level; CharacterData *c = storage->getCharacter(p->character); if (!c) { // character wasnt found msg.writeInt8(ERRMSG_FAILURE); } else { client->characterId = c->getDatabaseID(); delete p; msg.writeInt8(ERRMSG_OK); // Add chat client to player map mPlayerMap.insert(std::make_pair(client->characterName, client)); } client->send(msg); } NetComputer *ChatHandler::computerConnected(ENetPeer *peer) { return new ChatClient(peer); } void ChatHandler::computerDisconnected(NetComputer *comp) { auto computer = static_cast< ChatClient * >(comp); if (computer->characterName.empty()) { // Not yet fully logged in, remove it from pending clients. mTokenCollector.deletePendingClient(computer); } else { // Remove user from all channels. chatChannelManager->removeUserFromAllChannels(computer); // Remove user from party removeUserFromParty(*computer); // Notify guilds about him leaving guildManager->disconnectPlayer(computer); // Remove the character from the player map // need to do this after removing them from party // as that uses the player map mPlayerMap.erase(computer->characterName); } delete computer; } void ChatHandler::processMessage(NetComputer *comp, MessageIn &message) { ChatClient &computer = *static_cast< ChatClient * >(comp); if (computer.characterName.empty()) { if (message.getId() != PCMSG_CONNECT) return; std::string magic_token = message.readString(MAGIC_TOKEN_LENGTH); mTokenCollector.addPendingClient(magic_token, &computer); sendGuildRejoin(computer); return; } switch (message.getId()) { case PCMSG_CHAT: handleChatMessage(computer, message); break; case PCMSG_PRIVMSG: handlePrivMsgMessage(computer, message); break; case PCMSG_WHO: handleWhoMessage(computer); break; case PCMSG_ENTER_CHANNEL: handleEnterChannelMessage(computer, message); break; case PCMSG_USER_MODE: handleModeChangeMessage(computer, message); break; case PCMSG_KICK_USER: handleKickUserMessage(computer, message); case PCMSG_QUIT_CHANNEL: handleQuitChannelMessage(computer, message); break; case PCMSG_LIST_CHANNELS: handleListChannelsMessage(computer, message); break; case PCMSG_LIST_CHANNELUSERS: handleListChannelUsersMessage(computer, message); break; case PCMSG_TOPIC_CHANGE: handleTopicChange(computer, message); break; case PCMSG_DISCONNECT: handleDisconnectMessage(computer, message); break; case PCMSG_GUILD_CREATE: handleGuildCreate(computer, message); break; case PCMSG_GUILD_INVITE: handleGuildInvite(computer, message); break; case PCMSG_GUILD_ACCEPT: handleGuildAcceptInvite(computer, message); break; case PCMSG_GUILD_GET_MEMBERS: handleGuildGetMembers(computer, message); break; case PCMSG_GUILD_PROMOTE_MEMBER: handleGuildMemberLevelChange(computer, message); break; case PCMSG_GUILD_KICK_MEMBER: handleGuildKickMember(computer, message); case PCMSG_GUILD_QUIT: handleGuildQuit(computer, message); break; case PCMSG_PARTY_INVITE_ANSWER: handlePartyInviteAnswer(computer, message); break; case PCMSG_PARTY_QUIT: handlePartyQuit(computer); break; default: LOG_WARN("ChatHandler::processMessage, Invalid message type" << message.getId()); MessageOut result(XXMSG_INVALID); computer.send(result); break; } } void ChatHandler::handleCommand(ChatClient &computer, const std::string &command) { LOG_INFO("Chat: Received unhandled command: " << command); MessageOut result(CPMSG_ERROR); result.writeInt8(CHAT_UNHANDLED_COMMAND); computer.send(result); } void ChatHandler::warnPlayerAboutBadWords(ChatClient &computer) { // We could later count if the player is really often unpolite. MessageOut result(CPMSG_ERROR); result.writeInt8(CHAT_USING_BAD_WORDS); // The Channel computer.send(result); LOG_INFO(computer.characterName << " says bad words."); } void ChatHandler::handleChatMessage(ChatClient &client, MessageIn &msg) { std::string text = msg.readString(); // Pass it through the slang filter (false when it contains bad words) if (!stringFilter->filterContent(text)) { warnPlayerAboutBadWords(client); return; } short channelId = msg.readInt16(); ChatChannel *channel = chatChannelManager->getChannel(channelId); if (channel) { MessageOut result(CPMSG_PUBMSG); result.writeInt16(channelId); result.writeString(client.characterName); result.writeString(text); sendInChannel(channel, result); } } void ChatHandler::handleAnnounce(const std::string &message, int senderId, const std::string &senderName) { // We do not need to check for right permissions since the game server does // this. MessageOut result(CPMSG_ANNOUNCEMENT); result.writeString(message); result.writeString(senderName); sendToEveryone(result); if (!senderId) return; // Do not log scripted announcements // log transaction Transaction trans; trans.mCharacterId = senderId; trans.mAction = TRANS_MSG_ANNOUNCE; trans.mMessage = senderName + " announced: " + message; storage->addTransaction(trans); } void ChatHandler::handlePrivMsgMessage(ChatClient &client, MessageIn &msg) { std::string user = msg.readString(); std::string text = msg.readString(); if (!stringFilter->filterContent(text)) { warnPlayerAboutBadWords(client); return; } // We seek the player to whom the message is told and send it to her/him. sayToPlayer(client, user, text); } void ChatHandler::handleWhoMessage(ChatClient &client) { MessageOut reply(CPMSG_WHO_RESPONSE); std::map::iterator itr, itr_end; itr = mPlayerMap.begin(); itr_end = mPlayerMap.end(); while (itr != itr_end) { reply.writeString(itr->first); ++itr; } client.send(reply); } void ChatHandler::handleEnterChannelMessage(ChatClient &client, MessageIn &msg) { MessageOut reply(CPMSG_ENTER_CHANNEL_RESPONSE); std::string channelName = msg.readString(); std::string givenPassword = msg.readString(); ChatChannel *channel = nullptr; if (chatChannelManager->channelExists(channelName) || chatChannelManager->tryNewPublicChannel(channelName)) { channel = chatChannelManager->getChannel(channelName); } if (!channel) { reply.writeInt8(ERRMSG_INVALID_ARGUMENT); } else if (!channel->getPassword().empty() && channel->getPassword() != givenPassword) { // Incorrect password (should probably have its own return value) reply.writeInt8(ERRMSG_INSUFFICIENT_RIGHTS); } else if (!channel->canJoin()) { reply.writeInt8(ERRMSG_INVALID_ARGUMENT); } else { if (channel->addUser(&client)) { reply.writeInt8(ERRMSG_OK); // The user entered the channel, now give him the channel // id, the announcement string and the user list. reply.writeInt16(channel->getId()); reply.writeString(channelName); reply.writeString(channel->getAnnouncement()); const ChatChannel::ChannelUsers &users = channel->getUserList(); for (auto chatClient : users) { reply.writeString(chatClient->characterName); reply.writeString(channel->getUserMode(chatClient)); } // Send an CPMSG_UPDATE_CHANNEL to warn other clients a user went // in the channel. warnUsersAboutPlayerEventInChat(channel, client.characterName, CHAT_EVENT_NEW_PLAYER); // log transaction Transaction trans; trans.mCharacterId = client.characterId; trans.mAction = TRANS_CHANNEL_JOIN; trans.mMessage = "User joined " + channelName; storage->addTransaction(trans); } else { reply.writeInt8(ERRMSG_FAILURE); } } client.send(reply); } void ChatHandler::handleModeChangeMessage(ChatClient &client, MessageIn &msg) { short channelId = msg.readInt16(); ChatChannel *channel = chatChannelManager->getChannel(channelId); if (channelId == 0 || !channel) { // invalid channel return; } if (channel->getUserMode(&client).find('o') == std::string::npos) { // invalid permissions return; } // get the user whos mode has been changed std::string user = msg.readString(); // get the mode to change to unsigned char mode = msg.readInt8(); channel->setUserMode(getClient(user), mode); // set the info to pass to all channel clients std::stringstream info; info << client.characterName << ":" << user << ":" << mode; warnUsersAboutPlayerEventInChat(channel, info.str(), CHAT_EVENT_MODE_CHANGE); // log transaction Transaction trans; trans.mCharacterId = client.characterId; trans.mAction = TRANS_CHANNEL_MODE; trans.mMessage = "User mode "; trans.mMessage.append(utils::toString(mode) + " set on " + user); storage->addTransaction(trans); } void ChatHandler::handleKickUserMessage(ChatClient &client, MessageIn &msg) { short channelId = msg.readInt16(); ChatChannel *channel = chatChannelManager->getChannel(channelId); if (channelId == 0 || !channel) { // invalid channel return; } if (channel->getUserMode(&client).find('o') == std::string::npos) { // invalid permissions return; } // get the user whos being kicked std::string user = msg.readString(); if (channel->removeUser(getClient(user))) { std::stringstream ss; ss << client.characterName << ":" << user; warnUsersAboutPlayerEventInChat(channel, ss.str(), CHAT_EVENT_KICKED_PLAYER); } // log transaction Transaction trans; trans.mCharacterId = client.characterId; trans.mAction = TRANS_CHANNEL_KICK; trans.mMessage = "User kicked " + user; storage->addTransaction(trans); } void ChatHandler::handleQuitChannelMessage(ChatClient &client, MessageIn &msg) { MessageOut reply(CPMSG_QUIT_CHANNEL_RESPONSE); short channelId = msg.readInt16(); ChatChannel *channel = chatChannelManager->getChannel(channelId); if (channelId == 0 || !channel) { reply.writeInt8(ERRMSG_INVALID_ARGUMENT); } else if (!channel->removeUser(&client)) { reply.writeInt8(ERRMSG_FAILURE); } else { reply.writeInt8(ERRMSG_OK); reply.writeInt16(channelId); // Send an CPMSG_UPDATE_CHANNEL to warn other clients a user left // the channel. warnUsersAboutPlayerEventInChat(channel, client.characterName, CHAT_EVENT_LEAVING_PLAYER); // log transaction Transaction trans; trans.mCharacterId = client.characterId; trans.mAction = TRANS_CHANNEL_QUIT; trans.mMessage = "User left " + channel->getName(); storage->addTransaction(trans); if (channel->getUserList().empty()) { chatChannelManager->removeChannel(channel->getId()); } } client.send(reply); } void ChatHandler::handleListChannelsMessage(ChatClient &client, MessageIn &) { MessageOut reply(CPMSG_LIST_CHANNELS_RESPONSE); const auto channels = chatChannelManager->getPublicChannels(); for (auto &channel : channels) { reply.writeString(channel->getName()); reply.writeInt16(channel->getUserList().size()); } client.send(reply); // log transaction Transaction trans; trans.mCharacterId = client.characterId; trans.mAction = TRANS_CHANNEL_LIST; storage->addTransaction(trans); } void ChatHandler::handleListChannelUsersMessage(ChatClient &client, MessageIn &msg) { MessageOut reply(CPMSG_LIST_CHANNELUSERS_RESPONSE); std::string channelName = msg.readString(); ChatChannel *channel = chatChannelManager->getChannel(channelName); if (channel) { reply.writeString(channel->getName()); for (auto user : channel->getUserList()) { reply.writeString(user->characterName); reply.writeString(channel->getUserMode(user)); } client.send(reply); } // log transaction Transaction trans; trans.mCharacterId = client.characterId; trans.mAction = TRANS_CHANNEL_USERLIST; storage->addTransaction(trans); } void ChatHandler::handleTopicChange(ChatClient &client, MessageIn &msg) { short channelId = msg.readInt16(); std::string topic = msg.readString(); ChatChannel *channel = chatChannelManager->getChannel(channelId); if (!guildManager->doesExist(channel->getName())) { chatChannelManager->setChannelTopic(channelId, topic); } else { guildChannelTopicChange(channel, client.characterId, topic); } // log transaction Transaction trans; trans.mCharacterId = client.characterId; trans.mAction = TRANS_CHANNEL_TOPIC; trans.mMessage = "User changed topic to " + topic; trans.mMessage.append(" in " + channel->getName()); storage->addTransaction(trans); } void ChatHandler::handleDisconnectMessage(ChatClient &client, MessageIn &) { MessageOut reply(CPMSG_DISCONNECT_RESPONSE); reply.writeInt8(ERRMSG_OK); chatChannelManager->removeUserFromAllChannels(&client); guildManager->disconnectPlayer(&client); client.send(reply); } void ChatHandler::sayToPlayer(ChatClient &computer, const std::string &playerName, const std::string &text) { // Send it to the being if the being exists MessageOut result(CPMSG_PRIVMSG); result.writeString(computer.characterName); result.writeString(text); for (auto &client : clients) { if (static_cast< ChatClient * >(client)->characterName == playerName) { client->send(result); break; } } } void ChatHandler::warnUsersAboutPlayerEventInChat(ChatChannel *channel, const std::string &info, char eventId) { MessageOut msg(CPMSG_CHANNEL_EVENT); msg.writeInt16(channel->getId()); msg.writeInt8(eventId); msg.writeString(info); sendInChannel(channel, msg); } void ChatHandler::sendInChannel(ChatChannel *channel, MessageOut &msg) { for (auto user : channel->getUserList()) { user->send(msg); } } ChatClient *ChatHandler::getClient(const std::string &name) const { auto itr = mPlayerMap.find(name); return (itr != mPlayerMap.end()) ? itr->second : nullptr; }