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]
domain =
host =
jid =
password =
resource =
user =
[nostr]
pkhex =

247
index.js

@ -6,8 +6,8 @@ const os = require('os');
const path = require('path');
const wspf = require('websocket-polyfill');
const WebSocket = require('ws');
const xmpp = require('simple-xmpp');
const { client, xml } = require('@xmpp/client');
// nostr things
let {
@ -18,7 +18,6 @@ let {
let letsCID;
let signedEvent;
// config setup for home folders
// Determine the platform-specific config path
function getConfigPath() {
@ -40,184 +39,214 @@ 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
// 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
? 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);
// we've got a connection to XMPP!
xmpp.on('online', async () => {
console.log('Connected with JID: ' + config.xmpp.user);
console.log('Pubkey: ' + pk);
await xmpp.send(xml('presence', {}, xml('status', {}, 'https://nostrsms.com')));
});
// 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
// Debug: Log all incoming stanzas
xmpp.on('stanza', async (stanza) => {
console.log('Received stanza:', stanza.toString());
// check for chat messages
if (stanza.is('message') && stanza.attrs.type === 'chat') {
// store from jid, and message contents
const from = stanza.attrs.from;
const body = stanza.getChildText('body');
// 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)) {
xmpp.send(from, 'Not authorized');
debug();
return; // Exit the function early
await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, 'Not authorized')));
return;
}
// 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() {
// use read relays in config.ini
// store read relays from 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}`);
});
// we've got a connection to the nostr!
relay.on('connect', () => 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}`));
// attempt connection to the nostr
await relay.connect();
// create subscription to get events
let sub = relay.sub([{
kinds: [1],
limit: 10
}, ])
// we've go an event! time to parse!
// create a subscription to get events
let sub = relay.sub([{ kinds: [1], limit: 10 }]);
// we've got an event!
sub.on('event', event => {
console.log('got event:', event);
// Check if the event is already an object
// check if the event recieved is valid json
// if it is parse it
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) {
// store parsed data from event
const content = globalEvents.content;
const author = globalEvents.pubkey;
const createdAt = globalEvents.created_at;
// WIP move to debug
// print out to console
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}`);
// send parsed event data to XMPP
xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, `"${content}", - ${author} @ ${createdAt} `)));
// All set! Let's disconnect from the relay...or...
// I am not sure if I will change this...
relay.close();
} else {
// event recieved wasn't a valid nostr event
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!
// run and print errors to console
getGlobalEvents().catch(console.error);
} else {
// user hasn't input a command, which defaults to a nostr post.
async function newPost() {
// get relays from config.ini
// store write 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}`);
});
// we've got a connection to the nostr!
relay.on('connect', () => 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}`));
// wait for the above to give us the go ahead
// attempt connection to the nostr
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 = {
// https://git.vanderwarker.family/nostr/nips/src/branch/master/01.md
// kind 1 is a basic text post
kind: 1,
// get users public key hex
// set users pubkey
pubkey: pk,
// create this instant! (timestamp)
// this will be created using a Unix 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,
// 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']],
// set event content to the message recieved from the user.
content: body,
};
// sign nostr event
// sign the nostr event using privkey from config.ini
const signedEvent = nostr.finishEvent(event, sk);
// get nostr event it
// store event id
letsCID = signedEvent.id;
// sends id and pubkey to function below
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);
});
// run and print errors to console
newPost().catch(console.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
// hey look the function below!
// gets id and pk from above function ;)
async function showSID(letsCID, pk) {
// print id to console
console.log(letsCID);
// print pubkey to console
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 = {
id: letsCID,
relays: [config.relays.write],
author: pk,
kind: 1,
};
// encode event using nip-19 (nevent)
const encodedNEvent = nostr.nip19.neventEncode(event);
xmpp.send(from, "nostr:" + encodedNEvent);
console.log(encodedNEvent)
debug();
// send the nevent back to the user
// the can copy and paste this into most nostr
// 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
xmpp.on('error', function (err) {
console.error(err);
// uh-oh, XMPP error
xmpp.on('error', (err) => {
// print error to console
console.error('XMPP 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 ??
// start XMPP connectioh
async function startXMPP() {
try {
// wait for connection
await xmpp.start();
// print success message to console
console.log('🚀 XMPP Started Successfully');
} catch (err) {
// print success message to console
console.error('❌ Failed to Start XMPP:', err);
// 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 () {
console.log("SigInt receieved. Shutdown inevitable!")
console.log("SigInt received. Shutdown inevitable!");
xmpp.stop();
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 {
console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=");
console.error("Config file not found at ${configPath}");
console.error(`Config file not found at ${configPath}`);
console.log("=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=_-_+_-_=");
console.log("");
console.log("Please create a config.ini");

4608
package-lock.json generated

File diff suppressed because it is too large Load Diff

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