Compare commits

..

No commits in common. "c776045198f2ad7bda727ff9406c99e80f63e984" and "f4dbaecfd270f108dfe042c9c628c76f0b657cfc" have entirely different histories.

4 changed files with 1773 additions and 3189 deletions

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

333
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,6 +18,7 @@ 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() {
@ -39,214 +40,184 @@ 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);
// prepare xmpp client // only allow users from config.ini -> [authorized_users] -> users
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', async () => { xmpp.on('online', function (data, to) {
console.log('Connected with JID: ' + config.xmpp.user); console.log('Connected with JID: ' + config.xmpp.jid);
console.log('Pubkey: ' + pk); console.log('Pubkey: ' + pk);
await xmpp.send(xml('presence', {}, xml('status', {}, 'https://nostrsms.com')));
}); });
// Debug: Log all incoming stanzas // We've got an XMPP message!
xmpp.on('stanza', async (stanza) => { xmpp.on('chat', function (from, message) {
console.log('Received stanza:', stanza.toString()); // 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
}
// check for chat messages // get the latest documentation for nostrsms
if (stanza.is('message') && stanza.attrs.type === 'chat') { if (message === '!help') {
// store from jid, and message contents xmpp.send(from, 'https://wiki.vanderwarker.family/doku.php?id=code:nostrsms:commands');
const from = stanza.attrs.from; debug();
const body = stanza.getChildText('body');
// show them to the user in console // get latest 10 posts from global
console.log(`Message from ${from}: ${body}`); } 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}`);
});
// if there is no body, it's not a message. await relay.connect();
// it just qqs
if (!body) return;
// deny messages if they aren't a valid user // create subscription to get events
if (!authorizedUsers.includes(from)) { let sub = relay.sub([{
await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, 'Not authorized'))); kinds: [1],
return; 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.');
}
});
} }
// 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}`)));
async function getGlobalEvents() { // let's run the above code to get our events and send them on XMPP/SMS! :)
// store read relays from config.ini getGlobalEvents().catch((error) => {
const relay = nostr.relayInit(config.relays.read, WebSocket); console.error(error);
// 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 // Well they didn't use a command, so let's get to the NOSTR STUFF!!1!
await relay.connect(); } 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}`);
});
// create a subscription to get events // wait for the above to give us the go ahead
let sub = relay.sub([{ kinds: [1], limit: 10 }]); await relay.connect();
// we've got an event!
sub.on('event', event => {
// check if the event recieved is valid json
// if it is parse it
const globalEvents = typeof event === 'string' ? JSON.parse(event) : event;
// 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;
// print out to console
console.log('Content:', content);
console.log('Author:', author);
console.log('Created At:', 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.');
}
});
}
// 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() {
// store write relays from config.ini
const relay = nostr.relayInit(config.relays.write, WebSocket);
// 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();
/*
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 event = {
// kind 1 is a basic text post
kind: 1,
// set users pubkey
pubkey: pk,
// this will be created using a Unix timestamp
created_at: Math.floor(Date.now() / 1000),
// 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 the nostr event using privkey from config.ini
const signedEvent = nostr.finishEvent(event, sk);
// store event id
letsCID = signedEvent.id;
// sends id and pubkey to function below
showSID(letsCID, pk);
//
await relay.publish(signedEvent);
relay.close();
}
// run and print errors to console
newPost().catch(console.error);
// hey look the function below! // let's construct our "note"!
let event = {
// gets id and pk from above function ;) // https://git.vanderwarker.family/nostr/nips/src/branch/master/01.md
async function showSID(letsCID, pk) { kind: 1,
// print id to console // get users public key hex
console.log(letsCID); pubkey: pk,
// print pubkey to console // create this instant! (timestamp)
console.log(pk); created_at: Math.floor(Date.now() / 1000),
// takes the event id, your write relay(s), pubkey, and kind 1 // add client tag for UIs to optionally display
// mashing it together into a nevent tags: [['client','NostrSMS','31990:9be1b8315248eeb20f9d9ab2717d1750e4f27489eab1fa531d679dadd34c2f8d:1726937516']],
// (makes it easier for other clients to find the event) // set the nostr content to the incoming xmpp message
const event = { content: message,
id: letsCID, };
relays: [config.relays.write], // sign nostr event
author: pk, const signedEvent = nostr.finishEvent(event, sk);
kind: 1, // 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);
});
// encode event using nip-19 (nevent) // get's id, pubkey, kind, from signed event, and relay from config.ini and create
const encodedNEvent = nostr.nip19.neventEncode(event); // a nip-19 encoded entity to send back to the user
// send the nevent back to the user // this can be pasted into most nostr clients for immediate fetching/viewing
// the can copy and paste this into most nostr async function showSID(letsCID, pk) {
// clients and get the event console.log(letsCID);
await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, 'nostr:' + encodedNEvent))); console.log(pk);
// also print nevent to console const event = {
console.log(encodedNEvent); 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();
} }
} }
}); });
// uh-oh, XMPP error // xmpp seems to have an error, so we show it in the console
xmpp.on('error', (err) => { xmpp.on('error', function (err) {
// print error to console console.error(err);
console.error('XMPP Error:', err);
}); });
// start XMPP connectioh // set xmpp presence to nostrsms url (free advertisement! :P)
async function startXMPP() { xmpp.setPresence('chat', 'https://nostrsms.com');
try { // connect to xmpp server using info from config.ini
// wait for connection xmpp.connect({
await xmpp.start(); jid: config.xmpp.jid,
// print success message to console password: config.xmpp.password,
console.log('🚀 XMPP Started Successfully'); host: config.xmpp.host,
} catch (err) { port: 5222
// print success message to console });
console.error('❌ Failed to Start XMPP:', err); // catch ctrl+c
// kill nostrsms process to let user try again // TODO: add confirmation ??
process.exit(1);
}
}
// attempt to connect to XMPP
startXMPP();
// user hit ctrl+c!
process.on('SIGINT', function () { process.on('SIGINT', function () {
console.log("SigInt received. Shutdown inevitable!"); console.log("SigInt receieved. 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");

4604
package-lock.json generated

File diff suppressed because it is too large Load Diff

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