/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * 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, see . */ #include "ClubService.h" #include "BattlenetRpcErrorCodes.h" #include "ClubMembershipService.h" #include "GameTime.h" #include "Guild.h" #include "Player.h" #include "SocialMgr.h" #include "api/client/v1/club_listener.pb.h" namespace Battlenet::Services { ClubService::ClubService(WorldSession* session) : BaseService(session) { } uint32 ClubService::HandleGetClubType(club::v1::client::GetClubTypeRequest const* request, club::v1::client::GetClubTypeResponse* response, std::function& /*continuation*/) { // We only support guilds for now. if (request->type().name() == "guild") { response->set_allocated_type(CreateGuildClubType().release()); return ERROR_OK; } return ERROR_NOT_IMPLEMENTED; } uint32 ClubService::HandleSubscribe(club::v1::client::SubscribeRequest const* /*request*/, NoData* /*response*/, std::function& /*continuation*/) { Player const* player = _session->GetPlayer(); if (!player) return ERROR_INTERNAL; Guild const* guild = player->GetGuild(); if (!guild) return ERROR_CLUB_NO_CLUB; // Subscibe the client to it's own guild club. club::v1::client::SubscribeNotification subscribeNotification; Guild::Member const* guildMember = guild->GetMember(player->GetGUID()); if (!guildMember) return ERROR_CLUB_NOT_MEMBER; Guild::Member const* guildLeader = guild->GetMember(guild->GetLeaderGUID()); if (!guildLeader) return ERROR_CLUB_NO_SUCH_MEMBER; subscribeNotification.set_club_id(guild->GetId()); subscribeNotification.set_allocated_agent_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release()); club::v1::client::Club* guildClub = subscribeNotification.mutable_club(); guildClub->set_id(guild->GetId()); guildClub->set_allocated_type(CreateGuildClubType().release()); guildClub->set_name(guild->GetName()); // These are not related to normal guild functionality so we hardcode them for now. guildClub->set_privacy_level(club::v1::PrivacyLevel::PRIVACY_LEVEL_OPEN); guildClub->set_visibility_level(club::v1::VISIBILITY_LEVEL_PRIVATE); guildClub->set_member_count(guild->GetMembersCount()); // Set the club leader, guild master in this case. club::v1::client::MemberDescription* guildLeaderDescription = guildClub->add_leader(); guildLeaderDescription->mutable_id()->set_account_id(guildLeader->GetAccountId()); guildLeaderDescription->mutable_id()->set_unique_id(guildLeader->GetGUID().GetCounter()); club::v1::client::Member* subscriber = subscribeNotification.mutable_member(); // The member sending the notification data. subscriber->set_allocated_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release()); // Community/Club default roles have slightly different values. // Also this is required to set the current leader/guild master symbol in the interface. // 1 = Owner, 4 = Member. Once communities are fully implemented these will go into a new database table. if (guildMember->IsRank(GuildRankId::GuildMaster)) subscriber->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner)); else if (guild->HasAnyRankRight(guildMember->GetRankId(), GuildRankRights(GR_RIGHT_OFFCHATLISTEN | GR_RIGHT_OFFCHATSPEAK))) subscriber->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator)); else subscriber->add_role(AsUnderlyingType(ClubRoleIdentifier::Member)); subscriber->set_presence_level(club::v1::client::PRESENCE_LEVEL_RICH); subscriber->set_whisper_level(club::v1::client::WHISPER_LEVEL_OPEN); // Member is online and active. subscriber->set_active(true); WorldserverService(_session).OnSubscribe(&subscribeNotification, true, true); // Notify the client about the changed club state. club::v1::client::SubscriberStateChangedNotification subscriberStateChangedNotification; subscriberStateChangedNotification.set_club_id(guild->GetId()); club::v1::client::SubscriberStateAssignment* assignment = subscriberStateChangedNotification.add_assignment(); assignment->set_allocated_member_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release()); // Member is online and active. assignment->set_active(true); WorldserverService(_session).OnSubscriberStateChanged(&subscriberStateChangedNotification, true, true); return ERROR_OK; } uint32 ClubService::HandleGetMembers(club::v1::client::GetMembersRequest const* /*request*/, club::v1::client::GetMembersResponse* response, std::function& /*continuation*/) { Player const* player = _session->GetPlayer(); if (!player) return ERROR_INTERNAL; Guild const* guild = player->GetGuild(); if (!guild) return ERROR_CLUB_NO_CLUB; response->mutable_member()->Reserve(guild->GetMembersCount()); for (auto const& [guid, member] : guild->GetMembers()) { club::v1::client::Member* clubMember = response->add_member(); clubMember->set_allocated_id(ClubMembershipService::CreateClubMemberId(guid).release()); // Community/Club default roles have slightly different values. // When communities are implemented those are going to be database fields. if (member.IsRank(GuildRankId::GuildMaster)) clubMember->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner)); else if (guild->HasAnyRankRight(member.GetRankId(), GuildRankRights(GR_RIGHT_OFFCHATLISTEN | GR_RIGHT_OFFCHATSPEAK))) clubMember->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator)); else clubMember->add_role(AsUnderlyingType(ClubRoleIdentifier::Member)); clubMember->set_presence_level(club::v1::client::PresenceLevel::PRESENCE_LEVEL_RICH); clubMember->set_whisper_level(club::v1::client::WhisperLevel::WHISPER_LEVEL_OPEN); clubMember->set_note(member.GetPublicNote()); clubMember->set_active(member.IsOnline()); } return ERROR_OK; } uint32 ClubService::HandleGetStreams(club::v1::client::GetStreamsRequest const* /*request*/, club::v1::client::GetStreamsResponse* response, std::function& /*continuation*/) { Player const* player = _session->GetPlayer(); if (!player) return ERROR_INTERNAL; Guild const* guild = player->GetGuild(); if (!guild) return ERROR_CLUB_NO_CLUB; // General guild channel. club::v1::client::Stream* generalGuildChannelStream = response->add_stream(); generalGuildChannelStream->set_club_id(guild->GetId()); generalGuildChannelStream->set_id(AsUnderlyingType(ClubStreamType::Guild)); v2::Attribute* generalStreamAttribute = generalGuildChannelStream->add_attribute(); generalStreamAttribute->set_name("global_strings_tag"); generalStreamAttribute->mutable_value()->set_string_value("COMMUNITIES_GUILD_GENERAL_CHANNEL_NAME"); generalGuildChannelStream->set_name("Guild"); // All roles got access to this channel. // Club roles are currently guild role + 1. // With a complete club/community system those will be handled differently. generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner)); generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Leader)); generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator)); generalGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Member)); // No voice support. generalGuildChannelStream->set_voice_level(club::v1::client::StreamVoiceLevel::VOICE_LEVEL_DISABLED); // Officer guild channel. club::v1::client::Stream* officerGuildChannelStream = response->add_stream(); officerGuildChannelStream->set_club_id(guild->GetId()); officerGuildChannelStream->set_id(AsUnderlyingType(ClubStreamType::Officer)); v2::Attribute* officerStreamAttribute = officerGuildChannelStream->add_attribute(); officerStreamAttribute->set_name("global_strings_tag"); officerStreamAttribute->mutable_value()->set_string_value("COMMUNITIES_GUILD_OFFICER_CHANNEL_NAME"); officerGuildChannelStream->set_name("Officer"); // All roles got access to this channel. // Club roles are currently guild role + 1. // With a complete club/community system those will be handled differently. officerGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Owner)); officerGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Leader)); officerGuildChannelStream->mutable_access()->add_role(AsUnderlyingType(ClubRoleIdentifier::Moderator)); // No voice support. officerGuildChannelStream->set_voice_level(club::v1::client::StreamVoiceLevel::VOICE_LEVEL_DISABLED); // Enable channel view club::v1::client::StreamView* generalView = response->add_view(); generalView->set_club_id(guild->GetId()); generalView->set_stream_id(AsUnderlyingType(ClubStreamType::Guild)); club::v1::client::StreamView* officerView = response->add_view(); officerView->set_club_id(guild->GetId()); officerView->set_stream_id(AsUnderlyingType(ClubStreamType::Officer)); return ERROR_OK; } uint32 ClubService::HandleSubscribeStream(club::v1::client::SubscribeStreamRequest const* request, NoData* /*response*/, std::function& /*continuation*/) { Player const* player = _session->GetPlayer(); if (!player) return ERROR_INTERNAL; Guild const* guild = player->GetGuild(); if (!guild) return ERROR_CLUB_NO_CLUB; // Basic sanity check until full communities are implemented. // 1 - Guild, 2 - Officer chat stream. if (request->stream_id().empty() || (request->stream_id().Get(0) != AsUnderlyingType(ClubStreamType::Guild) && request->stream_id().Get(0) != AsUnderlyingType(ClubStreamType::Officer))) return ERROR_CLUB_STREAM_NO_STREAM; return ERROR_OK; } uint32 ClubService::HandleUnsubscribeStream(club::v1::client::UnsubscribeStreamRequest const* /*request*/, NoData* /*response*/, std::function& /*continuation*/) { // We just have to signal the client that the unsubscribe request came through. return ERROR_OK; } uint32 ClubService::HandleSetStreamFocus(club::v1::client::SetStreamFocusRequest const* /*request*/, NoData* /*response*/, std::function& /*continuation*/) { Player const* player = _session->GetPlayer(); if (!player) return ERROR_INTERNAL; Guild const* guild = player->GetGuild(); if (!guild) return ERROR_CLUB_NOT_MEMBER; return ERROR_OK; } uint32 ClubService::HandleAdvanceStreamViewTime(club::v1::client::AdvanceStreamViewTimeRequest const* /*request*/, NoData* /*response*/, std::function& /*continuation*/) { Player const* player = _session->GetPlayer(); if (!player) return ERROR_INTERNAL; Guild const* guild = player->GetGuild(); if (!guild) return ERROR_CLUB_NOT_MEMBER; return ERROR_OK; } uint32 ClubService::HandleCreateMessage(club::v1::client::CreateMessageRequest const* request, club::v1::client::CreateMessageResponse* response, std::function& continuation) { // Basic sanity check until full communities are implemented. // 1 - Guild, 2 - Officer chat stream. if (request->stream_id() != AsUnderlyingType(ClubStreamType::Guild) && request->stream_id() != AsUnderlyingType(ClubStreamType::Officer)) return ERROR_CLUB_STREAM_NO_STREAM; // Just some sanity checks. We do not care about the requested stream for now since we only have two. Player const* player = _session->GetPlayer(); if (!player) return ERROR_INTERNAL; Guild const* guild = player->GetGuild(); if (!guild) return ERROR_CLUB_NO_CLUB; GuildRankRights requiredRights = { }; ChatMessageResult result = { }; switch (ClubStreamType(request->stream_id())) { case ClubStreamType::Guild: requiredRights = GR_RIGHT_GCHATLISTEN; result = _session->HandleChatMessage(CHAT_MSG_GUILD, LANG_UNIVERSAL, request->options().content()); break; case ClubStreamType::Officer: requiredRights = GR_RIGHT_OFFCHATLISTEN; result = _session->HandleChatMessage(CHAT_MSG_OFFICER, LANG_UNIVERSAL, request->options().content()); break; default: return ERROR_CLUB_STREAM_NO_STREAM; } if (result == ChatMessageResult::Ok) { std::chrono::microseconds messageTime = std::chrono::duration_cast(GameTime::GetSystemTime().time_since_epoch()); FillStreamMessage(response->mutable_message(), request->options().content(), messageTime, player->GetGUID()); club::v1::client::StreamMessageAddedNotification messageAddedNotification; messageAddedNotification.set_allocated_agent_id(ClubMembershipService::CreateClubMemberId(player->GetGUID()).release()); messageAddedNotification.set_club_id(guild->GetId()); messageAddedNotification.set_stream_id(request->stream_id()); FillStreamMessage(messageAddedNotification.mutable_message(), request->options().content(), messageTime, player->GetGUID()); guild->BroadcastWorker([&](Player const* receiver) { Guild::Member const* receiverMember = guild->GetMember(receiver->GetGUID()); if (!guild->HasAnyRankRight(receiverMember->GetRankId(), requiredRights)) return; if (receiver->GetSocial()->HasIgnore(player->GetGUID(), _session->GetAccountGUID())) return; WorldserverService(receiver->GetSession()).OnStreamMessageAdded(&messageAddedNotification, true, true); }, player); return ERROR_OK; } // If the message is empty there should never be a response to message request. continuation = nullptr; return ERROR_CLUB_STREAM_NO_SUCH_MESSAGE; } std::unique_ptr ClubService::CreateGuildClubType() { std::unique_ptr type = std::make_unique(); type->set_program(5730135); type->set_name("guild"); return type; } void ClubService::FillStreamMessage(club::v1::client::StreamMessage* message, std::string_view msg, std::chrono::microseconds messageTime, ObjectGuid author) { message->mutable_id()->set_epoch(messageTime.count()); message->mutable_id()->set_position(0); message->mutable_author()->set_allocated_id(ClubMembershipService::CreateClubMemberId(author).release()); club::v1::client::ContentChain* contentChain = message->add_content_chain(); contentChain->set_content(msg.data(), msg.size()); contentChain->set_edit_time(messageTime.count()); } }