diff --git a/bouncer.js b/bouncer.js index 1ab59e7..21f8caa 100644 --- a/bouncer.js +++ b/bouncer.js @@ -1,6 +1,7 @@ "use strict"; const { version } = require("./package.json"); const WebSocket = require("ws"); +const querystring = require("querystring"); const { verifyEvent, nip19, matchFilters, mergeFilters, getFilterLimit } = require("nostr-tools"); const auth = require("./auth.js"); const nip42 = require("./nip42.js"); @@ -16,6 +17,7 @@ if (relays.length < max_eose_score) max_eose_score = 0; // CL - User socket module.exports = (ws, req, onClose) => { + let query = querystring.parse(req.url.slice(2)); let authKey = null; let authorized = true; let lastEvent = Date.now(); @@ -30,6 +32,8 @@ module.exports = (ws, req, onClose) => { ws.subalias = new Map(); ws.fakesubalias = new Map(); ws.pubkey = null; + ws.rejectKinds = query.reject?.split(",").map(_ => parseInt(_)); + ws.acceptKinds = query.accept?.split(",").map(_ => parseInt(_)); if (authorized_keys?.length) { authKey = Date.now() + Math.random().toString(36); @@ -87,15 +91,24 @@ module.exports = (ws, req, onClose) => { if (ws.subs.has(data[1])) return ws.send(JSON.stringify(["CLOSED", data[1], "duplicate: subscription already opened"])); const origID = data[1]; const faked = Date.now() + Math.random().toString(36); - ws.subs.set(origID, data.slice(2)); + let filters = data.slice(2); + + for (const fn in filters) { + if (!Array.isArray(filters[fn].kinds)) continue; + filters[fn].kinds = filters[fn].kinds?.filter(kind => { + if (ws.rejectKinds && ws.rejectKinds.includes(kind)) return false; + if (ws.acceptKinds && !ws.acceptKinds.includes(kind)) return true; + return true; + }); + } + ws.subs.set(origID, filters); ws.events.set(origID, new Set()); ws.pause_subs.delete(origID); ws.subalias.set(faked, origID); ws.fakesubalias.set(origID, faked); - data[1] = faked; bc(data, ws); - if (data[2]?.limit < 1) return ws.send(JSON.stringify(["EOSE", origID])); + if (getFilterLimit(mergeFilters(...filters)) < 1) return ws.send(JSON.stringify(["EOSE", origID])); ws.pendingEOSE.set(origID, 0); break; } @@ -208,8 +221,11 @@ function newConn(addr, client, reconn_t = 0) { data[1] = client.subalias.get(data[1]); if (client.pause_subs.has(data[1])) return; + if (client.acceptKinds && !client.acceptKinds.includes(data[2]?.kind)) return; + if (client.rejectKinds && client.rejectKinds.includes(data[2]?.kind)) return; + const filters = client.subs.get(data[1]); - const filter = mergeFilters(filters); + const filter = mergeFilters(...filters); const NotInSearchQuery = "search" in filter && !data[2]?.content?.toLowerCase().includes(filter.search.toLowerCase()); if (!matchFilters(filters, data[2]) || NotInSearchQuery) return; if (client.events.get(data[1]).has(data[2]?.id)) return; // No need to transmit once it has been transmitted before. diff --git a/http.js b/http.js index cd1c77d..b1d5597 100644 --- a/http.js +++ b/http.js @@ -66,8 +66,14 @@ server.on('request', (req, res) => { res.write(`\nI have ${wss.clients.size} clients currently connected to this bouncer${(process.env.CLUSTERS || config.clusters) > 1 ? " on this cluster" : ""}.\n`); if (config?.authorized_keys?.length) res.write("\nNOTE: This relay has configured for personal use only. Only authorized users could use this bostr relay.\n"); - res.write(`\nConnect to this bouncer with nostr client: ${req.headers["x-forwarded-proto"]?.replace(/http/i, "ws") || (server.isStandaloneHTTPS ? "wss" : "ws")}://${req.headers.host}${req.url}\n\n---\n`); - res.end(`Powered by Bostr (${version}) - Open source Nostr bouncer\nhttps://github.com/Yonle/bostr`); + res.write(`\nConnect to this bouncer with nostr client: ${req.headers["x-forwarded-proto"]?.replace(/http/i, "ws") || (server.isStandaloneHTTPS ? "wss" : "ws")}://${req.headers.host}${req.url}`); + res.write(`\n\n- To make this bouncer only send you specific event kinds, Connect:`); + res.write(`\n ${req.headers["x-forwarded-proto"]?.replace(/http/i, "ws") || (server.isStandaloneHTTPS ? "wss" : "ws")}://${req.headers.host}${req.url}?accept=0,1,2,3,4,5,6,7,8,16,1984,1985,13194,21000,23194,23195,30315`); + res.write(`\n (Will only send events with kinds 0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 1984, 1985, 13194, 21000, 23194, 23195, and 30315)`); + res.write(`\n- To make this bouncer not sending some specific kind of events, Connect:`); + res.write(`\n ${req.headers["x-forwarded-proto"]?.replace(/http/i, "ws") || (server.isStandaloneHTTPS ? "wss" : "ws")}://${req.headers.host}${req.url}?reject=10000,10002`); + res.write(`\n (Will not send events with kind 10000, and 10002)`); + res.end(`\n\n---\nPowered by Bostr (${version}) - Open source Nostr bouncer\nhttps://github.com/Yonle/bostr`); } else if (req.url.startsWith("/favicon") && favicon) { res.writeHead(200, { "Content-Type": "image/" + config.favicon?.split(".").pop() }); res.end(favicon);