Files
znc/modules/partyline.cpp
Kyle Fuller f706ac55c2 Display a 401 when a partyline user is not attached
Send private partyline messages to each client seperately using the
correct nickname for that client. It may differ if they are user clients
or network clients where they are not connected to an IRC server.

I have created macro's for the nickname prefix so this could easily be
changed.

Use 401 instead of 403 for user/channel not found

Fixes #21
2012-02-07 16:54:22 +00:00

620 lines
20 KiB
C++

/*
* Copyright (C) 2004-2012 See the AUTHORS file for details.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*/
#include <znc/User.h>
#include <znc/znc.h>
#include <znc/IRCNetwork.h>
// If you change these and it breaks, you get to keep the pieces
#define CHAN_PREFIX_1 "~"
#define CHAN_PREFIX_1C '~'
#define CHAN_PREFIX CHAN_PREFIX_1 "#"
#define NICK_PREFIX CString("?")
#define NICK_PREFIX_C '?'
class CPartylineChannel {
public:
CPartylineChannel(const CString& sName) { m_sName = sName.AsLower(); }
~CPartylineChannel() {}
const CString& GetTopic() const { return m_sTopic; }
const CString& GetName() const { return m_sName; }
const set<CString>& GetNicks() const { return m_ssNicks; }
void SetTopic(const CString& s) { m_sTopic = s; }
void AddNick(const CString& s) { m_ssNicks.insert(s); }
void DelNick(const CString& s) { m_ssNicks.erase(s); }
bool IsInChannel(const CString& s) { return m_ssNicks.find(s) != m_ssNicks.end(); }
protected:
CString m_sTopic;
CString m_sName;
set<CString> m_ssNicks;
};
class CPartylineMod : public CModule {
public:
MODCONSTRUCTOR(CPartylineMod) {}
virtual ~CPartylineMod() {
while (m_ssChannels.size()) {
delete *m_ssChannels.begin();
m_ssChannels.erase(m_ssChannels.begin());
}
}
virtual bool OnBoot() {
// The config is now read completely, so all Users are set up
Load();
return true;
}
virtual bool OnLoad(const CString& sArgs, CString& sMessage) {
const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
for (map<CString, CUser*>::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it) {
CUser* pUser = it->second;
for (vector<CIRCNetwork*>::const_iterator i = pUser->GetNetworks().begin(); i != pUser->GetNetworks().end(); ++i) {
CIRCNetwork* pNetwork = *i;
if (pNetwork->GetIRCSock()) {
if (pNetwork->GetChanPrefixes().find(CHAN_PREFIX_1) == CString::npos) {
pNetwork->PutUser(":" + GetIRCServer(pNetwork) + " 005 " + pNetwork->GetIRCNick().GetNick() + " CHANTYPES=" + pNetwork->GetChanPrefixes() + CHAN_PREFIX_1 " :are supported by this server.");
}
}
}
}
VCString vsChans;
VCString::const_iterator it;
sArgs.Split(" ", vsChans, false);
for (it = vsChans.begin(); it != vsChans.end(); ++it) {
if (it->Left(2) == CHAN_PREFIX) {
m_ssDefaultChans.insert(it->Left(32));
}
}
Load();
return true;
}
void Load() {
CString sAction, sKey;
CPartylineChannel* pChannel;
for (MCString::iterator it = BeginNV(); it != EndNV(); ++it) {
if (it->first.find(":") != CString::npos) {
sAction = it->first.Token(0, false, ":");
sKey = it->first.Token(1, true, ":");
} else {
// backwards compatibility for older NV data
sAction = "fixedchan";
sKey = it->first;
}
if (sAction == "fixedchan") {
// Sorry, this was removed
}
if (sAction == "topic") {
pChannel = FindChannel(sKey);
if (pChannel && !(it->second).empty()) {
PutChan(pChannel->GetNicks(), ":irc.znc.in TOPIC " + pChannel->GetName() + " :" + it->second);
pChannel->SetTopic(it->second);
}
}
}
return;
}
void SaveTopic(CPartylineChannel* pChannel) {
if (!pChannel->GetTopic().empty())
SetNV("topic:" + pChannel->GetName(), pChannel->GetTopic());
else
DelNV("topic:" + pChannel->GetName());
}
virtual EModRet OnDeleteUser(CUser& User) {
// Loop through each chan
for (set<CPartylineChannel*>::iterator it = m_ssChannels.begin(); it != m_ssChannels.end();) {
CPartylineChannel *pChan = *it;
// RemoveUser() might delete channels, so make sure our
// iterator doesn't break.
it++;
RemoveUser(&User, pChan, "KICK", "User deleted", true);
}
return CONTINUE;
}
virtual EModRet OnRaw(CString& sLine) {
if (sLine.Token(1) == "005") {
CString::size_type uPos = sLine.AsUpper().find("CHANTYPES=");
if (uPos != CString::npos) {
uPos = sLine.find(" ", uPos);
if (uPos == CString::npos)
sLine.append(CHAN_PREFIX_1);
else
sLine.insert(uPos, CHAN_PREFIX_1);
m_spInjectedPrefixes.insert(m_pNetwork);
}
}
return CONTINUE;
}
virtual void OnIRCDisconnected() {
m_spInjectedPrefixes.erase(m_pNetwork);
}
virtual void OnClientLogin() {
if (m_spInjectedPrefixes.find(m_pNetwork) == m_spInjectedPrefixes.end() && m_pNetwork && !m_pNetwork->GetChanPrefixes().empty()) {
m_pClient->PutClient(":" + GetIRCServer(m_pNetwork) + " 005 " + m_pClient->GetNick() + " CHANTYPES=" + m_pNetwork->GetChanPrefixes() + CHAN_PREFIX_1 " :are supported by this server.");
}
// Make sure this user is in the default channels
for (set<CString>::iterator a = m_ssDefaultChans.begin(); a != m_ssDefaultChans.end(); ++a) {
CPartylineChannel* pChannel = GetChannel(*a);
const CString& sNick = m_pUser->GetUserName();
if (pChannel->IsInChannel(sNick))
continue;
CString sHost = m_pUser->GetBindHost();
const set<CString>& ssNicks = pChannel->GetNicks();
if (sHost.empty()) {
sHost = "znc.in";
}
PutChan(ssNicks, ":" + NICK_PREFIX + sNick + "!" + m_pUser->GetIdent() + "@" + sHost + " JOIN " + *a, false);
pChannel->AddNick(sNick);
}
CString sNickMask = m_pClient->GetNickMask();
for (set<CPartylineChannel*>::iterator it = m_ssChannels.begin(); it != m_ssChannels.end(); ++it) {
const set<CString>& ssNicks = (*it)->GetNicks();
if ((*it)->IsInChannel(m_pUser->GetUserName())) {
m_pClient->PutClient(":" + sNickMask + " JOIN " + (*it)->GetName());
if (!(*it)->GetTopic().empty()) {
m_pClient->PutClient(":" + GetIRCServer(m_pNetwork) + " 332 " + m_pClient->GetNickMask() + " " + (*it)->GetName() + " :" + (*it)->GetTopic());
}
SendNickList(m_pUser, m_pNetwork, ssNicks, (*it)->GetName());
PutChan(ssNicks, ":*" + GetModName() + "!znc@znc.in MODE " + (*it)->GetName() + " +" + CString(m_pUser->IsAdmin() ? "o" : "v") + " " + NICK_PREFIX + m_pUser->GetUserName(), true);
}
}
}
virtual void OnClientDisconnect() {
if (!m_pUser->IsUserAttached() && !m_pUser->IsBeingDeleted()) {
for (set<CPartylineChannel*>::iterator it = m_ssChannels.begin(); it != m_ssChannels.end(); ++it) {
const set<CString>& ssNicks = (*it)->GetNicks();
if (ssNicks.find(m_pUser->GetUserName()) != ssNicks.end()) {
PutChan(ssNicks, ":*" + GetModName() + "!znc@znc.in MODE " + (*it)->GetName() + " -ov " + NICK_PREFIX + m_pUser->GetUserName() + " " + NICK_PREFIX + m_pUser->GetUserName(), true);
}
}
}
}
virtual EModRet OnUserRaw(CString& sLine) {
if (sLine.Equals("WHO " CHAN_PREFIX_1, false, 5)) {
return HALT;
} else if (sLine.Equals("MODE " CHAN_PREFIX_1, false, 6)) {
return HALT;
} else if (sLine.Equals("TOPIC " CHAN_PREFIX, false, 8)) {
CString sChannel = sLine.Token(1);
CString sTopic = sLine.Token(2, true);
sTopic.TrimPrefix(":");
CPartylineChannel* pChannel = FindChannel(sChannel);
if (pChannel && pChannel->IsInChannel(m_pUser->GetUserName())) {
const set<CString>& ssNicks = pChannel->GetNicks();
if (!sTopic.empty()) {
if (m_pUser->IsAdmin()) {
PutChan(ssNicks, ":" + m_pClient->GetNickMask() + " TOPIC " + sChannel + " :" + sTopic);
pChannel->SetTopic(sTopic);
SaveTopic(pChannel);
} else {
m_pUser->PutUser(":irc.znc.in 482 " + m_pClient->GetNick() + " " + sChannel + " :You're not channel operator");
}
} else {
sTopic = pChannel->GetTopic();
if (sTopic.empty()) {
m_pUser->PutUser(":irc.znc.in 331 " + m_pClient->GetNick() + " " + sChannel + " :No topic is set.");
} else {
m_pUser->PutUser(":irc.znc.in 332 " + m_pClient->GetNick() + " " + sChannel + " :" + sTopic);
}
}
} else {
m_pUser->PutUser(":irc.znc.in 442 " + m_pClient->GetNick() + " " + sChannel + " :You're not on that channel");
}
return HALT;
}
return CONTINUE;
}
virtual EModRet OnUserPart(CString& sChannel, CString& sMessage) {
if (sChannel.Left(1) != CHAN_PREFIX_1) {
return CONTINUE;
}
if (sChannel.Left(2) != CHAN_PREFIX) {
m_pClient->PutClient(":" + GetIRCServer(m_pNetwork) + " 401 " + m_pClient->GetNick() + " " + sChannel + " :No such channel");
return HALT;
}
CPartylineChannel* pChannel = FindChannel(sChannel);
PartUser(m_pUser, pChannel);
return HALT;
}
void PartUser(CUser* pUser, CPartylineChannel* pChannel,
const CString& sMessage = "") {
RemoveUser(pUser, pChannel, "PART", sMessage);
}
void RemoveUser(CUser* pUser, CPartylineChannel* pChannel, const CString& sCommand,
const CString& sMessage = "", bool bNickAsTarget = false) {
if (!pChannel || !pChannel->IsInChannel(pUser->GetUserName())) {
return;
}
CString sCmd = " " + sCommand + " ";
CString sMsg = sMessage;
if (!sMsg.empty())
sMsg = " :" + sMsg;
pChannel->DelNick(pUser->GetUserName());
const set<CString>& ssNicks = pChannel->GetNicks();
CString sHost = pUser->GetBindHost();
if (sHost.empty()) {
sHost = "znc.in";
}
if (bNickAsTarget) {
for (vector<CIRCNetwork*>::const_iterator i = pUser->GetNetworks().begin(); i != pUser->GetNetworks().end(); ++i) {
CIRCNetwork* pNetwork = *i;
pNetwork->PutUser(":" + pNetwork->GetIRCNick().GetNickMask() + sCmd
+ pChannel->GetName() + " " + pNetwork->GetIRCNick().GetNick() + sMsg);
}
for (vector<CClient*>::const_iterator i = pUser->GetUserClients().begin(); i != pUser->GetUserClients().end(); ++i) {
CClient* pClient = *i;
pClient->PutClient(":" + pClient->GetNickMask() + sCmd
+ pChannel->GetName() + " " + pClient->GetNick() + sMsg);
}
PutChan(ssNicks, ":" + NICK_PREFIX + pUser->GetUserName() + "!" + pUser->GetIdent() + "@" + sHost
+ sCmd + pChannel->GetName() + " " + NICK_PREFIX + pUser->GetUserName() + sMsg,
false, true, pUser);
} else {
for (vector<CIRCNetwork*>::const_iterator i = pUser->GetNetworks().begin(); i != pUser->GetNetworks().end(); ++i) {
CIRCNetwork* pNetwork = *i;
pNetwork->PutUser(":" + pNetwork->GetIRCNick().GetNickMask() + sCmd
+ pChannel->GetName() + sMsg);
}
for (vector<CClient*>::const_iterator i = pUser->GetUserClients().begin(); i != pUser->GetUserClients().end(); ++i) {
CClient* pClient = *i;
pClient->PutClient(":" + pClient->GetNickMask() + sCmd + pChannel->GetName() + sMsg);
}
PutChan(ssNicks, ":" + NICK_PREFIX + pUser->GetUserName() + "!" + pUser->GetIdent() + "@" + sHost
+ sCmd + pChannel->GetName() + sMsg, false, true, pUser);
}
if (ssNicks.empty()) {
delete pChannel;
m_ssChannels.erase(pChannel);
}
}
virtual EModRet OnUserJoin(CString& sChannel, CString& sKey) {
if (sChannel.Left(1) != CHAN_PREFIX_1) {
return CONTINUE;
}
if (sChannel.Left(2) != CHAN_PREFIX) {
m_pClient->PutClient(":" + GetIRCServer(m_pNetwork) + " 403 " + m_pClient->GetNick() + " " + sChannel + " :Channels look like " CHAN_PREFIX "znc");
return HALT;
}
sChannel = sChannel.Left(32);
CPartylineChannel* pChannel = GetChannel(sChannel);
JoinUser(m_pUser, pChannel);
return HALT;
}
void JoinUser(CUser* pUser, CPartylineChannel* pChannel) {
if (pChannel && !pChannel->IsInChannel(pUser->GetUserName())) {
const set<CString>& ssNicks = pChannel->GetNicks();
const CString& sNick = pUser->GetUserName();
pChannel->AddNick(sNick);
CString sHost = pUser->GetBindHost();
if (sHost.empty()) {
sHost = "znc.in";
}
for (vector<CIRCNetwork*>::const_iterator i = pUser->GetNetworks().begin(); i != pUser->GetNetworks().end(); ++i) {
CIRCNetwork* pNetwork = *i;
pNetwork->PutUser(":" + pNetwork->GetIRCNick().GetNickMask() + " JOIN " + pChannel->GetName());
}
for (vector<CClient*>::const_iterator i = pUser->GetUserClients().begin(); i != pUser->GetUserClients().end(); ++i) {
CClient* pClient = *i;
pClient->PutClient(":" + pClient->GetNickMask() + " JOIN " + pChannel->GetName());
}
PutChan(ssNicks, ":" + NICK_PREFIX + sNick + "!" + pUser->GetIdent() + "@" + sHost + " JOIN " + pChannel->GetName(), false, true, pUser);
if (!pChannel->GetTopic().empty()) {
for (vector<CIRCNetwork*>::const_iterator i = pUser->GetNetworks().begin(); i != pUser->GetNetworks().end(); ++i) {
CIRCNetwork* pNetwork = *i;
pNetwork->PutUser(":" + GetIRCServer(pNetwork) + " 332 " + pNetwork->GetIRCNick().GetNickMask() + " " + pChannel->GetName() + " :" + pChannel->GetTopic());
}
for (vector<CClient*>::const_iterator i = pUser->GetUserClients().begin(); i != pUser->GetUserClients().end(); ++i) {
CClient* pClient = *i;
pClient->PutClient(":irc.znc.in 332 " + pClient->GetNickMask() + " " + pChannel->GetName() + " :" + pChannel->GetTopic());
}
}
SendNickList(pUser, NULL, ssNicks, pChannel->GetName());
if (pUser->IsAdmin()) {
PutChan(ssNicks, ":*" + GetModName() + "!znc@znc.in MODE " + pChannel->GetName() + " +o " + NICK_PREFIX + pUser->GetUserName(), false, true, pUser);
}
}
}
virtual EModRet HandleMessage(const CString& sCmd, const CString& sTarget, const CString& sMessage) {
if (sTarget.empty()) {
return CONTINUE;
}
char cPrefix = sTarget[0];
if (cPrefix != CHAN_PREFIX_1C && cPrefix != NICK_PREFIX_C) {
return CONTINUE;
}
CString sHost = m_pUser->GetBindHost();
if (sHost.empty()) {
sHost = "znc.in";
}
if (cPrefix == CHAN_PREFIX_1C) {
if (FindChannel(sTarget) == NULL) {
m_pClient->PutClient(":" + GetIRCServer(m_pNetwork) + " 401 " + m_pClient->GetNick() + " " + sTarget + " :No such channel");
return HALT;
}
PutChan(sTarget, ":" + NICK_PREFIX + m_pUser->GetUserName() + "!" + m_pUser->GetIdent() + "@" + sHost + " " + sCmd + " " + sTarget + " :" + sMessage, true, false);
} else {
CString sNick = sTarget.LeftChomp_n(1);
CUser* pUser = CZNC::Get().FindUser(sNick);
if (pUser) {
vector<CClient*> vClients = pUser->GetAllClients();
if (vClients.empty()) {
m_pClient->PutClient(":" + GetIRCServer(m_pNetwork) + " 401 " + m_pClient->GetNick() + " " + sTarget + " :User is not attached: " + sNick + "");
return HALT;
}
for (vector<CClient*>::const_iterator it = vClients.begin(); it != vClients.end(); ++it) {
CClient* pClient = *it;
pClient->PutClient(":" + NICK_PREFIX + m_pUser->GetUserName() + "!" + m_pUser->GetIdent() + "@" + sHost + " " + sCmd + " " + pClient->GetNick() + " :" + sMessage);
}
} else {
m_pClient->PutClient(":" + GetIRCServer(m_pNetwork) + " 401 " + m_pClient->GetNick() + " " + sTarget + " :No such znc user: " + sNick + "");
}
}
return HALT;
}
virtual EModRet OnUserMsg(CString& sTarget, CString& sMessage) {
return HandleMessage("PRIVMSG", sTarget, sMessage);
}
virtual EModRet OnUserNotice(CString& sTarget, CString& sMessage) {
return HandleMessage("NOTICE", sTarget, sMessage);
}
virtual EModRet OnUserAction(CString& sTarget, CString& sMessage) {
return HandleMessage("PRIVMSG", sTarget, "\001ACTION " + sMessage + "\001");
}
virtual EModRet OnUserCTCP(CString& sTarget, CString& sMessage) {
return HandleMessage("PRIVMSG", sTarget, "\001" + sMessage + "\001");
}
virtual EModRet OnUserCTCPReply(CString& sTarget, CString& sMessage) {
return HandleMessage("NOTICE", sTarget, "\001" + sMessage + "\001");
}
virtual void OnModCommand(const CString& sLine) {
CString sCommand = sLine.Token(0);
if (sCommand.Equals("HELP")) {
CTable Table;
Table.AddColumn("Command");
Table.AddColumn("Arguments");
Table.AddColumn("Description");
Table.AddRow();
Table.SetCell("Command", "Help");
Table.SetCell("Arguments", "");
Table.SetCell("Description", "List all partyline commands");
Table.AddRow();
Table.SetCell("Command", "List");
Table.SetCell("Arguments", "");
Table.SetCell("Description", "List all open channels");
PutModule(Table);
} else if (sCommand.Equals("LIST")) {
if (!m_ssChannels.size()) {
PutModule("There are no open channels.");
return;
}
CTable Table;
Table.AddColumn("Channel");
Table.AddColumn("Users");
for (set<CPartylineChannel*>::const_iterator a = m_ssChannels.begin(); a != m_ssChannels.end(); ++a) {
Table.AddRow();
Table.SetCell("Channel", (*a)->GetName());
Table.SetCell("Users", CString((*a)->GetNicks().size()));
}
PutModule(Table);
} else if (sCommand.Equals("ADDFIXCHAN")) {
PutModule("Sorry, support for fixed channels was dropped");
} else if (sCommand.Equals("DELFIXCHAN")) {
PutModule("Sorry, support for fixed channels was dropped");
} else if (sCommand.Equals("LISTFIXCHANS")) {
PutModule("Sorry, support for fixed channels was dropped");
} else if (sCommand.Equals("LISTFIXUSERS")) {
PutModule("Sorry, support for fixed channels was dropped");
} else {
PutModule("Unknown command, try 'HELP'");
}
}
const CString GetIRCServer(CIRCNetwork *pNetwork) {
if (!pNetwork) {
return "irc.znc.in";
}
const CString& sServer = pNetwork->GetIRCServer();
if (!sServer.empty())
return sServer;
return "irc.znc.in";
}
bool PutChan(const CString& sChan, const CString& sLine, bool bIncludeCurUser = true, bool bIncludeClient = true, CUser* pUser = NULL, CClient* pClient = NULL) {
CPartylineChannel* pChannel = FindChannel(sChan);
if (pChannel != NULL) {
PutChan(pChannel->GetNicks(), sLine, bIncludeCurUser, bIncludeClient, pUser, pClient);
return true;
}
return false;
}
void PutChan(const set<CString>& ssNicks, const CString& sLine, bool bIncludeCurUser = true, bool bIncludeClient = true, CUser* pUser = NULL, CClient* pClient = NULL) {
const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
if (!pUser)
pUser = m_pUser;
if (!pClient)
pClient = m_pClient;
for (map<CString, CUser*>::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it) {
if (ssNicks.find(it->first) != ssNicks.end()) {
if (it->second == pUser) {
if (bIncludeCurUser) {
it->second->PutAllUser(sLine, NULL, (bIncludeClient ? NULL : pClient));
}
} else {
it->second->PutAllUser(sLine);
}
}
}
}
void PutUserIRCNick(CUser* pUser, const CString& sPre, const CString& sPost) {
const vector<CClient*>& vClients = pUser->GetAllClients();
vector<CClient*>::const_iterator it;
for (it = vClients.begin(); it != vClients.end(); ++it) {
(*it)->PutClient(sPre + (*it)->GetNick() + sPost);
}
}
void SendNickList(CUser* pUser, CIRCNetwork* pNetwork, const set<CString>& ssNicks, const CString& sChan) {
CString sNickList;
for (set<CString>::const_iterator it = ssNicks.begin(); it != ssNicks.end(); ++it) {
CUser* pChanUser = CZNC::Get().FindUser(*it);
if (pChanUser && pChanUser->IsUserAttached()) {
sNickList += (pChanUser->IsAdmin()) ? "@" : "+";
}
sNickList += NICK_PREFIX + (*it) + " ";
if (sNickList.size() >= 500) {
PutUserIRCNick(pUser, ":" + GetIRCServer(pNetwork) + " 353 ", " @ " + sChan + " :" + sNickList);
sNickList.clear();
}
}
if (sNickList.size()) {
PutUserIRCNick(pUser, ":" + GetIRCServer(pNetwork) + " 353 ", " @ " + sChan + " :" + sNickList);
}
PutUserIRCNick(pUser, ":" + GetIRCServer(pNetwork) + " 366 ", " " + sChan + " :End of /NAMES list.");
}
CPartylineChannel* FindChannel(const CString& sChan) {
CString sChannel = sChan.AsLower();
for (set<CPartylineChannel*>::iterator it = m_ssChannels.begin(); it != m_ssChannels.end(); ++it) {
if ((*it)->GetName().AsLower() == sChannel)
return *it;
}
return NULL;
}
CPartylineChannel* GetChannel(const CString& sChannel) {
CPartylineChannel* pChannel = FindChannel(sChannel);
if (pChannel == NULL) {
pChannel = new CPartylineChannel(sChannel.AsLower());
m_ssChannels.insert(pChannel);
}
return pChannel;
}
private:
set<CPartylineChannel*> m_ssChannels;
set<CIRCNetwork*> m_spInjectedPrefixes;
set<CString> m_ssDefaultChans;
};
template<> void TModInfo<CPartylineMod>(CModInfo& Info) {
Info.SetWikiPage("partyline");
}
GLOBALMODULEDEFS(CPartylineMod, "Internal channels and queries for users connected to znc")