From 05ee3e137eb2aa9047eb31f0195bbb51714ffc0f Mon Sep 17 00:00:00 2001 From: Curve Date: Fri, 20 Oct 2023 18:05:43 +0200 Subject: [PATCH] feat: allow to specify required props --- README.md | 5 ++-- addon/addon.cpp | 52 +++++++++++++++++++++++++++++++----- include/vencord/patchbay.hpp | 2 +- lib/module.d.ts | 13 +++------ private/message.hpp | 1 + private/patchbay.impl.hpp | 2 +- server/main.cpp | 14 +++++----- src/patchbay.cpp | 4 +-- src/patchbay.impl.cpp | 40 +++++++++------------------ tests/node/api.test.js | 12 ++++++--- 10 files changed, 87 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 0e88a4d..bbcef91 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ _venmic_ can be used as node-module or as a local rest-server. The node-module is intended for internal usage by [Vesktop](https://github.com/Vencord/Vesktop). The Rest-Server exposes three simple endpoints -* (GET) `/list` - > List all available applications to share +* (POST) `/list` + > List all available applications to share. + > You can optionally define a JSON-Body containing all the props the listed nodes should have (i.e. `["node.name"]`). * (POST) `/link` > Expects a JSON-Body containing the target application, i.e. `{"key": "node.name", "value": "Firefox", "mode": "include"}` diff --git a/addon/addon.cpp b/addon/addon.cpp index 113887a..c370ba9 100644 --- a/addon/addon.cpp +++ b/addon/addon.cpp @@ -25,7 +25,33 @@ struct patchbay : public Napi::ObjectWrap { auto env = info.Env(); - auto list = vencord::patchbay::get().list(); + std::set props{}; + + if (info.Length() == 1) + { + if (!info[0].IsArray()) + { + Napi::Error::New(env, "[venmic] expected array").ThrowAsJavaScriptException(); + return {}; + } + + auto array = info[0].As(); + + for (auto i = 0u; array.Length() > i; i++) + { + auto item = array.Get(i); + + if (!item.IsString()) + { + Napi::Error::New(env, "[venmic] expected item to be string").ThrowAsJavaScriptException(); + return {}; + } + + props.emplace(item.ToString()); + } + } + + auto list = vencord::patchbay::get().list(props); auto rtn = Napi::Array::New(env, list.size()); auto convert = [&](const auto &item) @@ -56,15 +82,29 @@ struct patchbay : public Napi::ObjectWrap { auto env = info.Env(); - if (info.Length() != 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsString()) + if (info.Length() != 1 || !info[0].IsObject()) + { + Napi::Error::New(env, "[venmic] expected link object").ThrowAsJavaScriptException(); + return Napi::Boolean::New(env, false); + } + + auto data = info[0].ToObject(); + + if (!data.Has("key") || !data.Has("value") || !data.Has("mode")) + { + Napi::Error::New(env, "[venmic] expected keys 'key', 'value' and 'mode'").ThrowAsJavaScriptException(); + return Napi::Boolean::New(env, false); + } + + if (!data.Get("key").IsString() || !data.Get("value").IsString() || !data.Get("mode").IsString()) { - Napi::Error::New(env, "[venmic] expected three string arguments").ThrowAsJavaScriptException(); + Napi::Error::New(env, "[venmic] expected values to be strings").ThrowAsJavaScriptException(); return Napi::Boolean::New(env, false); } - auto key = static_cast(info[0].ToString()); - auto value = static_cast(info[1].ToString()); - auto mode = static_cast(info[2].ToString()); + auto key = static_cast(data.Get("key").ToString()); + auto value = static_cast(data.Get("value").ToString()); + auto mode = static_cast(data.Get("mode").ToString()); if (mode != "include" && mode != "exclude") { diff --git a/include/vencord/patchbay.hpp b/include/vencord/patchbay.hpp index 8926677..49e71d0 100644 --- a/include/vencord/patchbay.hpp +++ b/include/vencord/patchbay.hpp @@ -44,7 +44,7 @@ namespace vencord void unlink(); public: - [[nodiscard]] std::set list(); + [[nodiscard]] std::set list(std::set props); public: [[nodiscard]] static patchbay &get(); diff --git a/lib/module.d.ts b/lib/module.d.ts index 4a297c4..3a8e600 100644 --- a/lib/module.d.ts +++ b/lib/module.d.ts @@ -1,16 +1,11 @@ -export interface Props -{ - "application.process.binary": string; - "application.process.id": string; - "node.name": string; -} +type DefaultProps = 'node.name' | 'application.name'; export class PatchBay { - list(): Props[]; - unlink(): void; - link(key: keyof Props, value: string, mode: "include" | "exclude"): boolean; + + list(props?: T[]): Record; + link(data: {key: string, value: string, mode: "include" | "exclude"}): boolean; static hasPipeWire(): boolean; } diff --git a/private/message.hpp b/private/message.hpp index fc5be51..cd18146 100644 --- a/private/message.hpp +++ b/private/message.hpp @@ -12,6 +12,7 @@ namespace vencord { struct list_nodes { + std::set props; }; struct unset_target diff --git a/private/patchbay.impl.hpp b/private/patchbay.impl.hpp index 5e4bacf..9a0ad98 100644 --- a/private/patchbay.impl.hpp +++ b/private/patchbay.impl.hpp @@ -74,7 +74,7 @@ namespace vencord private: template - void receive(cr_recipe::sender, T); + void receive(cr_recipe::sender &, const T &); private: void start(pw_recipe::receiver, cr_recipe::sender); diff --git a/server/main.cpp b/server/main.cpp index 93798e3..608ab3a 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -47,12 +47,14 @@ int main(int argc, char **args) httplib::Server server; - server.Get("/list", - [](const auto &, auto &response) - { - auto data = glz::write_json(patchbay::get().list()); - response.set_content(data, "application/json"); - }); + server.Post("/list", + [](const auto &req, auto &response) + { + auto props = glz::read_json>(req.body); + auto data = glz::write_json(patchbay::get().list(props.value_or(std::set{}))); + + response.set_content(data, "application/json"); + }); server.Post("/link", [](const auto &req, auto &response) diff --git a/src/patchbay.cpp b/src/patchbay.cpp index d7808d0..92ed0ca 100644 --- a/src/patchbay.cpp +++ b/src/patchbay.cpp @@ -20,9 +20,9 @@ namespace vencord m_impl->sender->send(unset_target{}); } - std::set patchbay::list() + std::set patchbay::list(std::set props) { - m_impl->sender->send(list_nodes{}); + m_impl->sender->send(list_nodes{std::move(props)}); return m_impl->receiver->recv_as>(); } diff --git a/src/patchbay.impl.cpp b/src/patchbay.impl.cpp index 28e23b2..97fc6c3 100644 --- a/src/patchbay.impl.cpp +++ b/src/patchbay.impl.cpp @@ -87,12 +87,7 @@ namespace vencord { auto matching_channel = [&](auto &item) { - if (target_ports.size() == 1) - { - return true; - } - - return item.props["audio.channel"] == port.props["audio.channel"]; + return target_ports.size() == 1 || item.props["audio.channel"] == port.props["audio.channel"]; }; auto others = source_ports | ranges::views::filter(matching_channel); @@ -209,7 +204,7 @@ namespace vencord auto parent = std::stoull(props["node.id"]); nodes[parent].ports.emplace_back(port->info()); - //? Yes this belongs here, as the node is created **before** the ports. + //? Yes. This belongs here, as the node is created **before** the ports. on_node(parent); } @@ -288,28 +283,22 @@ namespace vencord } template <> - void patchbay::impl::receive(cr_recipe::sender sender, [[maybe_unused]] list_nodes) + void patchbay::impl::receive(cr_recipe::sender &sender, const list_nodes &req) { - static std::set desired_props{"application.process.binary", "application.process.id", "node.name"}; + static const std::set required{"application.name", "node.name"}; + const auto &props = req.props.empty() ? required : req.props; auto desireable = [&](auto &item) { - return ranges::all_of(desired_props, [&](const auto &key) { return item.second.info.props.contains(key); }); + return ranges::all_of(props, [&](const auto &key) { return item.second.info.props.contains(key); }); }; auto can_output = [](const auto &item) { return item.second.info.output.max > 0; }; - auto to_node = [](auto &item) + auto to_node = [&](auto &item) { - node rtn; - - for (const auto &key : desired_props) - { - rtn[key] = item.second.info.props[key]; - } - - return rtn; + return node{item.second.info.props}; }; core->update(); @@ -324,8 +313,7 @@ namespace vencord } template <> - // NOLINTNEXTLINE(*-value-param) - void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender, vencord::target req) + void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender &, const vencord::target &req) { if (!mic) { @@ -333,7 +321,7 @@ namespace vencord } created.clear(); - target.emplace(std::move(req)); + target.emplace(req); for (const auto &[id, info] : nodes) { @@ -347,8 +335,7 @@ namespace vencord } template <> - // NOLINTNEXTLINE(*-value-param) - void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender, [[maybe_unused]] unset_target) + void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender &, [[maybe_unused]] const unset_target &) { target.reset(); created.clear(); @@ -357,8 +344,7 @@ namespace vencord } template <> - // NOLINTNEXTLINE(*-value-param) - void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender, [[maybe_unused]] quit) + void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender &, [[maybe_unused]] const quit &) { core->context()->loop()->quit(); } @@ -380,7 +366,7 @@ namespace vencord return; } - receiver.attach(loop, [this, sender](T &&message) { receive(sender, std::forward(message)); }); + receiver.attach(loop, [this, &sender](T &&message) { receive(sender, std::forward(message)); }); auto listener = registry->listen(); listener.on([this](std::uint32_t id) { global_removed(id); }); diff --git a/tests/node/api.test.js b/tests/node/api.test.js index 5b261eb..131dc2d 100644 --- a/tests/node/api.test.js +++ b/tests/node/api.test.js @@ -6,12 +6,16 @@ try const patchbay = new venmic.PatchBay(); assert(Array.isArray(patchbay.list())); + assert(Array.isArray(patchbay.list(["node.name"]))); - assert.throws(() => patchbay.link(10), /expected three string/ig); - assert.throws(() => patchbay.link(10, 10), /expected three string/ig); - assert.throws(() => patchbay.link("node.name", "Firefox", "gibberish"), /expected mode/ig); + assert.throws(() => patchbay.list({}), /expected array/ig); + assert.throws(() => patchbay.list([10]), /expected item to be string/ig); - assert.doesNotThrow(() => patchbay.link("node.name", "Firefox", "include")); + assert.throws(() => patchbay.link(10), /expected link object/ig); + assert.throws(() => patchbay.link({ a: "A", b: "B", c: "C" }), /expected keys/ig); + assert.throws(() => patchbay.link({ key: "node.name", value: "Firefox", mode: "gibberish" }), /expected mode/ig); + + assert.doesNotThrow(() => patchbay.link({ key: "node.name", value: "Firefox", mode: "include" })); assert.doesNotThrow(() => patchbay.unlink()); } catch (error)