Compare commits

...

5 Commits

Author SHA1 Message Date
c776045198 Switching from simple-xmpp to xmpp/client in index.js
Not too bad of a re-write. added more comments too
2025-01-31 16:23:54 -05:00
f85ae21f9e Adding xmpp resource 2025-01-30 21:09:09 -05:00
4746c37d7a Removing debugging option, changed names for xmpp (see wiki) 2025-01-30 21:07:43 -05:00
821b774695 Locking new versions 2025-01-30 21:06:29 -05:00
7dff65812a Changing from simple-xmpp to xmppjs 2025-01-30 21:06:10 -05:00
4 changed files with 3213 additions and 1797 deletions

@ -1,9 +1,9 @@
debug =
[xmpp] [xmpp]
domain =
host = host =
jid =
password = password =
resource =
user =
[nostr] [nostr]
pkhex = pkhex =

245
index.js

@ -6,8 +6,8 @@ const os = require('os');
const path = require('path'); const path = require('path');
const wspf = require('websocket-polyfill'); const wspf = require('websocket-polyfill');
const WebSocket = require('ws'); const WebSocket = require('ws');
const xmpp = require('simple-xmpp');
const { client, xml } = require('@xmpp/client');
// nostr things // nostr things
let { let {
@ -18,7 +18,6 @@ let {
let letsCID; let letsCID;
let signedEvent; let signedEvent;
// config setup for home folders // config setup for home folders
// Determine the platform-specific config path // Determine the platform-specific config path
function getConfigPath() { function getConfigPath() {
@ -40,184 +39,214 @@ const configPath = getConfigPath();
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
console.log(`Found config at ${configPath}`); console.log(`Found config at ${configPath}`);
const config = ini.parse(fs.readFileSync(configPath, 'utf-8')); const config = ini.parse(fs.readFileSync(configPath, 'utf-8'));
// get users nostr private hex
let sk = config.nostr.pkhex; let sk = config.nostr.pkhex;
// calculate pubkey from private
let pk = nostr.getPublicKey(sk); let pk = nostr.getPublicKey(sk);
// only allow users from config.ini -> [authorized_users] -> users // prepare xmpp client
const xmpp = client({
service: config.xmpp.host,
domain: config.xmpp.domain,
resource: config.xmpp.resource || 'bot',
username: config.xmpp.user,
password: config.xmpp.password,
});
// set up allow users list from config.ini
const authorizedUsers = config.authorized_users.users const authorizedUsers = config.authorized_users.users
? config.authorized_users.users.split(',').map(user => user.trim()) ? config.authorized_users.users.split(',').map(user => user.trim())
: []; : [];
// We've got a connection to XMPP! // we've got a connection to XMPP!
xmpp.on('online', function (data, to) { xmpp.on('online', async () => {
console.log('Connected with JID: ' + config.xmpp.jid); console.log('Connected with JID: ' + config.xmpp.user);
console.log('Pubkey: ' + pk); console.log('Pubkey: ' + pk);
await xmpp.send(xml('presence', {}, xml('status', {}, 'https://nostrsms.com')));
}); });
// We've got an XMPP message! // Debug: Log all incoming stanzas
xmpp.on('chat', function (from, message) { xmpp.on('stanza', async (stanza) => {
// setup debugging based on config.ini console.log('Received stanza:', stanza.toString());
function debug() {
if (config.debug === '1') { // check for chat messages
console.log('[DEBUG]'); if (stanza.is('message') && stanza.attrs.type === 'chat') {
console.log(message); // store from jid, and message contents
console.log(from); const from = stanza.attrs.from;
}; const body = stanza.getChildText('body');
}
// deny users not in config.ini // show them to the user in console
console.log(`Message from ${from}: ${body}`);
// if there is no body, it's not a message.
// it just qqs
if (!body) return;
// deny messages if they aren't a valid user
if (!authorizedUsers.includes(from)) { if (!authorizedUsers.includes(from)) {
xmpp.send(from, 'Not authorized'); await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, 'Not authorized')));
debug(); return;
return; // Exit the function early
} }
// user needs help
// let's send them a wiki link back. (This should be nostrfied....right?!)
if (body === '!help') {
await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, 'https://wiki.vanderwarker.family/doku.php?id=code:nostrsms:commands')));
// fetch global posts
} else if (body === '!g') {
await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, `Here are the latest 10 posts from global on ${config.relays.read}`)));
// 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() { async function getGlobalEvents() {
// use read relays in config.ini // store read relays from config.ini
const relay = nostr.relayInit(config.relays.read, WebSocket); const relay = nostr.relayInit(config.relays.read, WebSocket);
// connect to read relay // we've got a connection to the nostr!
relay.on('connect', () => { relay.on('connect', () => console.log(`connected to ${relay.url}`));
console.log(`connected to ${relay.url}`); // we've not got a connection to the nostr!
}); relay.on('error', () => console.log(`failed to connect to ${relay.url}`));
relay.on('error', () => {
console.log(`failed to connect to ${relay.url}`);
});
// attempt connection to the nostr
await relay.connect(); await relay.connect();
// create subscription to get events // create a subscription to get events
let sub = relay.sub([{ let sub = relay.sub([{ kinds: [1], limit: 10 }]);
kinds: [1], // we've got an event!
limit: 10
}, ])
// we've go an event! time to parse!
sub.on('event', event => { sub.on('event', event => {
console.log('got event:', event); // check if the event recieved is valid json
// if it is parse it
// Check if the event is already an object
const globalEvents = typeof event === 'string' ? JSON.parse(event) : event; const globalEvents = typeof event === 'string' ? JSON.parse(event) : event;
// if globalEvents.* is set, set to const for later use // check for json data
// continue if it exists (atomic)
if (globalEvents && globalEvents.content && globalEvents.pubkey && globalEvents.created_at) { if (globalEvents && globalEvents.content && globalEvents.pubkey && globalEvents.created_at) {
// store parsed data from event
const content = globalEvents.content; const content = globalEvents.content;
const author = globalEvents.pubkey; const author = globalEvents.pubkey;
const createdAt = globalEvents.created_at; const createdAt = globalEvents.created_at;
// print out to console
// WIP move to debug
console.log('Content:', content); console.log('Content:', content);
console.log('Author:', author); console.log('Author:', author);
console.log('Created At:', createdAt); console.log('Created At:', createdAt);
// send parsed event data to XMPP
// send the latest posts to the user whom requested them xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, `"${content}", - ${author} @ ${createdAt} `)));
xmpp.send(from, `\"${content}\", - ${author} @ ${createdAt}`); // All set! Let's disconnect from the relay...or...
// I am not sure if I will change this...
relay.close(); relay.close();
} else { } else {
// event recieved wasn't a valid nostr event
console.error('Invalid event structure or missing required properties.'); console.error('Invalid event structure or missing required properties.');
} }
}); });
} }
// run and print errors to console
// let's run the above code to get our events and send them on XMPP/SMS! :) getGlobalEvents().catch(console.error);
getGlobalEvents().catch((error) => {
console.error(error);
});
// Well they didn't use a command, so let's get to the NOSTR STUFF!!1!
} else { } else {
// user hasn't input a command, which defaults to a nostr post.
async function newPost() { async function newPost() {
// get relays from config.ini // store write relays from config.ini
const relay = nostr.relayInit(config.relays.write, WebSocket); const relay = nostr.relayInit(config.relays.write, WebSocket);
// look at that, already on the nostr // we've got a connection to the nostr!
relay.on('connect', () => { relay.on('connect', () => console.log(`connected to ${relay.url}`));
console.log(`connected to ${relay.url}`); // we've not got a connection to the nostr!
}); relay.on('error', () => console.log(`failed to connect 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 // attempt connection to the nostr
await relay.connect(); await relay.connect();
/*
incredible, but this is a nostr event.
truth be told it doesn't even need the tags.
That's just referencing another type of
event that advertises nostrsms and shows
links to this source code. nostr is
waaayyy more than a X (Twitter) replacement
*/
// let's construct our "note"!
let event = { let event = {
// https://git.vanderwarker.family/nostr/nips/src/branch/master/01.md // kind 1 is a basic text post
kind: 1, kind: 1,
// get users public key hex // set users pubkey
pubkey: pk, pubkey: pk,
// create this instant! (timestamp) // this will be created using a Unix timestamp
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
// add client tag for UIs to optionally display // some clients will show via NostrSMS much like X has/had? idk haven't been on in years :P
tags: [['client', 'NostrSMS', '31990:9be1b8315248eeb20f9d9ab2717d1750e4f27489eab1fa531d679dadd34c2f8d:1726937516']], tags: [['client', 'NostrSMS', '31990:9be1b8315248eeb20f9d9ab2717d1750e4f27489eab1fa531d679dadd34c2f8d:1726937516']],
// set the nostr content to the incoming xmpp message // set event content to the message recieved from the user.
content: message, content: body,
}; };
// sign nostr event // sign the nostr event using privkey from config.ini
const signedEvent = nostr.finishEvent(event, sk); const signedEvent = nostr.finishEvent(event, sk);
// get nostr event it // store event id
letsCID = signedEvent.id; letsCID = signedEvent.id;
// sends id and pubkey to function below
showSID(letsCID, pk); showSID(letsCID, pk);
// publish! //
await relay.publish(signedEvent); await relay.publish(signedEvent);
// done here. we can disconnect! (from nostr relay)
relay.close(); relay.close();
} }
// catch errors // run and print errors to console
newPost().catch((error) => { newPost().catch(console.error);
console.error(error);
});
// get's id, pubkey, kind, from signed event, and relay from config.ini and create // hey look the function below!
// a nip-19 encoded entity to send back to the user
// this can be pasted into most nostr clients for immediate fetching/viewing // gets id and pk from above function ;)
async function showSID(letsCID, pk) { async function showSID(letsCID, pk) {
// print id to console
console.log(letsCID); console.log(letsCID);
// print pubkey to console
console.log(pk); console.log(pk);
// takes the event id, your write relay(s), pubkey, and kind 1
// mashing it together into a nevent
// (makes it easier for other clients to find the event)
const event = { const event = {
id: letsCID, id: letsCID,
relays: [config.relays.write], relays: [config.relays.write],
author: pk, author: pk,
kind: 1, kind: 1,
}; };
// encode event using nip-19 (nevent)
const encodedNEvent = nostr.nip19.neventEncode(event); const encodedNEvent = nostr.nip19.neventEncode(event);
xmpp.send(from, "nostr:" + encodedNEvent); // send the nevent back to the user
console.log(encodedNEvent) // the can copy and paste this into most nostr
debug(); // clients and get the event
await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, 'nostr:' + encodedNEvent)));
// also print nevent to console
console.log(encodedNEvent);
}
} }
} }
}); });
// xmpp seems to have an error, so we show it in the console // uh-oh, XMPP error
xmpp.on('error', function (err) { xmpp.on('error', (err) => {
console.error(err); // print error to console
console.error('XMPP Error:', err);
}); });
// set xmpp presence to nostrsms url (free advertisement! :P) // start XMPP connectioh
xmpp.setPresence('chat', 'https://nostrsms.com'); async function startXMPP() {
// connect to xmpp server using info from config.ini try {
xmpp.connect({ // wait for connection
jid: config.xmpp.jid, await xmpp.start();
password: config.xmpp.password, // print success message to console
host: config.xmpp.host, console.log('🚀 XMPP Started Successfully');
port: 5222 } catch (err) {
}); // print success message to console
// catch ctrl+c console.error('❌ Failed to Start XMPP:', err);
// TODO: add confirmation ?? // kill nostrsms process to let user try again
process.exit(1);
}
}
// attempt to connect to XMPP
startXMPP();
// user hit ctrl+c!
process.on('SIGINT', function () { process.on('SIGINT', function () {
console.log("SigInt receieved. Shutdown inevitable!") console.log("SigInt received. Shutdown inevitable!");
xmpp.stop();
process.exit(); process.exit();
}); });
// error out and show install instructions if config.ini isn't found.
// config.ini is invalid, or missing.
// print wiki link to console for further instructions
} else { } else {
console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_="); console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=");
console.error("Config file not found at ${configPath}"); console.error(`Config file not found at ${configPath}`);
console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_="); console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=");
console.log(""); console.log("");
console.log("Please create a config.ini"); console.log("Please create a config.ini");

4626
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -15,15 +15,16 @@
} }
], ],
"dependencies": { "dependencies": {
"@types/ws": "8.5.5", "@xmpp/client": "^0.13.0",
"assert": "^2.1.0", "bech32": "^2.0.0",
"bech32": "2.0.0", "debug": "^4.3.4",
"ini": "2.0.0", "fs": "0.0.1-security",
"nostr-tools": "1.14.2", "ini": "^2.0.0",
"simple-xmpp": "1.3.0", "nostr-tools": "^1.9.0",
"sqlite3": "5.1.6", "os": "^0.1.2",
"path": "^0.12.7",
"websocket-polyfill": "0.0.3", "websocket-polyfill": "0.0.3",
"ws": "^8.14.0" "ws": "^8.13.0"
}, },
"bugs": { "bugs": {
"url": "https://git.vanderwarker.family/nostr/nostrsms/issues" "url": "https://git.vanderwarker.family/nostr/nostrsms/issues"