// initialize required packages const fs = require('fs'); const ini = require('ini'); const nostr = require('nostr-tools'); const os = require('os'); const path = require('path'); const wspf = require('websocket-polyfill'); const WebSocket = require('ws'); const xmpp = require('simple-xmpp'); // nostr things let { bech32, bech32m } = require('bech32'); let letsCID; let signedEvent; // config setup for home folders // Determine the platform-specific config path function getConfigPath() { const platform = os.platform(); if (platform === 'win32') { // Windows return path.join(process.env.APPDATA, 'nostrsms', 'config.ini'); } else if (platform === 'darwin') { // macOS return path.join(os.homedir(), 'Library', 'Application Support', 'nostrsms', 'config.ini'); } else { // Linux and other Unix-like systems return path.join(os.homedir(), '.config', 'nostrsms', 'config.ini'); } } // prepare config for parsing const configPath = getConfigPath(); if (fs.existsSync(configPath)) { console.log(`Found config at ${configPath}`); const config = ini.parse(fs.readFileSync(configPath, 'utf-8')); // get users nostr private hex let sk = config.nostr.pkhex; // calculate pubkey from private let pk = nostr.getPublicKey(sk); // only allow users from config.ini -> [authorized_users] -> users const authorizedUsers = config.authorized_users.users ? config.authorized_users.users.split(',').map(user => user.trim()) : []; // We've got a connection to XMPP! xmpp.on('online', function (data, to) { console.log('Connected with JID: ' + config.xmpp.jid); console.log('Pubkey: ' + pk); }); // We've got an XMPP message! xmpp.on('chat', function (from, message) { // setup debugging based on config.ini function debug() { if (config.debug === '1') { console.log('[DEBUG]'); console.log(message); console.log(from); }; } // deny users not in config.ini if (!authorizedUsers.includes(from)) { xmpp.send(from, 'Not authorized'); debug(); return; // Exit the function early } // get the latest documentation for nostrsms if (message === '!help') { xmpp.send(from, 'https://wiki.vanderwarker.family/doku.php?id=code:nostrsms:commands'); debug(); // get latest 10 posts from global } else if (message === "!g") { xmpp.send(from, `Here are the latest 10 posts from global on ${config.relays.read}`); async function getGlobalEvents() { // use read relays in config.ini const relay = nostr.relayInit(config.relays.read, WebSocket); // connect to read relay relay.on('connect', () => { console.log(`connected to ${relay.url}`); }); relay.on('error', () => { console.log(`failed to connect to ${relay.url}`); }); await relay.connect(); // create subscription to get events let sub = relay.sub([{ kinds: [1], limit: 10 }, ]) // we've go an event! time to parse! sub.on('event', event => { console.log('got event:', event); // Check if the event is already an object const globalEvents = typeof event === 'string' ? JSON.parse(event) : event; // if globalEvents.* is set, set to const for later use if (globalEvents && globalEvents.content && globalEvents.pubkey && globalEvents.created_at) { const content = globalEvents.content; const author = globalEvents.pubkey; const createdAt = globalEvents.created_at; // WIP move to debug console.log('Content:', content); console.log('Author:', author); console.log('Created At:', createdAt); // send the latest posts to the user whom requested them xmpp.send(from, `\"${content}\", - ${author} @ ${createdAt}`); relay.close(); } else { console.error('Invalid event structure or missing required properties.'); } }); } // let's run the above code to get our events and send them on XMPP/SMS! :) getGlobalEvents().catch((error) => { console.error(error); }); // Well they didn't use a command, so let's get to the NOSTR STUFF!!1! } else { async function newPost() { // get relays from config.ini const relay = nostr.relayInit(config.relays.write, WebSocket); // look at that, already on the nostr relay.on('connect', () => { console.log(`connected to ${relay.url}`); }); // dang...failed connection! Try again sooon, or try one of the thousands of other relays ;) relay.on('error', () => { console.log(`failed to connect to ${relay.url}`); }); // wait for the above to give us the go ahead await relay.connect(); // let's construct our "note"! let event = { // https://git.vanderwarker.family/nostr/nips/src/branch/master/01.md kind: 1, // get users public key hex pubkey: pk, // create this instant! (timestamp) created_at: Math.floor(Date.now() / 1000), // add client tag for UIs to optionally display tags: [['client','NostrSMS','31990:9be1b8315248eeb20f9d9ab2717d1750e4f27489eab1fa531d679dadd34c2f8d:1726937516']], // set the nostr content to the incoming xmpp message content: message, }; // sign nostr event const signedEvent = nostr.finishEvent(event, sk); // get nostr event it letsCID = signedEvent.id; showSID(letsCID, pk); // publish! await relay.publish(signedEvent); // done here. we can disconnect! (from nostr relay) relay.close(); } // catch errors newPost().catch((error) => { console.error(error); }); // get's id, pubkey, kind, from signed event, and relay from config.ini and create // a nip-19 encoded entity to send back to the user // this can be pasted into most nostr clients for immediate fetching/viewing async function showSID(letsCID, pk) { console.log(letsCID); console.log(pk); const event = { id: letsCID, relays: [config.relays.write], author: pk, kind: 1, }; const encodedNEvent = nostr.nip19.neventEncode(event); xmpp.send(from, "nostr:" + encodedNEvent); console.log(encodedNEvent) debug(); } } }); // xmpp seems to have an error, so we show it in the console xmpp.on('error', function (err) { console.error(err); }); // set xmpp presence to nostrsms url (free advertisement! :P) xmpp.setPresence('chat', 'https://nostrsms.com'); // connect to xmpp server using info from config.ini xmpp.connect({ jid: config.xmpp.jid, password: config.xmpp.password, host: config.xmpp.host, port: 5222 }); // catch ctrl+c // TODO: add confirmation ?? process.on('SIGINT', function () { console.log("SigInt receieved. Shutdown inevitable!") process.exit(); }); // error out and show install instructions if config.ini isn't found. } else { console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_="); console.error("Config file not found at ${configPath}"); console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_="); console.log(""); console.log("Please create a config.ini"); console.log("See https://wiki.vanderwarker.family/doku.php?id=code:nostrsms:install"); console.log("and choose your preferred install method."); process.exit(1); }