feat: support cap-notify
authorKyle Fuller <redacted>
Sun, 21 Jul 2019 16:57:07 +0000 (17:57 +0100)
committerDennis Collaris <redacted>
Mon, 22 Jul 2019 12:24:57 +0000 (14:24 +0200)
.circleci/config.yml
CHANGELOG.md
palaver.cpp
test/test_palaver.py

index 503aad7ea20d8f117fbaabe8755fe4b147b09f22..5f171cfd15fb904c7ef7feff64cb41d44315e421 100644 (file)
@@ -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
 
index 88da1e0d82794b04893bb6c71b5b74accdcd56cf..b04af65efb63ed0d17ffda002957d645419f1825 100644 (file)
@@ -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
index 9d77c9b453c639b67e930430adfdb460ec9e9314..500ee34bf0dfa2d83d4788a81e9c3bcf812ad847 100644 (file)
 #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<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
+
+               for (std::map<CString, CUser*>::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<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
+
+               for (std::map<CString, CUser*>::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) {
index 53c656dcc5ec9aa8020091d9b24991c8e41ba9fd..e8be557edc80e9c303bb4361b25950f47bc5faad 100644 (file)
@@ -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)
git clone https://git.99rst.org/PROJECT