From: Kyle Fuller Date: Tue, 12 Mar 2013 22:38:23 +0000 (+0000) Subject: Initial commit X-Git-Tag: 1.0.0~36 X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=e7b67171fed648059c69f43330e41e072e844dd8;p=znc-palaver.git Initial commit --- e7b67171fed648059c69f43330e41e072e844dd8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..845552d --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013 Kyle Fuller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0abc191 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Palaver ZNC Module + +Palaver ZNC module provides push notifications. + +## Compiling + + znc-build palaver.cpp + +## Installation + +Copy the compile ZNC module to your ZNC settings: + + $ cp palaver.sp ~/.znc/modules + +Now load the ZNC module: + + /msg *status loadmod palaver + diff --git a/palaver.cpp b/palaver.cpp new file mode 100644 index 0000000..72bfd9d --- /dev/null +++ b/palaver.cpp @@ -0,0 +1,556 @@ +/* + * ZNC Palaver Module + * + * Copyright (c) 2013 Kyle Fuller + * License under the MIT license + */ + +#define REQUIRESSL + +#include +#include +#include +#include +#include + + + +const char *kPLVCapability = "palaverapp.com"; +const char *kPLVCommand = "PALAVER"; +const char *kPLVPushEndpointKey = "PUSH-ENDPOINT"; +const char *kPLVMentionKeywordKey = "MENTION-KEYWORD"; +const char *kPLVMentionChannelKey = "MENTION-CHANNEL"; +const char *kPLVMentionNickKey = "MENTION-NICK"; +const char *kPLVIgnoreKeywordKey = "IGNORE-KEYWORD"; +const char *kPLVIgnoreChannelKey = "IGNORE-CHANNEL"; +const char *kPLVIgnoreNickKey = "IGNORE-NICK"; + + +class CDevice { +public: + CDevice(const CString &sToken) { + m_sToken = sToken; + m_bInNegotiation = false; + } + + CString GetVersion() const { + return m_sVersion; + } + + bool InNegotiation() const { + return m_bInNegotiation; + } + + void SetInNegotiation(bool inNegotiation) { + m_bInNegotiation = inNegotiation; + } + + void SetVersion(const CString &sVersion) { + m_sVersion = sVersion; + } + + CString GetToken() const { + return m_sToken; + } + + void SetPushEndpoint(const CString &sEndpoint) { + m_sPushEndpoint = sEndpoint; + } + + CString GetPushEndpoint() const { + return m_sPushEndpoint; + } + + bool HasClient(const CClient& client) const { + bool bHasClient = false; + + for (std::vector::const_iterator it = m_vClients.begin(); + it != m_vClients.end(); ++it) { + CClient *pCurrentClient = *it; + + if (&client == pCurrentClient) { + bHasClient = true; + break; + } + } + + return bHasClient; + } + + void AddClient(CClient& client) { + if (HasClient(client) == false) { + m_vClients.push_back(&client); + } + } + + void RemoveClient(const CClient& client) { + for (std::vector::iterator it = m_vClients.begin(); + it != m_vClients.end(); ++it) { + CClient *pCurrentClient = *it; + + if (&client == pCurrentClient) { + m_vClients.erase(it); + break; + } + } + } + + void AddNetwork(CIRCNetwork& network) { + CUser *user = network.GetUser(); + const CString& sUsername = user->GetUserName(); + + m_msvsNetworks[sUsername].push_back(network.GetName()); + } + + void RemoveNetwork(CIRCNetwork& network) { + const CUser *user = network.GetUser(); + const CString& sUsername = user->GetUserName(); + + std::map::iterator it = m_msvsNetworks.find(sUsername); + if (it != m_msvsNetworks.end()) { + VCString &networks = it->second; + + for (VCString::iterator it2 = networks.begin(); it2 != networks.end(); ++it2) { + CString &name = *it2; + + if (name.Equals(network.GetName())) { + networks.erase(it2); + break; + } + } + + if (networks.empty()) { + m_msvsNetworks.erase(it); + } + } + } + + bool HasNetwork(CIRCNetwork& network) { + bool hasNetwork = false; + + const CUser *user = network.GetUser(); + const CString& sUsername = user->GetUserName(); + + std::map::iterator it = m_msvsNetworks.find(sUsername); + if (it != m_msvsNetworks.end()) { + VCString &networks = it->second; + + for (VCString::iterator it2 = networks.begin(); it2 != networks.end(); ++it2) { + CString &name = *it2; + + if (name.Equals(network.GetName())) { + hasNetwork = true; + break; + } + } + } + + return hasNetwork; + } + + void ResetDevice() { + m_bInNegotiation = false; + m_sVersion = ""; + m_sPushEndpoint = ""; + + m_vMentionKeywords.clear(); + m_vMentionChannels.clear(); + m_vMentionNicks.clear(); + m_vIgnoreKeywords.clear(); + m_vIgnoreChannels.clear(); + m_vIgnoreNicks.clear(); + } + + void AddMentionKeyword(const CString& sKeyword) { + m_vMentionKeywords.push_back(sKeyword); + } + + void AddMentionChannel(const CString& sChannel) { + m_vMentionChannels.push_back(sChannel); + } + + void AddMentionNick(const CString& sNick) { + m_vMentionNicks.push_back(sNick); + } + + void AddIgnoreKeyword(const CString& sKeyword) { + m_vIgnoreKeywords.push_back(sKeyword); + } + + void AddIgnoreChannel(const CString& sChannel) { + m_vIgnoreChannels.push_back(sChannel); + } + + void AddIgnoreNick(const CString& sNick) { + m_vIgnoreNicks.push_back(sNick); + } + + bool HasMentionChannel(const CString& sChannel) const { + bool bResult = false; + + for (VCString::const_iterator it = m_vMentionChannels.begin(); + it != m_vMentionChannels.end(); ++it) { + const CString& channel = *it; + + if (channel.WildCmp(sChannel)) { + bResult = true; + break; + } + } + + return bResult; + } + + bool HasIgnoreChannel(const CString& sChannel) const { + bool bResult = false; + + for (VCString::const_iterator it = m_vIgnoreChannels.begin(); + it != m_vIgnoreChannels.end(); ++it) { + const CString& channel = *it; + + if (channel.WildCmp(sChannel)) { + bResult = true; + break; + } + } + + return bResult; + } + + bool HasMentionNick(const CString& sNick) const { + bool bResult = false; + + for (VCString::const_iterator it = m_vMentionNicks.begin(); + it != m_vMentionNicks.end(); ++it) { + const CString& nick = *it; + + if (nick.WildCmp(sNick)) { + bResult = true; + break; + } + } + + return bResult; + } + + bool HasIgnoreNick(const CString& sNick) const { + bool bResult = false; + + for (VCString::const_iterator it = m_vIgnoreNicks.begin(); + it != m_vIgnoreNicks.end(); ++it) { + const CString& nick = *it; + + if (nick.WildCmp(sNick)) { + bResult = true; + break; + } + } + + return bResult; + } + + bool IncludesMentionKeyword(const CString& sMessage, const CString &sNick) const { + bool bResult = false; + + for (VCString::const_iterator it = m_vMentionKeywords.begin(); + it != m_vMentionKeywords.end(); ++it) { + const CString& sKeyword = *it; + + if (sKeyword.Equals("{nick}") && sMessage.WildCmp(sNick)) { + bResult = true; + break; + } + + if (sMessage.WildCmp(sKeyword)) { + bResult = true; + break; + } + } + + return bResult; + } + + bool IncludesIgnoreKeyword(const CString& sMessage) const { + bool bResult = false; + + for (VCString::const_iterator it = m_vIgnoreKeywords.begin(); + it != m_vIgnoreKeywords.end(); ++it) { + const CString& sKeyword = *it; + + if (sMessage.WildCmp(sKeyword)) { + bResult = true; + break; + } + } + + return bResult; + } + +#pragma mark - Notifications + + void SendNotification(CModule& module, const CString& sSender, const CString& sNotification, const CChan *pChannel) { + // todo parse from m_sPushEndpoint + bool bUseTLS = true; + CString sHostname = "api.palaverapp.com"; + unsigned short uPort = 443; + CString sPath = "/1/push"; + + CString sJSON = "{"; + sJSON += "\"message\": \"" + sNotification.Replace_n("\"", "\\\"") + "\""; + sJSON += ",\"sender\": \"" + sSender.Replace_n("\"", "\\\"") + "\""; + if (pChannel) { + sJSON += ",\"channel\": \"" + pChannel->GetName().Replace_n("\"", "\\\"") + "\""; + } + sJSON += "}"; + + CSocket *pSocket = new CSocket(&module); + pSocket->Connect(sHostname, uPort, bUseTLS); + pSocket->Write("POST " + sPath + " HTTP/1.1\r\n"); + pSocket->Write("Host: " + sHostname + "\r\n"); + pSocket->Write("Authorization: Bearer " + GetToken() + "\r\n"); + pSocket->Write("Connection: close\r\n"); + pSocket->Write("User-Agent: ZNC\r\n"); + pSocket->Write("Content-Type: application/json\r\n"); + pSocket->Write("Content-Length: " + CString(sJSON.length()) + "\r\n"); + pSocket->Write("\r\n"); + pSocket->Write(sJSON); + pSocket->Close(Csock::CLT_AFTERWRITE); + module.AddSocket(pSocket); + } + +private: + CString m_sToken; + CString m_sVersion; + CString m_sPushEndpoint; + + std::map m_msvsNetworks; + + std::vector m_vClients; + + VCString m_vMentionKeywords; + VCString m_vMentionChannels; + VCString m_vMentionNicks; + + VCString m_vIgnoreKeywords; + VCString m_vIgnoreChannels; + VCString m_vIgnoreNicks; + + bool m_bInNegotiation; +}; + +class CPalaverMod : public CModule { +public: + MODCONSTRUCTOR(CPalaverMod) {} + +#pragma mark - Cap + + virtual void OnClientCapLs(CClient* pClient, SCString& ssCaps) { + ssCaps.insert(kPLVCapability); + } + + virtual bool IsClientCapSupported(CClient* pClient, const CString& sCap, bool bState) { + return sCap.Equals(kPLVCapability); + } + +#pragma mark - + + virtual EModRet OnUserRaw(CString& sLine) { + return HandleUserRaw(m_pClient, sLine); + } + + virtual EModRet OnUnknownUserRaw(CClient* pClient, CString& sLine) { + return HandleUserRaw(pClient, sLine); + } + + virtual EModRet HandleUserRaw(CClient* pClient, CString& sLine) { + if (sLine.Token(0).Equals(kPLVCommand)) { + CString sCommand = sLine.Token(1); + + if (sCommand.Equals("BACKGROUND")) { + m_pClient->SetAway(true); + } else if (sCommand.Equals("FOREGROUND")) { + m_pClient->SetAway(false); + } else if (sCommand.Equals("IDENTIFY")) { + CDevice *pDevice = DeviceForClient(*pClient); + if (pDevice) { + pDevice->RemoveClient(*pClient); + } + + CString sToken = sLine.Token(2); + CString sVersion = sLine.Token(3); + + CDevice& device = DeviceWithToken(sToken); + + if (device.InNegotiation() == false && device.GetVersion().Equals(sVersion) == false) { + pClient->PutClient("PALAVER REQ *"); + device.SetInNegotiation(true); + } + + device.AddClient(*pClient); + + if (m_pNetwork) { + device.AddNetwork(*m_pNetwork); + } + } else if (sCommand.Equals("BEGIN")) { + CString sToken = sLine.Token(2); + CString sVersion = sLine.Token(3); + CDevice& device = DeviceWithToken(sToken); + + device.ResetDevice(); + device.SetInNegotiation(true); + device.SetVersion(sVersion); + + device.AddClient(*pClient); + } else if (sCommand.Equals("END")) { + CDevice *pDevice = DeviceForClient(*pClient); + + if (pDevice) { + pDevice->SetInNegotiation(false); + } + } else if (sCommand.Equals("SET")) { + CString sKey = sLine.Token(2); + CString sValue = sLine.Token(3, true); + + CDevice *pDevice = DeviceForClient(*pClient); + + if (pDevice) { + if (sKey.Equals("VERSION")) { + pDevice->SetVersion(sValue); + } else if (sKey.Equals(kPLVPushEndpointKey)) { + pDevice->SetPushEndpoint(sValue); + } + } + } else if (sCommand.Equals("ADD")) { + CString sKey = sLine.Token(2); + CString sValue = sLine.Token(3, true); + + CDevice *pDevice = DeviceForClient(*pClient); + + if (pDevice) { + if (sKey.Equals(kPLVIgnoreKeywordKey)) { + pDevice->AddIgnoreKeyword(sValue); + } else if (sKey.Equals(kPLVIgnoreChannelKey)) { + pDevice->AddIgnoreChannel(sValue); + } else if (sKey.Equals(kPLVIgnoreNickKey)) { + pDevice->AddIgnoreNick(sValue); + } else if (sKey.Equals(kPLVMentionKeywordKey)) { + pDevice->AddMentionKeyword(sValue); + } else if (sKey.Equals(kPLVMentionChannelKey)) { + pDevice->AddMentionChannel(sValue); + } else if (sKey.Equals(kPLVMentionNickKey)) { + pDevice->AddMentionNick(sValue); + } + } + } + + return HALT; + } + + return CONTINUE; + } + +#pragma mark - + + virtual void OnClientLogin() { + // Associate client with the user/network + CDevice *pDevice = DeviceForClient(*m_pClient); + if (pDevice && m_pNetwork) { + pDevice->AddNetwork(*m_pNetwork); + } + } + + virtual void OnClientDisconnect() { + CDevice *pDevice = DeviceForClient(*m_pClient); + if (pDevice) { + pDevice->SetInNegotiation(false); + pDevice->RemoveClient(*m_pClient); + } + } + +#pragma mark - + + CDevice& DeviceWithToken(const CString& sToken) { + CDevice *pDevice = NULL; + + for (std::vector::const_iterator it = m_vDevices.begin(); + it != m_vDevices.end(); ++it) { + CDevice& device = **it; + + if (device.GetToken().Equals(sToken)) { + pDevice = &device; + break; + } + } + + if (pDevice == NULL) { + pDevice = new CDevice(sToken); + m_vDevices.push_back(pDevice); + } + + return *pDevice; + } + + CDevice* DeviceForClient(CClient &client) const { + CDevice *pDevice = NULL; + + for (std::vector::const_iterator it = m_vDevices.begin(); + it != m_vDevices.end(); ++it) { + CDevice& device = **it; + + if (device.HasClient(client)) { + pDevice = &device; + break; + } + } + + return pDevice; + } + +#pragma mark - + + void ParseMessage(CNick& Nick, CString& sMessage, CChan *pChannel = NULL) { + if (m_pNetwork->IsUserOnline() == false) { + for (std::vector::const_iterator it = m_vDevices.begin(); + it != m_vDevices.end(); ++it) + { + CDevice& device = **it; + + if (device.HasNetwork(*m_pNetwork)) { + bool bMention = ( + ((pChannel == NULL) || device.HasMentionChannel(pChannel->GetName())) || + device.HasMentionNick(Nick.GetNick()) || + device.IncludesMentionKeyword(sMessage, m_pNetwork->GetIRCNick().GetNick())); + + if (bMention && ( + (pChannel && device.HasIgnoreChannel(pChannel->GetName())) || + device.HasIgnoreNick(Nick.GetNick()) || + device.IncludesIgnoreKeyword(sMessage))) + { + bMention = false; + } + + if (bMention) { + device.SendNotification(*this, Nick.GetNick(), sMessage, pChannel); + } + } + } + } + } + + virtual EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) { + ParseMessage(Nick, sMessage, &Channel); + return CONTINUE; + } + + virtual EModRet OnPrivMsg(CNick& Nick, CString& sMessage) { + ParseMessage(Nick, sMessage, NULL); + return CONTINUE; + } + +private: + + std::vector m_vDevices; +}; + +GLOBALMODULEDEFS(CPalaverMod, "Palaver support module") +