4 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
3 changed files with 204 additions and 104 deletions

View File

@@ -1,49 +0,0 @@
Reply to a post from Primal
{
"kind": 1,
"created_at": 1763420960,
"content": "From Primal.",
"pubkey": "8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e",
"id": "555c77e419c6f34d1d36b6baf36489c5eb17d829645c940fc996d90c26df59a1",
"tags": [
[
"e",
"ac50dd87901863590d577fb9080ab294b8dd1b6b10563e9733d5bad817c735db",
"wss://nos.lol",
"root"
],
[
"p",
"8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e"
],
[
"r",
"wss://nostr.mom/"
],
[
"r",
"wss://relay.laantungir.net/"
]
],
"sig": "5ab8f7b43e972e2089ebc95f68c49cace183832a3f867801a85c689ce6797eaa52a60ba07847212019fc2361ffca276c74f9cd69402095c6c783faac2c827bde"
}
Reply to post from proposed superball builder change.
{
"id": "56e6dadc32afe11f13e83fcf8107ea8a819e4a96232d64eacafd10370224e685",
"sig": "3766102783acfc7654f134b1dc5fd716e89e154fc81a96c7f60d20a867e5b52919cdfa7a01151550880e4e8a2945ef05feb43c022bbd7761724ded8308254b1d",
"kind": 1,
"tags": [
[
"e",
"ac50dd87901863590d577fb9080ab294b8dd1b6b10563e9733d5bad817c735db",
"root"
]
],
"pubkey": "8ff74724ed641b3c28e5a86d7c5cbc49c37638ace8c6c38935860e7a5eedde0e",
"content": "Yeah, it is a great night.",
"created_at": 1763420892
}

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);
}
@@ -744,11 +745,6 @@ small {
margin: 20px;
} */
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
textarea {
width: 100%;
@@ -788,4 +784,20 @@ textarea {
.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,29 +27,32 @@
<!-- FINAL EVENT SECTION -->
<div class="section">
<h2>Final Event (What gets posted at the end)</h2>
<div class="input-group">
<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="final-content">Message Content:</label>
<textarea id="final-content" rows="3" placeholder="Enter your message content..."></textarea>
<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="nevent">Nevent:</label>
<textarea id="nevent" placeholder="Enter the nevent for the note..."></textarea>
<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/Edit Profile</h3>
<h3>Create Profile</h3>
<label for="name">Name:</label>
<textarea id="name" placeholder="Enter your name..."></textarea>
<label for="about">About:</label>
@@ -67,9 +70,20 @@
<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>
@@ -107,12 +121,12 @@
</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>
@@ -132,11 +146,6 @@
// Show corresponding content
const tabId = tab.getAttribute('data-tab');
document.getElementById(tabId).classList.add('active');
// If tab3 (Create/Edit Profile) is activated, populate form with existing profile data
if (tabId === 'tab3' && userProfileData) {
populateProfileForm(userProfileData);
}
});
});
});
@@ -149,7 +158,6 @@
let bounceCounter = 0;
let discoveredThrowers = [];
let userRelays = [];
let userProfileData = null; // Store loaded profile data
// Initialize NOSTR_LOGIN_LITE
async function initializeApp() {
@@ -235,7 +243,6 @@
if (events.length > 0) {
console.log('SUCCESS', 'Profile event received');
const profile = JSON.parse(events[0].content);
userProfileData = profile; // Store profile data globally
displayProfile(profile);
} else {
console.log('INFO', 'No profile found');
@@ -253,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;
@@ -260,24 +272,18 @@
document.getElementById('profile-picture').src = picture;
}
console.log('SUCCESS', `Profile displayed: ${name}`);
}
// Edit Profile section
// Populate profile form with existing profile data
function populateProfileForm(profile) {
if (!profile) return;
// Populate each field with existing data if available
document.getElementById('name').value = profile.name || '';
document.getElementById('about').value = profile.about || '';
document.getElementById('profile-pic').value = profile.picture || '';
document.getElementById('display-name').value = profile.display_name || profile.displayName || '';
document.getElementById('website').value = profile.website || '';
document.getElementById('banner').value = profile.banner || '';
document.getElementById('nip05').value = profile.nip05 || '';
document.getElementById('lud16').value = profile.lud16 || '';
console.log('INFO', 'Profile form populated with existing data');
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}`);
}
// Load user's NIP-65 relay list
@@ -794,6 +800,79 @@
}
}
// 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() {
// Get the active tab
@@ -801,8 +880,9 @@
// Get content based on active tab
let content = '';
let nevent = '';
let neventData;
let replyEventId = '';
let replyEvent = {};
let replyTags = [];
let name = '';
let about = '';
let profilePic = '';
@@ -811,14 +891,16 @@
let banner = '';
let nip05 = '';
let lud16 = '';
let tags = '';
let kind = '';
switch(activeTab) {
case 'tab1': // Post
content = document.getElementById('final-content').value.trim();
content = document.getElementById('post-content').value.trim();
break;
case 'tab2': // Reply
content = document.getElementById('reply-content').value.trim();
nevent = document.getElementById('nevent').value.trim();
replyEventId = document.getElementById('reply-id').value.trim();
break;
case 'tab3': // Create Profile
name = document.getElementById('name').value.trim();
@@ -830,8 +912,13 @@
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) {
@@ -843,14 +930,39 @@
alert('Please enter message content');
return;
}
if (!nevent.startsWith('nevent')) {
alert('Please enter a valid nevent');
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 {
neventData = window.NostrTools.nip19.decode(nevent).data;
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 decoding nevent string', error.message);
alert('Error fetching reply event', error.message);
return;
}
} else if (activeTab === 'tab3') {
@@ -858,6 +970,22 @@
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 {
@@ -877,12 +1005,9 @@
eventTemplate = {
kind: 1,
content: content,
tags: [['e', neventData.id, neventData.relays[0], 'root'], ['p', neventData.author]],
tags: replyTags,
created_at: Math.floor(Date.now() / 1000)
};
neventData.relays.slice(1).forEach(relay => {
eventTemplate.tags.push(['r', relay]);
});
break;
case 'tab3': // Create Profile
@@ -902,6 +1027,15 @@
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
@@ -2108,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