Skip to content

Commit 0574ac5

Browse files
authored
feature - (liyunfan1223#1100) Linking of "trusted" accounts to allow altbot-control apart from own account or guild (liyunfan1223#1267)
* Add table to store the security keys for accounts. * Add table to store relationships between accounts. * Add a new configuration option to enable or disable trusted account bots. * add checks for linked accounts * Handle account linking and chat commands * fix uppercase typo * change query & fix chatcommandtable * add missing functions to header * move account linking to updates dir * moved table creation to correct updates folder * use playerbots db instead of character db * fix db * fix install? * remove duplicated logic and add hashing to stored securityKey * add object before call * change chat variable * rename SQL file for correct execution order * add header include for ubuntu compatibility * remove old sql
1 parent b69ebfb commit 0574ac5

9 files changed

+299
-19
lines changed

conf/playerbots.conf.dist

+8-5
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
# MAGE
4242
# WARLOCK
4343
# DRUID
44-
# RANDOM BOT DEFAULT TALENT SPEC
44+
# RANDOM BOT DEFAULT TALENT SPEC
4545
# WARRIOR
4646
# PALADIN
4747
# HUNTER
@@ -54,7 +54,7 @@
5454
# DRUID
5555
# PLAYERBOT SYSTEM SETTINGS
5656
# DATABASE & CONNECTIONS
57-
# DEBUG
57+
# DEBUG
5858
# CHAT SETTINGS
5959
# LOGS
6060
# DEPRECIATED (TEMPORARY)
@@ -132,6 +132,9 @@ AiPlayerbot.AllowAccountBots = 1
132132
# Allow/deny bots in the player's guild
133133
AiPlayerbot.AllowGuildBots = 1
134134

135+
# Allow linking accounts for shared alt-bot control
136+
AiPlayerbot.AllowTrustedAccountBots = 1
137+
135138
# Random bot guild count
136139
AiPlayerbot.RandomBotGuildCount = 20
137140

@@ -790,7 +793,7 @@ AiPlayerbot.RandomBotAutoJoinBG = 0
790793
# Known issue: When enabling a lot of brackats in combination with multiple instances,
791794
# can lead to more instances created by bots than intended (over-queuing).
792795
#
793-
# This section controls the level brackets and
796+
# This section controls the level brackets and
794797
# automatic bot participation in battlegrounds and arenas.
795798
#
796799
# Brackets:
@@ -1179,7 +1182,7 @@ AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012
11791182
###################################
11801183

11811184
####################################################################################################
1182-
#
1185+
#
11831186
#
11841187
#
11851188

@@ -1231,7 +1234,7 @@ AiPlayerbot.WorldBuff.0.11.3.80.80 = 53760,57358 #DRUID FERAL
12311234
###################################
12321235

12331236
####################################################################################################
1234-
#
1237+
#
12351238
#
12361239
#
12371240

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
DROP TABLE IF EXISTS `playerbot_account_keys`;
2+
3+
CREATE TABLE `playerbot_account_keys` (
4+
`account_id` INT PRIMARY KEY,
5+
`security_key` VARCHAR(255) NOT NULL,
6+
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
7+
) ENGINE=INNODB DEFAULT CHARSET=latin1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
DROP TABLE IF EXISTS `playerbot_account_links`;
2+
3+
CREATE TABLE `playerbot_account_links` (
4+
`id` INT AUTO_INCREMENT PRIMARY KEY,
5+
`account_id` INT NOT NULL,
6+
`linked_account_id` INT NOT NULL,
7+
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
8+
UNIQUE KEY `account_link` (`account_id`, `linked_account_id`)
9+
) ENGINE=INNODB DEFAULT CHARSET=latin1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
DROP TABLE IF EXISTS `playerbot_account_links`;
2+
3+
CREATE TABLE `playerbot_account_links` (
4+
`id` INT AUTO_INCREMENT PRIMARY KEY,
5+
`account_id` INT NOT NULL,
6+
`linked_account_id` INT NOT NULL,
7+
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
8+
UNIQUE KEY `account_link` (`account_id`, `linked_account_id`)
9+
) ENGINE=INNODB DEFAULT CHARSET=latin1;
10+
11+
DROP TABLE IF EXISTS `playerbot_account_keys`;
12+
13+
CREATE TABLE `playerbot_account_keys` (
14+
`account_id` INT PRIMARY KEY,
15+
`security_key` VARCHAR(255) NOT NULL,
16+
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
17+
) ENGINE=INNODB DEFAULT CHARSET=latin1;

src/PlayerbotAIConfig.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ bool PlayerbotAIConfig::Initialize()
128128

129129
allowAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowAccountBots", true);
130130
allowGuildBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowGuildBots", true);
131+
allowTrustedAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowTrustedAccountBots", true);
131132
randomBotGuildNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGuildNearby", false);
132133
randomBotInvitePlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotInvitePlayer", false);
133134
inviteChat = sConfigMgr->GetOption<bool>("AiPlayerbot.InviteChat", false);
@@ -554,7 +555,7 @@ bool PlayerbotAIConfig::Initialize()
554555
{
555556
sRandomPlayerbotMgr->Init();
556557
}
557-
558+
558559
sRandomItemMgr->Init();
559560
sRandomItemMgr->InitAfterAhBot();
560561
sPlayerbotTextMgr->LoadBotTexts();

src/PlayerbotAIConfig.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class PlayerbotAIConfig
5555
bool IsInPvpProhibitedArea(uint32 id);
5656

5757
bool enabled;
58-
bool allowAccountBots, allowGuildBots;
58+
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
5959
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
6060
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
6161
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;

src/PlayerbotMgr.cpp

+143-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
#include <cstring>
1010
#include <istream>
1111
#include <string>
12+
#include <openssl/sha.h>
1213
#include <unordered_set>
14+
#include <iomanip>
1315

1416
#include "ChannelMgr.h"
1517
#include "CharacterCache.h"
@@ -93,21 +95,22 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
9395
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(playerGuid);
9496
if (!accountId)
9597
return;
96-
98+
9799
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
98100
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
99101

100102
bool isRndbot = !masterAccountId;
101103
bool sameAccount = sPlayerbotAIConfig->allowAccountBots && accountId == masterAccountId;
102104
Guild* guild = masterPlayer ? sGuildMgr->GetGuildById(masterPlayer->GetGuildId()) : nullptr;
103-
bool sameGuild = sPlayerbotAIConfig->allowGuildBots && guild && guild->GetMember(playerGuid);
105+
bool sameGuild = sPlayerbotAIConfig->allowGuildBots && guild && guild->GetMember(playerGuid);
104106
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(playerGuid.GetCounter());
107+
bool linkedAccount = sPlayerbotAIConfig->allowTrustedAccountBots && IsAccountLinked(accountId, masterAccountId);
105108

106109
bool allowed = true;
107110
std::ostringstream out;
108111
std::string botName;
109112
sCharacterCache->GetCharacterNameByGuid(playerGuid, botName);
110-
if (!isRndbot && !sameAccount && !sameGuild && !addClassBot)
113+
if (!isRndbot && !sameAccount && !sameGuild && !addClassBot && !linkedAccount)
111114
{
112115
allowed = false;
113116
out << "Failure: You are not allowed to control bot " << botName.c_str();
@@ -144,13 +147,20 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
144147
}
145148

146149
botLoading.insert(playerGuid);
147-
150+
148151
// Always login in with world session to avoid race condition
149152
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
150153
.AfterComplete([this](SQLQueryHolderBase const& holder)
151154
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
152155
}
153156

157+
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
158+
{
159+
QueryResult result = PlayerbotsDatabase.Query(
160+
"SELECT 1 FROM playerbot_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId);
161+
return result != nullptr;
162+
}
163+
154164
void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder)
155165
{
156166
uint32 botAccountId = holder.GetAccountId();
@@ -181,7 +191,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
181191
{
182192
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
183193
}
184-
194+
185195
sRandomPlayerbotMgr->OnPlayerLogin(bot);
186196
OnBotLogin(bot);
187197

@@ -425,7 +435,7 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
425435

426436
void PlayerbotHolder::RemoveFromPlayerbotsMap(ObjectGuid guid)
427437
{
428-
playerBots.erase(guid);
438+
playerBots.erase(guid);
429439
}
430440

431441
Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const
@@ -451,7 +461,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
451461

452462
sPlayerbotsMgr->AddPlayerbotData(bot, true);
453463
playerBots[bot->GetGUID()] = bot;
454-
464+
455465
OnBotLoginInternal(bot);
456466

457467
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
@@ -674,8 +684,11 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
674684
if (!accountId)
675685
return "character not found";
676686

677-
if (!sPlayerbotAIConfig->allowAccountBots && accountId != masterAccountId)
678-
return "you can only add bots from your own account";
687+
if (!sPlayerbotAIConfig->allowAccountBots && accountId != masterAccountId &&
688+
!(sPlayerbotAIConfig->allowTrustedAccountBots && IsAccountLinked(accountId, masterAccountId)))
689+
{
690+
return "you can only add bots from your own account or linked accounts";
691+
}
679692
}
680693

681694
AddPlayerBot(guid, masterAccountId);
@@ -701,12 +714,12 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
701714

702715
if (!bot)
703716
return "bot not found";
704-
717+
705718
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(guid.GetCounter());
706719

707720
if (!addClassBot)
708721
return "ERROR: You can not use this command on non-addclass bot.";
709-
722+
710723
if (!admin)
711724
{
712725
Player* master = ObjectAccessor::FindConnectedPlayer(masterguid);
@@ -732,7 +745,7 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
732745
{
733746
return "Initialization already in progress, please wait.";
734747
}
735-
748+
736749
int gs;
737750
if (cmd == "init=white" || cmd == "init=common")
738751
{
@@ -1705,3 +1718,121 @@ PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
17051718

17061719
return nullptr;
17071720
}
1721+
1722+
void PlayerbotMgr::HandleSetSecurityKeyCommand(Player* player, const std::string& key)
1723+
{
1724+
uint32 accountId = player->GetSession()->GetAccountId();
1725+
1726+
// Hash the security key using SHA-256
1727+
unsigned char hash[SHA256_DIGEST_LENGTH];
1728+
SHA256((unsigned char*)key.c_str(), key.size(), hash);
1729+
1730+
// Convert the hash to a hexadecimal string
1731+
std::ostringstream hashedKey;
1732+
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
1733+
hashedKey << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
1734+
1735+
// Store the hashed key in the database
1736+
PlayerbotsDatabase.Execute(
1737+
"REPLACE INTO playerbot_account_keys (account_id, security_key) VALUES ({}, '{}')",
1738+
accountId, hashedKey.str());
1739+
1740+
ChatHandler(player->GetSession()).PSendSysMessage("Security key set successfully.");
1741+
}
1742+
1743+
void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& accountName, const std::string& key)
1744+
{
1745+
QueryResult result = LoginDatabase.Query("SELECT id FROM account WHERE username = '{}'", accountName);
1746+
if (!result)
1747+
{
1748+
ChatHandler(player->GetSession()).PSendSysMessage("Account not found.");
1749+
return;
1750+
}
1751+
1752+
Field* fields = result->Fetch();
1753+
uint32 linkedAccountId = fields[0].Get<uint32>();
1754+
1755+
result = PlayerbotsDatabase.Query("SELECT security_key FROM playerbot_account_keys WHERE account_id = {}", linkedAccountId);
1756+
if (!result)
1757+
{
1758+
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
1759+
return;
1760+
}
1761+
1762+
// Hash the provided key
1763+
unsigned char hash[SHA256_DIGEST_LENGTH];
1764+
SHA256((unsigned char*)key.c_str(), key.size(), hash);
1765+
1766+
// Convert the hash to a hexadecimal string
1767+
std::ostringstream hashedKey;
1768+
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
1769+
hashedKey << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
1770+
1771+
// Compare the hashed key with the stored hashed key
1772+
std::string storedKey = result->Fetch()->Get<std::string>();
1773+
if (hashedKey.str() != storedKey)
1774+
{
1775+
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
1776+
return;
1777+
}
1778+
1779+
uint32 accountId = player->GetSession()->GetAccountId();
1780+
PlayerbotsDatabase.Execute(
1781+
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
1782+
accountId, linkedAccountId);
1783+
PlayerbotsDatabase.Execute(
1784+
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
1785+
linkedAccountId, accountId);
1786+
1787+
ChatHandler(player->GetSession()).PSendSysMessage("Account linked successfully.");
1788+
}
1789+
1790+
void PlayerbotMgr::HandleViewLinkedAccountsCommand(Player* player)
1791+
{
1792+
uint32 accountId = player->GetSession()->GetAccountId();
1793+
QueryResult result = PlayerbotsDatabase.Query("SELECT linked_account_id FROM playerbot_account_links WHERE account_id = {}", accountId);
1794+
1795+
if (!result)
1796+
{
1797+
ChatHandler(player->GetSession()).PSendSysMessage("No linked accounts.");
1798+
return;
1799+
}
1800+
1801+
ChatHandler(player->GetSession()).PSendSysMessage("Linked accounts:");
1802+
do
1803+
{
1804+
Field* fields = result->Fetch();
1805+
uint32 linkedAccountId = fields[0].Get<uint32>();
1806+
1807+
QueryResult accountResult = LoginDatabase.Query("SELECT username FROM account WHERE id = {}", linkedAccountId);
1808+
if (accountResult)
1809+
{
1810+
Field* accountFields = accountResult->Fetch();
1811+
std::string username = accountFields[0].Get<std::string>();
1812+
ChatHandler(player->GetSession()).PSendSysMessage("- {}", username.c_str());
1813+
}
1814+
else
1815+
{
1816+
ChatHandler(player->GetSession()).PSendSysMessage("- Unknown account");
1817+
}
1818+
} while (result->NextRow());
1819+
}
1820+
1821+
void PlayerbotMgr::HandleUnlinkAccountCommand(Player* player, const std::string& accountName)
1822+
{
1823+
QueryResult result = LoginDatabase.Query("SELECT id FROM account WHERE username = '{}'", accountName);
1824+
if (!result)
1825+
{
1826+
ChatHandler(player->GetSession()).PSendSysMessage("Account not found.");
1827+
return;
1828+
}
1829+
1830+
Field* fields = result->Fetch();
1831+
uint32 linkedAccountId = fields[0].Get<uint32>();
1832+
uint32 accountId = player->GetSession()->GetAccountId();
1833+
1834+
PlayerbotsDatabase.Execute("DELETE FROM playerbot_account_links WHERE (account_id = {} AND linked_account_id = {}) OR (account_id = {} AND linked_account_id = {})",
1835+
accountId, linkedAccountId, linkedAccountId, accountId);
1836+
1837+
ChatHandler(player->GetSession()).PSendSysMessage("Account unlinked successfully.");
1838+
}

src/PlayerbotMgr.h

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class PlayerbotHolder : public PlayerbotAIBase
2828
virtual ~PlayerbotHolder(){};
2929

3030
void AddPlayerBot(ObjectGuid guid, uint32 masterAccountId);
31+
bool IsAccountLinked(uint32 accountId, uint32 masterAccountId);
3132
void HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder);
3233

3334
void LogoutPlayerBot(ObjectGuid guid);
@@ -82,6 +83,11 @@ class PlayerbotMgr : public PlayerbotHolder
8283

8384
void SaveToDB();
8485

86+
void HandleSetSecurityKeyCommand(Player* player, const std::string& key);
87+
void HandleLinkAccountCommand(Player* player, const std::string& accountName, const std::string& key);
88+
void HandleViewLinkedAccountsCommand(Player* player);
89+
void HandleUnlinkAccountCommand(Player* player, const std::string& accountName);
90+
8591
protected:
8692
void OnBotLoginInternal(Player* const bot) override;
8793
void CheckTellErrors(uint32 elapsed);

0 commit comments

Comments
 (0)