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

249
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,6 +18,7 @@ let {
let letsCID;
let signedEvent;
// config setup for home folders
// Determine the platform-specific config path
function getConfigPath() {
@ -39,214 +40,184 @@ 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);
// 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
// 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', async () => {
console.log('Connected with JID: ' + config.xmpp.user);
// 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);
await xmpp.send(xml('presence', {}, xml('status', {}, 'https://nostrsms.com')));
});
// 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)) {
await xmpp.send(xml('message', { to: from, type: 'chat' }, xml('body', {}, 'Not authorized')));
return;
// 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
}
// 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() {
// store read relays from config.ini
// use read relays in config.ini
const relay = nostr.relayInit(config.relays.read, 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}`));
// connect to read relay
relay.on('connect', () => {
console.log(`connected to ${relay.url}`);
});
relay.on('error', () => {
console.log(`failed to connect to ${relay.url}`);
});
// attempt connection to the nostr
await relay.connect();
// create a subscription to get events
let sub = relay.sub([{ kinds: [1], limit: 10 }]);
// we've got an event!
// 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 => {
// check if the event recieved is valid json
// if it is parse it
console.log('got event:', event);
// Check if the event is already an object
const globalEvents = typeof event === 'string' ? JSON.parse(event) : event;
// check for json data
// continue if it exists (atomic)
// if globalEvents.* is set, set to const for later use
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
// WIP move to debug
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...
// send the latest posts to the user whom requested them
xmpp.send(from, `\"${content}\", - ${author} @ ${createdAt}`);
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);
// 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 {
// user hasn't input a command, which defaults to a nostr post.
async function newPost() {
// store write relays from config.ini
// get 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}`));
// 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}`);
});
// attempt connection to the nostr
// wait for the above to give us the go ahead
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 = {
// kind 1 is a basic text post
// https://git.vanderwarker.family/nostr/nips/src/branch/master/01.md
kind: 1,
// set users pubkey
// get users public key hex
pubkey: pk,
// this will be created using a Unix timestamp
// create this instant! (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,
// 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 the nostr event using privkey from config.ini
// sign nostr event
const signedEvent = nostr.finishEvent(event, sk);
// store event id
// get nostr event it
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();
}
// run and print errors to console
newPost().catch(console.error);
// catch errors
newPost().catch((error) => {
console.error(error);
});
// hey look the function below!
// gets id and pk from above function ;)
// 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) {
// 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);
// 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.send(from, "nostr:" + encodedNEvent);
console.log(encodedNEvent)
debug();
}
}
});
// uh-oh, XMPP error
xmpp.on('error', (err) => {
// print error to console
console.error('XMPP Error:', err);
// xmpp seems to have an error, so we show it in the console
xmpp.on('error', function (err) {
console.error(err);
});
// 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!
// 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 received. Shutdown inevitable!");
xmpp.stop();
console.log("SigInt receieved. Shutdown inevitable!")
process.exit();
});
// config.ini is invalid, or missing.
// print wiki link to console for further instructions
// error out and show install instructions if config.ini isn't found.
} 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");

4600
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -15,16 +15,15 @@
}
],
"dependencies": {
"@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",
"@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",
"websocket-polyfill": "0.0.3",
"ws": "^8.13.0"
"ws": "^8.14.0"
},
"bugs": {
"url": "https://git.vanderwarker.family/nostr/nostrsms/issues"