258 lines
9.5 KiB
JavaScript
258 lines
9.5 KiB
JavaScript
// 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 { client, xml } = require('@xmpp/client');
|
|
|
|
// 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'));
|
|
let sk = config.nostr.pkhex;
|
|
let pk = nostr.getPublicKey(sk);
|
|
|
|
// prepare xmpp client
|
|
const xmpp = client({
|
|
service: config.xmpp.host,
|
|
domain: config.xmpp.domain,
|
|
resource: config.xmpp.resource || 'nostrsms-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', async () => {
|
|
console.log('Connected with JID: ' + config.xmpp.user);
|
|
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 rawfrom = stanza.attrs.from;
|
|
const from = rawfrom.split('/')[0];
|
|
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;
|
|
}
|
|
// 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() {
|
|
// store read relays from 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}`));
|
|
|
|
// 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!
|
|
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!
|
|
|
|
// 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);
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
// uh-oh, XMPP error
|
|
xmpp.on('error', (err) => {
|
|
// print error to console
|
|
console.error('XMPP 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!
|
|
process.on('SIGINT', function () {
|
|
console.log("SigInt received. Shutdown inevitable!");
|
|
xmpp.stop();
|
|
process.exit();
|
|
});
|
|
|
|
|
|
// 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.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);
|
|
}
|