15 Commits

Author SHA1 Message Date
58e2ae95c0 Update web/superball.html 2025-11-30 03:47:37 +00:00
792eb46ca2 Update web/superball-shared.css 2025-11-24 03:49:18 +00:00
ee5f318532 Update web/superball.html 2025-11-24 03:48:39 +00:00
59b0461bad Added custom events and updated reply events handling
Implemented support for creating custom events. Also integrated functionality to generate reply events by retrieving them from relays first, with compatibility for multiple eventId formats like nevent, note, and hexadecimal strings.
2025-11-22 11:24:50 +00:00
9285f8e583 Update web/superball.html 2025-11-18 04:11:37 +00:00
c5ec067a79 Update web/superball.html 2025-11-18 03:54:22 +00:00
387f5d4725 Update web/superball.html 2025-11-18 02:37:28 +00:00
c4d73026b5 CSS 2025-11-16 04:38:00 +00:00
516c4f2f80 Adds reply and profile creation along with post functionality
This commit introduces new features to enhance user interaction and profile management:

- Adds the ability to create replies for comments
- Enables profile creation for users
- Supports post creation for sharing updates and content
2025-11-16 04:36:10 +00:00
Your Name
6ff47cec92 Fixed bugs with install script 2025-09-29 10:41:04 -04:00
Your Name
5b1fa185f7 . 2025-09-29 10:30:31 -04:00
Your Name
d941638779 . 2025-09-29 09:57:36 -04:00
Your Name
e6bef22494 . 2025-09-29 09:39:14 -04:00
Your Name
3edc477069 install.sh 2025-09-29 09:28:19 -04:00
Your Name
5b618c08ed fix ws issue 2025-09-29 07:36:12 -04:00
6 changed files with 656 additions and 130 deletions

View File

@@ -418,7 +418,7 @@ class EventProcessor {
if (!routing.audit || typeof routing.audit !== 'string') return false;
// Check maximum delay limit
const maxDelay = this.config.get('thrower.maxDelay') || 3600;
const maxDelay = this.config.get('thrower.maxDelay') || 86460;
if (routing.delay > maxDelay) {
this.logger.error(`Routing delay ${routing.delay}s exceeds maximum allowed delay of ${maxDelay}s`);
return false;
@@ -648,7 +648,7 @@ class ThrowerInfoManager {
if (throwerInfo.privacyPolicy) tags.push(['privacy_policy', throwerInfo.privacyPolicy]);
if (throwerInfo.termsOfService) tags.push(['terms_of_service', throwerInfo.termsOfService]);
tags.push(['refresh_rate', throwerInfo.refreshRate.toString()]);
tags.push(['max_delay', (throwerInfo.maxDelay || 3600).toString()]);
tags.push(['max_delay', (throwerInfo.maxDelay || 86460).toString()]);
const eventTemplate = {
kind: 12222,
@@ -933,7 +933,7 @@ class WebSocketManager {
this.logger.info('Stopping WebSocket monitoring...');
this.connections.forEach(({ url, ws }) => {
if (ws.readyState === WebSocket.OPEN) {
if (ws.readyState === ws.OPEN) {
// Send CLOSE message for subscription
if (this.subscriptionId) {
const closeMsg = JSON.stringify(['CLOSE', this.subscriptionId]);

View File

@@ -5,126 +5,261 @@
set -e
echo "=== Superball Thrower Daemon Installation ==="
echo
echo "🏀 Superball Thrower Daemon Installation"
echo "========================================"
# Check if running as root
if [[ $EUID -eq 0 ]]; then
echo "This script should not be run as root for security reasons."
echo "Please run as a regular user with sudo privileges."
echo "This script should not be run as root for security reasons"
echo " Please run as a regular user with sudo privileges"
exit 1
fi
# Check if Node.js is installed
if ! command -v node &> /dev/null; then
echo "Error: Node.js is not installed."
echo "Please install Node.js 16 or later from https://nodejs.org/"
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check for required system dependencies
echo "📋 Checking system dependencies..."
# Check for curl
if ! command_exists curl; then
echo "❌ curl is required but not installed"
echo " Please install curl: sudo apt update && sudo apt install curl"
exit 1
fi
# Check Node.js version
NODE_VERSION=$(node --version | cut -d'v' -f2 | cut -d'.' -f1)
if [ "$NODE_VERSION" -lt 16 ]; then
echo "Error: Node.js version 16 or later is required."
echo "Current version: $(node --version)"
# Check for systemctl (systemd)
if ! command_exists systemctl; then
echo "❌ systemd is required but not found"
echo " This script requires a systemd-based system"
exit 1
fi
echo "✓ Node.js $(node --version) detected"
# Install latest Node.js using nvm
echo "🔧 Installing latest Node.js using nvm..."
# Check if npm is installed
if ! command -v npm &> /dev/null; then
echo "Error: npm is not installed."
# Download and install nvm
if [ ! -d "$HOME/.nvm" ]; then
echo " Downloading and installing nvm..."
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
fi
# Load nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# Install Node.js 22 (latest LTS)
echo " Installing Node.js 22..."
nvm install 22
nvm use 22
# Verify installation
NODE_VERSION=$(node --version)
NPM_VERSION=$(npm --version)
echo "✅ Node.js $NODE_VERSION installed"
echo "✅ npm $NPM_VERSION installed"
# Copy Node.js binaries to system-wide location
echo "📋 Installing Node.js binaries system-wide..."
NODE_PATH=$(which node)
NPM_PATH=$(which npm)
if [ -n "$NODE_PATH" ] && [ -x "$NODE_PATH" ]; then
# Remove any existing symlinks first
sudo rm -f /usr/local/bin/node
sudo cp "$NODE_PATH" /usr/local/bin/node
sudo chmod +x /usr/local/bin/node
echo "✅ Node.js binary copied to /usr/local/bin/node"
else
echo "❌ Failed to find Node.js executable"
exit 1
fi
echo "✓ npm $(npm --version) detected"
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DAEMON_DIR="$SCRIPT_DIR"
echo "Installing to: $DAEMON_DIR"
echo
# Install Node.js dependencies
echo "Installing Node.js dependencies..."
cd "$DAEMON_DIR"
npm install
if [ $? -ne 0 ]; then
echo "Error: Failed to install Node.js dependencies"
if [ -n "$NPM_PATH" ] && [ -x "$NPM_PATH" ]; then
# Remove any existing symlinks first
sudo rm -f /usr/local/bin/npm
sudo cp "$NPM_PATH" /usr/local/bin/npm
sudo chmod +x /usr/local/bin/npm
echo "✅ npm binary copied to /usr/local/bin/npm"
else
echo "❌ Failed to find npm executable"
exit 1
fi
echo "✓ Dependencies installed successfully"
echo
# Install npm dependencies
echo "📦 Installing npm dependencies..."
if ! npm install; then
echo "❌ Failed to install npm dependencies"
exit 1
fi
# Create user for daemon (if it doesn't exist)
DAEMON_USER="superball"
if ! id "$DAEMON_USER" &>/dev/null; then
echo "Creating system user: $DAEMON_USER"
echo "✅ Dependencies installed successfully"
# Interactive configuration
echo ""
echo "⚙️ Thrower Configuration"
echo "========================"
echo "Please provide the following information for your Superball Thrower:"
echo ""
# Prompt for configuration values
read -p "Enter your private key (hex format, leave empty to generate new): " PRIVATE_KEY
read -p "Enter your public key (hex format, leave empty if generating new): " PUBLIC_KEY
read -p "Enter thrower name (e.g., 'My Superball Thrower'): " THROWER_NAME
read -p "Enter thrower description: " THROWER_DESCRIPTION
read -p "Enter banner URL (optional, press enter to skip): " BANNER_URL
read -p "Enter icon URL (optional, press enter to skip): " ICON_URL
# Generate keypair if not provided
if [ -z "$PRIVATE_KEY" ] || [ -z "$PUBLIC_KEY" ]; then
echo "🔑 Generating new keypair..."
# We'll let the daemon generate the keypair on first run
PRIVATE_KEY=""
PUBLIC_KEY=""
fi
# Update config.json with user input
echo "📝 Updating configuration file..."
# Create a temporary config file with user values
cat > config.json << EOF
{
"thrower": {
"privateKey": "$PRIVATE_KEY",
"publicKey": "$PUBLIC_KEY",
"name": "$THROWER_NAME",
"description": "$THROWER_DESCRIPTION",
"banner": "$BANNER_URL",
"icon": "$ICON_URL",
"adminPubkey": "",
"contact": "",
"supportedSups": "1,2,3,4,5,6",
"software": "https://git.laantungir.net/laantungir/super_ball.git",
"version": "1.0.0",
"privacyPolicy": "",
"termsOfService": "",
"refreshRate": 300,
"maxDelay": 86460
},
"relays": [
{ "url": "wss://relay.laantungir.net", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://relay.damus.io", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://nos.lol", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://relay.snort.social", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://relay.primal.net", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://relay.nostr.band", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://nostr.oxtr.dev", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://offchain.pub", "read": true, "write": true, "authStatus": "unknown" },
{ "url": "wss://nostr-pub.wellorder.net", "read": true, "write": true, "authStatus": "unknown" }
],
"daemon": {
"logLevel": "info",
"maxQueueSize": 1000,
"maxLogEntries": 1000,
"autoStart": false
}
}
EOF
echo "✅ Configuration updated"
# Create system user for the daemon
echo "👤 Creating system user..."
DAEMON_USER="superball-thrower"
if id "$DAEMON_USER" &>/dev/null; then
echo "✅ User $DAEMON_USER already exists"
else
echo " Creating user: $DAEMON_USER"
sudo useradd --system --no-create-home --shell /bin/false "$DAEMON_USER"
echo " User $DAEMON_USER created"
else
echo "✓ User $DAEMON_USER already exists"
echo " User $DAEMON_USER created"
fi
# Create directories
echo "Creating directories..."
sudo mkdir -p /var/log/superball
sudo mkdir -p /etc/superball
sudo chown "$DAEMON_USER:$DAEMON_USER" /var/log/superball
echo "✓ Directories created"
# Create installation directory
INSTALL_DIR="/opt/superball-thrower"
echo "📁 Setting up installation directory: $INSTALL_DIR"
# Copy configuration file
if [ ! -f "/etc/superball/config.json" ]; then
echo "Installing default configuration..."
sudo cp "$DAEMON_DIR/config.json" /etc/superball/config.json
sudo chown "$DAEMON_USER:$DAEMON_USER" /etc/superball/config.json
sudo chmod 600 /etc/superball/config.json
echo "✓ Configuration installed to /etc/superball/config.json"
echo "⚠️ IMPORTANT: Edit /etc/superball/config.json with your private key and settings"
else
echo "✓ Configuration already exists at /etc/superball/config.json"
sudo mkdir -p "$INSTALL_DIR"
sudo cp daemon.js "$INSTALL_DIR/"
sudo cp package.json "$INSTALL_DIR/"
sudo cp config.json "$INSTALL_DIR/"
# Copy node_modules if it exists
if [ -d "node_modules" ]; then
sudo cp -r node_modules "$INSTALL_DIR/"
fi
# Set ownership and permissions
sudo chown -R "$DAEMON_USER:$DAEMON_USER" "$INSTALL_DIR"
sudo chmod 755 "$INSTALL_DIR"
sudo chmod 644 "$INSTALL_DIR"/*.js "$INSTALL_DIR"/*.json
sudo chmod -R 755 "$INSTALL_DIR/node_modules" 2>/dev/null || true
echo "✅ Files installed to $INSTALL_DIR"
# Create log directory
LOG_DIR="/var/log/superball-thrower"
echo "📝 Creating log directory: $LOG_DIR"
sudo mkdir -p "$LOG_DIR"
sudo chown "$DAEMON_USER:$DAEMON_USER" "$LOG_DIR"
sudo chmod 755 "$LOG_DIR"
echo "✅ Log directory created"
# Install systemd service
echo "Installing systemd service..."
sudo cp "$DAEMON_DIR/superball-thrower.service" /etc/systemd/system/
sudo sed -i "s|/path/to/thrower_daemon|$DAEMON_DIR|g" /etc/systemd/system/superball-thrower.service
echo "⚙️ Installing systemd service..."
sudo cp superball-thrower.service /etc/systemd/system/
sudo systemctl daemon-reload
echo "✓ Systemd service installed"
sudo systemctl enable superball-thrower.service
# Set permissions
echo "Setting permissions..."
sudo chown -R "$DAEMON_USER:$DAEMON_USER" "$DAEMON_DIR"
sudo chmod +x "$DAEMON_DIR/daemon.js"
echo "✓ Permissions set"
echo "✅ Systemd service installed and enabled"
echo
echo "=== Installation Complete ==="
echo
echo "Next steps:"
echo "1. Edit the configuration file:"
echo " sudo nano /etc/superball/config.json"
echo
echo "2. Add your private key and configure relays"
echo
echo "3. Enable and start the service:"
echo " sudo systemctl enable superball-thrower"
echo " sudo systemctl start superball-thrower"
echo
echo "4. Check service status:"
echo " sudo systemctl status superball-thrower"
echo
echo "5. View logs:"
echo " sudo journalctl -u superball-thrower -f"
echo
echo "Configuration file location: /etc/superball/config.json"
echo "Log file location: /var/log/superball/daemon.log"
echo "Service name: superball-thrower"
echo
echo "⚠️ SECURITY WARNING:"
echo " Make sure to secure your private key in the configuration file!"
echo " The config file should only be readable by the superball user."
# Initialize daemon if no keypair was provided
if [ -z "$PRIVATE_KEY" ]; then
echo "🔑 Initializing daemon to generate keypair..."
# Create a simple init script that generates keypair
sudo -u "$DAEMON_USER" bash -c "
cd \"$INSTALL_DIR\"
/usr/local/bin/node -e \"
const { generateSecretKey, getPublicKey } = require('nostr-tools/pure');
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
const sk = generateSecretKey();
const pk = getPublicKey(sk);
config.thrower.privateKey = Buffer.from(sk).toString('hex');
config.thrower.publicKey = pk;
fs.writeFileSync('config.json', JSON.stringify(config, null, 2));
console.log('Generated keypair:');
console.log('Private key:', Buffer.from(sk).toString('hex'));
console.log('Public key:', pk);
\"
"
echo "✅ Keypair generated"
fi
# Installation complete
echo ""
echo "🎉 Installation completed successfully!"
echo ""
echo "📋 Next steps:"
echo " 1. Start the service: sudo systemctl start superball-thrower"
echo " 2. Check status: sudo systemctl status superball-thrower"
echo " 3. View logs: sudo journalctl -u superball-thrower -f"
echo ""
echo "📖 For more information, see the README.md file"
echo ""
echo "🔐 Security notes:"
echo " - The daemon runs as user '$DAEMON_USER' with limited privileges"
echo " - Configuration files are owned by the daemon user"
echo " - Logs are written to $LOG_DIR"
echo ""
echo "⚙️ Configuration summary:"
echo " - Thrower name: $THROWER_NAME"
echo " - Description: $THROWER_DESCRIPTION"
echo " - Config file: $INSTALL_DIR/config.json"
echo ""

View File

@@ -3,9 +3,19 @@
# Superball Thrower Daemon Installation Script
# This script installs and configures the Superball Thrower daemon
sudo apt install nodejs npm
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"
# Download and install Node.js:
nvm install 22
# Verify the Node.js version:
node -v # Should print "v22.20.0".
# Verify npm version:
npm -v # Should print "10.9.3".
# Verify installation
node --version
npm --version

View File

@@ -6,10 +6,11 @@ Wants=network-online.target
[Service]
Type=simple
User=superball
Group=superball
WorkingDirectory=/path/to/thrower_daemon
ExecStart=/usr/bin/node daemon.js /etc/superball/config.json
User=superball-thrower
Group=superball-thrower
WorkingDirectory=/opt/superball-thrower
ExecStart=/usr/local/bin/node daemon.js start
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Restart=always
RestartSec=10
StandardOutput=journal
@@ -21,14 +22,14 @@ NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/superball
ReadWritePaths=/var/log/superball-thrower /opt/superball-thrower
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
# MemoryDenyWriteExecute=true # Disabled - conflicts with Node.js JIT compilation
RestrictNamespaces=true
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

View File

@@ -142,7 +142,8 @@ button:active {
label {
display: block;
font-weight: bold;
margin-bottom: 3px;
margin-bottom: 2px;
margin-top: 7px;
font-family: var(--font-family);
color: var(--primary-color);
}
@@ -738,3 +739,65 @@ small {
.floating-tab:hover {
transform: scale(1.05);
}
/* .input-group {
margin: 20px;
} */
textarea {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.tabs {
display: flex;
margin: 20px 20px 0;
border-bottom: 1px solid #ccc;
}
.tab {
padding: 10px 20px;
cursor: pointer;
background-color: #f1f1f1;
border: 1px solid #ccc;
border-bottom: none;
margin-right: 5px;
border-radius: 4px 4px 0 0;
}
.tab.active {
background-color: #fff;
border-bottom: 1px solid #fff;
}
.tab-content {
display: none;
padding: 20px;
border: 1px solid #ccc;
border-radius: 4px;
}
.tab-content.active {
display: block;
}
.fetched-event {
background: var(--secondary-color);
border: 1px solid var(--muted-color);
border-radius: 4px;
padding: 10px;
font-family: var(--font-family);
font-size: 16px;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 400px;
overflow-y: auto;
margin: 10px 0;
color: var(--primary-color);
display: none;
}

View File

@@ -27,13 +27,63 @@
<!-- FINAL EVENT SECTION -->
<div class="section">
<h2>Final Event (What gets posted at the end)</h2>
<div class="input-group">
<label for="final-content">Message Content:</label>
<textarea id="final-content" rows="3" placeholder="Enter your message content..."></textarea>
<div class="tabs">
<div class="tab active" data-tab="tab1">Post</div>
<div class="tab" data-tab="tab2">Reply</div>
<div class="tab" data-tab="tab3">Create/Edit Profile</div>
<div class="tab" data-tab="tab4">Custom Event</div>
</div>
<div class="tab-content active" id="tab1">
<h3>Post</h3>
<label for="post-content">Message Content:</label>
<textarea id="post-content" rows="3" placeholder="Enter your message content..."></textarea>
</div>
<div class="tab-content" id="tab2">
<h3>Reply</h3>
<label for="reply-id">EventId/NoteId/Nevent:</label>
<textarea id="reply-id" placeholder="Enter the nevent for the note..."></textarea>
<label for="fetched-event-display" id="fetched-event-label" style="display: none;">Event Replying To:</label>
<div id="fetched-event-display" class="fetched-event"></div>
<label for="reply-content">Message Content:</label>
<textarea id="reply-content" rows="3" placeholder="Enter your message content..."></textarea>
</div>
<div class="tab-content" id="tab3">
<h3>Create Profile</h3>
<label for="name">Name:</label>
<textarea id="name" placeholder="Enter your name..."></textarea>
<label for="about">About:</label>
<textarea id="about" rows="2" placeholder="A short bio..."></textarea>
<label for="profile-pic">Profile Picture:</label>
<textarea id="profile-pic" placeholder="URL of your profile pic..."></textarea>
<label for="display-name">Display Name:</label>
<textarea id="display-name" placeholder="Enter your display name..."></textarea>
<label for="website">Website:</label>
<textarea id="website" placeholder="Web URL..."></textarea>
<label for="banner">Banner:</label>
<textarea id="banner" placeholder="Enter your bannerm a (~1024x768) wide picture url..."></textarea>
<label for="nip05">NIP05:</label>
<textarea id="nip05" placeholder="Enter your nip05 in the format username@domain.com..."></textarea>
<label for="lud16">Lightning Address:</label>
<textarea id="lud16" placeholder="Enter your lightning address..."></textarea>
</div>
<div class="tab-content" id="tab4">
<h3>Custom Event</h3>
<label for="kind">Event Kind:</label>
<textarea id="kind" placeholder="Enter your event kind. Ex: 0, 1, 30000..."></textarea>
<label for="content">Content:</label>
<textarea id="content" rows="3" placeholder="A short bio..."></textarea>
<label for="tags">Tags:</label>
<textarea id="tags" rows="3" placeholder="Tags in format [['p', 'pubkey...'], ['e', 'event_id', wss://nos.lol, 'pubkey...']]"></textarea>
</div>
</div>
<button onclick="createFinalEvent()">Create Event That Will Be Published Publicly</button>
<h2 style="padding-top: 16px;">Final Event (What gets posted at the end)</h2>
<div id="final-event-display" class="json-display"></div>
</div>
@@ -71,14 +121,34 @@
</div>
<!-- Load the official nostr-tools bundle first -->
<!-- <script src="./nostr.bundle.js"></script> -->
<script src="https://laantungir.net/nostr-login-lite/nostr.bundle.js"></script>
<script src="./nostr.bundle.js"></script>
<!-- <script src="https://laantungir.net/nostr-login-lite/nostr.bundle.js"></script> -->
<!-- Load NOSTR_LOGIN_LITE main library -->
<script src="https://laantungir.net/nostr-login-lite/nostr-lite.js"></script>
<!-- <script src="./nostr-lite.js"></script> -->
<!-- <script src="https://laantungir.net/nostr-login-lite/nostr-lite.js"></script> -->
<script src="./nostr-lite.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// Remove active class from all tabs and contents
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
// Add active class to clicked tab
tab.classList.add('active');
// Show corresponding content
const tabId = tab.getAttribute('data-tab');
document.getElementById(tabId).classList.add('active');
});
});
});
// Global variables
let nlLite = null;
let userPubkey = null;
@@ -190,6 +260,11 @@
const name = profile.name || profile.display_name || profile.displayName || 'Anonymous User';
const about = profile.about || '';
const picture = profile.picture || '';
const display_name = profile.display_name || '';
const website = profile.website || '';
const banner = profile.banner || '';
const nip05 = profile.nip05 || '';
const lud16 = profile.lud16 || '';
document.getElementById('profile-name').textContent = name;
@@ -197,6 +272,17 @@
document.getElementById('profile-picture').src = picture;
}
// Edit Profile section
document.getElementById('name').textContent = name;
document.getElementById('about').textContent = about;
document.getElementById('profile-pic').textContent = picture;
document.getElementById('display-name').textContent = display_name;
document.getElementById('website').textContent = website;
document.getElementById('banner').textContent = banner;
document.getElementById('nip05').textContent = nip05;
document.getElementById('lud16').textContent = lud16;
console.log('SUCCESS', `Profile displayed: ${name}`);
}
@@ -232,7 +318,11 @@
} else {
console.log('INFO', 'No relay list found, using defaults');
userRelays = [
{ url: 'wss://relay.laantungir.net', type: 'both' }
{ url: 'wss://relay.laantungir.net', type: 'both' },
{ url: 'wss://relay.primal.net', type: 'both' },
{ url: 'wss://nos.lol', type: 'both' },
{ url: 'wss://relay.damus.io', type: 'both' },
{ url: 'wss://offchain.pub', type: 'both' }
];
return userRelays;
}
@@ -710,23 +800,247 @@
}
}
// Load event from id
async function loadReplyTagsForEvent(id, knownRelays) {
if (!id) return;
console.log('INFO', `Loading event`, id);
try {
const pool = new window.NostrTools.SimplePool();
const relays = [relayUrl, 'wss://relay.laantungir.net', 'wss://nos.lol', 'wss://relay.primal.net', 'wss://relay.damus.io', 'wss://relay.nostr.band'].concat(knownRelays);
// Enable tracking
pool.trackRelays = true;
// Query for an event
const events = await pool.querySync(relays, {
ids: [id],
limit: 1
});
const event = events[0];
let returnEventTags = [];
let relaysSeenOn = [];
if (event) {
const seenRelays = pool.seenOn.get(event.id);
relaysSeenOn = seenRelays ? Array.from(seenRelays).map(r => r.url) : [];
}
pool.close(relays);
if (events.length > 0) {
// event is a nostr event with tags
const refs = window.NostrTools.nip10.parse(events[0])
// get the root event of the thread
if (refs.root) {
const relay = refs.root.relays.length > 0 ? refs.root.relays[0] : '';
if (refs.root.author) {
returnEventTags.push(['e', refs.root.id, relay, 'root', refs.root.author]);
}
else {
returnEventTags.push(['e', refs.root.id, relay, 'root']);
}
returnEventTags.push(['e', id, relaysSeenOn[0], 'reply', event.pubkey]);
if (refs.root.author)
returnEventTags.push(['p', refs.root.author, relay]);
returnEventTags.push(['p', event.pubkey, relaysSeenOn[0]]);
} else {
returnEventTags.push(['e', id, relaysSeenOn[0], 'root']);
returnEventTags.push(['p', event.pubkey]);
}
// get any referenced profiles
for (let profile of refs.profiles) {
if (!returnEventTags.some(tag => tag[0] === 'p' && tag[1] === profile.pubkey)) {
if (profile.relays.length > 0) {
returnEventTags.push(['p', profile.pubkey, profile.relays[0]]);
}
else {
returnEventTags.push(['p', profile.pubkey]);
}
}
}
return [events[0], returnEventTags];
} else {
console.log('INFO', 'Event not found');
return null;
}
} catch (error) {
console.log('ERROR', `Profile loading failed: ${error.message}`);
}
}
// Create final event (kind 1)
async function createFinalEvent() {
const content = document.getElementById('final-content').value.trim();
// Get the active tab
const activeTab = document.querySelector('.tab.active').getAttribute('data-tab');
// Get content based on active tab
let content = '';
let replyEventId = '';
let replyEvent = {};
let replyTags = [];
let name = '';
let about = '';
let profilePic = '';
let displayName = '';
let website = '';
let banner = '';
let nip05 = '';
let lud16 = '';
let tags = '';
let kind = '';
switch(activeTab) {
case 'tab1': // Post
content = document.getElementById('post-content').value.trim();
break;
case 'tab2': // Reply
content = document.getElementById('reply-content').value.trim();
replyEventId = document.getElementById('reply-id').value.trim();
break;
case 'tab3': // Create Profile
name = document.getElementById('name').value.trim();
about = document.getElementById('about').value.trim();
profilePic = document.getElementById('profile-pic').value.trim();
displayName = document.getElementById('display-name').value.trim();
website = document.getElementById('website').value.trim();
banner = document.getElementById('banner').value.trim();
nip05 = document.getElementById('nip05').value.trim();
lud16 = document.getElementById('lud16').value.trim();
break;
case 'tab4': // Custom Event
kind = document.getElementById('kind').value.trim();
content = document.getElementById('content').value.trim();
tags = document.getElementById('tags').value.trim();
break;
}
// Validate content based on tab
if (activeTab === 'tab1') {
if (!content) {
alert('Please enter message content');
return;
}
} else if (activeTab === 'tab2') {
if (!content) {
alert('Please enter message content');
return;
}
let eventId = '';
let knownRelays = [];
try {
if (replyEventId.startsWith('nevent')) {
const replyEventData = window.NostrTools.nip19.decode(replyEventId).data;
eventId = replyEventData.id;
knownRelays = replyEventData.relays;
} else if (replyEventId.startsWith('note')) {
eventId = window.NostrTools.nip19.decode(replyEventId).data;
} else {
eventId = replyEventId;
}
} catch (error) {
console.error(error);
alert('Error decoding nevent string', error.message);
return;
}
const regex = /^[0-9a-f]{64}$/;
if (!regex.test(eventId)) {
alert('Invalid event ID');
}
try {
// Create the final event (kind 1) - pure message, no relay info
const eventTemplate = {
document.getElementById('fetched-event-display').textContent = 'Loading event...';
document.getElementById('fetched-event-display').style.display = 'block';
document.getElementById('fetched-event-label').style.display = 'block';
[ replyEvent, replyTags ] = await loadReplyTagsForEvent(eventId, knownRelays);
console.log("Event replying to: ", replyEvent)
document.getElementById('fetched-event-display').textContent = replyEvent.content;
} catch (error) {
alert('Error fetching reply event', error.message);
return;
}
} else if (activeTab === 'tab3') {
if (!name) {
alert('Please enter your name');
return;
}
} else if (activeTab === 'tab4') {
if (!kind) {
alert('Please enter the event kind');
return;
}
if (!/^-?\d+$/.test(kind)) {
alert("Please enter a valid integer for event kind.");
return; // Prevent form submission
}
try {
kind = Number(kind);
tags = JSON.parse(tags.replace(/'/g, '"'))
} catch (error) {
alert('Error parsing tags', error.message);
return;
}
}
try {
let eventTemplate = {};
switch(activeTab) {
case 'tab1': // Post
eventTemplate = {
kind: 1,
content: content,
tags: [],
created_at: Math.floor(Date.now() / 1000)
};
break;
case 'tab2': // Reply
eventTemplate = {
kind: 1,
content: content,
tags: replyTags,
created_at: Math.floor(Date.now() / 1000)
};
break;
case 'tab3': // Create Profile
eventTemplate = {
kind: 0,
content: JSON.stringify({
name: name,
about: about,
picture: profilePic,
display_name: displayName,
website: website,
banner: banner,
nip05: nip05,
lud16: lud16
}),
tags: [],
created_at: Math.floor(Date.now() / 1000)
};
break;
case 'tab4': // Create Profile
eventTemplate = {
kind: kind,
content: content,
tags: tags,
created_at: Math.floor(Date.now() / 1000)
};
break;
}
// Your existing event publishing logic here
console.log('Event to publish:', eventTemplate);
// ... rest of your publishing code
// Sign the event using window.nostr (NIP-07)
finalEvent = await window.nostr.signEvent(eventTemplate);
@@ -1928,8 +2242,11 @@
bounceCounter = 0;
// Clear UI elements
document.getElementById('final-content').value = '';
document.getElementById('post-content').value = '';
document.getElementById('final-event-display').textContent = '';
document.getElementById('fetched-event-display').textContent = '';
document.getElementById('fetched-event-display').style.display = 'none';
document.getElementById('fetched-event-label').style.display = 'none';
document.getElementById('bounces-container').innerHTML = '';
// Hide the Add Bounce button since no final event exists