From: Kyle Fuller Date: Sun, 21 Jul 2019 16:57:07 +0000 (+0100) Subject: feat: support cap-notify X-Git-Tag: 1.2.0~8 X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=60a4fc4584f64dbb5fbec5eab6c095ce5f5ee194;p=znc-palaver.git feat: support cap-notify --- diff --git a/.circleci/config.yml b/.circleci/config.yml index 503aad7..5f171cf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,7 +34,7 @@ jobs: - checkout - run: | export PATH="/opt/znc/bin:$PATH" - pip3 install pytest-asyncio + pip3 install pytest-asyncio semantic_version znc-buildmod palaver.cpp make test-integration diff --git a/CHANGELOG.md b/CHANGELOG.md index 88da1e0..b04af65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog for Palaver ZNC Module +## TBD + +### Enhancements + +- Adds support for cap-notify, this allows Palaver to know when the ZNC module + was loaded/unloaded so it can setup push notifications without having to + reconnect Palaver after loading the module. + ## 1.1.2 ### Bug Fixes diff --git a/palaver.cpp b/palaver.cpp index 9d77c9b..500ee34 100644 --- a/palaver.cpp +++ b/palaver.cpp @@ -28,6 +28,12 @@ #endif +#if defined VERSION_MAJOR && defined VERSION_MINOR && VERSION_MAJOR >= 1 && VERSION_MINOR >= 7 +#define HAS_CAP_NOTIFY +#endif + + + const char *kPLVCapability = "palaverapp.com"; const char *kPLVCommand = "PALAVER"; const char *kPLVPushEndpointKey = "PUSH-ENDPOINT"; @@ -791,9 +797,37 @@ public: virtual bool OnLoad(const CString& sArgs, CString& sMessage) { Load(); +#ifdef HAS_CAP_NOTIFY + const std::map& msUsers = CZNC::Get().GetUserMap(); + + for (std::map::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it) { + CUser* pUser = it->second; + for (CClient* pClient : pUser->GetAllClients()) { + if (pClient->HasCapNotify()) { + pClient->PutClient(":irc.znc.in CAP " + pClient->GetNick() + " NEW :" + kPLVCapability); + } + } + } +#endif + return true; } +#ifdef HAS_CAP_NOTIFY + ~CPalaverMod() { + const std::map& msUsers = CZNC::Get().GetUserMap(); + + for (std::map::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it) { + CUser* pUser = it->second; + for (CClient* pClient : pUser->GetAllClients()) { + if (pClient->HasCapNotify()) { + pClient->PutClient(":irc.znc.in CAP " + pClient->GetNick() + " DEL :" + kPLVCapability); + } + } + } + } +#endif + #pragma mark - Cap virtual void OnClientCapLs(CClient* pClient, SCString& ssCaps) { diff --git a/test/test_palaver.py b/test/test_palaver.py index 53c656d..e8be557 100644 --- a/test/test_palaver.py +++ b/test/test_palaver.py @@ -1,19 +1,45 @@ import time import os import asyncio + import pytest +from semantic_version import Version + # All test coroutines will be treated as marked. pytestmark = pytest.mark.asyncio + +async def requires_znc_version(znc_version): + proc = await asyncio.create_subprocess_shell( + 'znc --version', + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE) + + stdout, stderr = await proc.communicate() + version = stdout.decode('utf-8').split()[1] + + if Version(znc_version) > Version(version): + pytest.skip('ZNC >= {} is required for this test, found {}'.format(znc_version, version)) + + async def setUp(event_loop): running_as_root = os.getuid() == 0 allow_root = ' --allow-root' if running_as_root else '' - + proc = await asyncio.create_subprocess_shell(f'znc -d test/fixtures --foreground --debug{allow_root}', loop=event_loop) time.sleep(31 if running_as_root else 1) (reader, writer) = await asyncio.open_connection('localhost', 6698, loop=event_loop) + writer.write(b'CAP LS 302\r\n') + + line = await reader.readline() + writer.write(b'CAP REQ :palaverapp.com\r\n') + + line = await reader.readline() + assert line == b':irc.znc.in CAP unknown-nick ACK :palaverapp.com\r\n' + + writer.write(b'CAP END\r\n') writer.write(b'PASS admin\r\n') writer.write(b'USER admin admin admin\r\n') writer.write(b'NICK admin\r\n') @@ -28,7 +54,9 @@ async def tearDown(proc): proc.kill() await proc.wait() - os.remove('test/fixtures/moddata/palaver/palaver.conf') + config = 'test/fixtures/moddata/palaver/palaver.conf' + if os.path.exists(config): + os.remove(config) async def test_registering_device(event_loop): (proc, reader, writer) = await setUp(event_loop) @@ -49,3 +77,48 @@ async def test_registering_device(event_loop): time.sleep(1) await tearDown(proc) + +async def test_loading_module_new_cap(event_loop): + await requires_znc_version('1.7.0') + + (proc, reader, writer) = await setUp(event_loop) + + writer.write(b'PRIVMSG *status :unloadmod palaver\r\n') + await writer.drain() + + line = await reader.readline() + assert line == b':irc.znc.in CAP admin DEL :palaverapp.com\r\n' + + line = await reader.readline() + assert line == b':*status!znc@znc.in PRIVMSG admin :Module palaver unloaded.\r\n' + + writer.write(b'PRIVMSG *status :loadmod palaver\r\n') + await writer.drain() + + line = await reader.readline() + assert line == b':irc.znc.in CAP admin NEW :palaverapp.com\r\n' + + line = await reader.readline() + assert line == b':*status!znc@znc.in PRIVMSG admin :Loaded module palaver: [test/fixtures/modules/palaver.so]\r\n' + + time.sleep(1) + + await tearDown(proc) + +async def test_unloading_module_del_cap(event_loop): + await requires_znc_version('1.7.0') + + (proc, reader, writer) = await setUp(event_loop) + + writer.write(b'PRIVMSG *status :unloadmod palaver\r\n') + await writer.drain() + + line = await reader.readline() + assert line == b':irc.znc.in CAP admin DEL :palaverapp.com\r\n' + + line = await reader.readline() + assert line == b':*status!znc@znc.in PRIVMSG admin :Module palaver unloaded.\r\n' + + time.sleep(1) + + await tearDown(proc)