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
2 changed files with 204 additions and 31 deletions

View File

@@ -142,7 +142,8 @@ button:active {
label { label {
display: block; display: block;
font-weight: bold; font-weight: bold;
margin-bottom: 3px; margin-bottom: 2px;
margin-top: 7px;
font-family: var(--font-family); font-family: var(--font-family);
color: var(--primary-color); color: var(--primary-color);
} }
@@ -744,11 +745,6 @@ small {
margin: 20px; margin: 20px;
} */ } */
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
textarea { textarea {
width: 100%; width: 100%;
@@ -788,4 +784,20 @@ textarea {
.tab-content.active { .tab-content.active {
display: block; 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,23 +27,26 @@
<!-- FINAL EVENT SECTION --> <!-- FINAL EVENT SECTION -->
<div class="section"> <div class="section">
<h2>Final Event (What gets posted at the end)</h2>
<div class="input-group"> <div class="input-group">
<div class="tabs"> <div class="tabs">
<div class="tab active" data-tab="tab1">Post</div> <div class="tab active" data-tab="tab1">Post</div>
<div class="tab" data-tab="tab2">Reply</div> <div class="tab" data-tab="tab2">Reply</div>
<div class="tab" data-tab="tab3">Create Profile</div> <div class="tab" data-tab="tab3">Create/Edit Profile</div>
<div class="tab" data-tab="tab4">Custom Event</div>
</div> </div>
<div class="tab-content active" id="tab1"> <div class="tab-content active" id="tab1">
<h3>Post</h3> <h3>Post</h3>
<label for="final-content">Message Content:</label> <label for="post-content">Message Content:</label>
<textarea id="final-content" rows="3" placeholder="Enter your message content..."></textarea> <textarea id="post-content" rows="3" placeholder="Enter your message content..."></textarea>
</div> </div>
<div class="tab-content" id="tab2"> <div class="tab-content" id="tab2">
<h3>Reply</h3> <h3>Reply</h3>
<label for="nevent">Nevent:</label> <label for="reply-id">EventId/NoteId/Nevent:</label>
<textarea id="nevent" placeholder="Enter the nevent for the note..."></textarea> <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> <label for="reply-content">Message Content:</label>
<textarea id="reply-content" rows="3" placeholder="Enter your message content..."></textarea> <textarea id="reply-content" rows="3" placeholder="Enter your message content..."></textarea>
</div> </div>
@@ -67,9 +70,20 @@
<label for="lud16">Lightning Address:</label> <label for="lud16">Lightning Address:</label>
<textarea id="lud16" placeholder="Enter your lightning address..."></textarea> <textarea id="lud16" placeholder="Enter your lightning address..."></textarea>
</div> </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> </div>
<button onclick="createFinalEvent()">Create Event That Will Be Published Publicly</button> <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 id="final-event-display" class="json-display"></div>
</div> </div>
@@ -107,12 +121,12 @@
</div> </div>
<!-- Load the official nostr-tools bundle first --> <!-- Load the official nostr-tools bundle first -->
<!-- <script src="./nostr.bundle.js"></script> --> <script src="./nostr.bundle.js"></script>
<script src="https://laantungir.net/nostr-login-lite/nostr.bundle.js"></script> <!-- <script src="https://laantungir.net/nostr-login-lite/nostr.bundle.js"></script> -->
<!-- Load NOSTR_LOGIN_LITE main library --> <!-- Load NOSTR_LOGIN_LITE main library -->
<script src="https://laantungir.net/nostr-login-lite/nostr-lite.js"></script> <!-- <script src="https://laantungir.net/nostr-login-lite/nostr-lite.js"></script> -->
<!-- <script src="./nostr-lite.js"></script> --> <script src="./nostr-lite.js"></script>
<script> <script>
@@ -246,6 +260,11 @@
const name = profile.name || profile.display_name || profile.displayName || 'Anonymous User'; const name = profile.name || profile.display_name || profile.displayName || 'Anonymous User';
const about = profile.about || ''; const about = profile.about || '';
const picture = profile.picture || ''; 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; document.getElementById('profile-name').textContent = name;
@@ -253,6 +272,17 @@
document.getElementById('profile-picture').src = picture; 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}`); console.log('SUCCESS', `Profile displayed: ${name}`);
} }
@@ -770,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) // Create final event (kind 1)
async function createFinalEvent() { async function createFinalEvent() {
// Get the active tab // Get the active tab
@@ -777,8 +880,9 @@
// Get content based on active tab // Get content based on active tab
let content = ''; let content = '';
let nevent = ''; let replyEventId = '';
let neventData; let replyEvent = {};
let replyTags = [];
let name = ''; let name = '';
let about = ''; let about = '';
let profilePic = ''; let profilePic = '';
@@ -787,14 +891,16 @@
let banner = ''; let banner = '';
let nip05 = ''; let nip05 = '';
let lud16 = ''; let lud16 = '';
let tags = '';
let kind = '';
switch(activeTab) { switch(activeTab) {
case 'tab1': // Post case 'tab1': // Post
content = document.getElementById('final-content').value.trim(); content = document.getElementById('post-content').value.trim();
break; break;
case 'tab2': // Reply case 'tab2': // Reply
content = document.getElementById('reply-content').value.trim(); content = document.getElementById('reply-content').value.trim();
nevent = document.getElementById('nevent').value.trim(); replyEventId = document.getElementById('reply-id').value.trim();
break; break;
case 'tab3': // Create Profile case 'tab3': // Create Profile
name = document.getElementById('name').value.trim(); name = document.getElementById('name').value.trim();
@@ -806,8 +912,13 @@
nip05 = document.getElementById('nip05').value.trim(); nip05 = document.getElementById('nip05').value.trim();
lud16 = document.getElementById('lud16').value.trim(); lud16 = document.getElementById('lud16').value.trim();
break; 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 // Validate content based on tab
if (activeTab === 'tab1') { if (activeTab === 'tab1') {
if (!content) { if (!content) {
@@ -819,14 +930,39 @@
alert('Please enter message content'); alert('Please enter message content');
return; return;
} }
if (!nevent.startsWith('nevent')) { let eventId = '';
alert('Please enter a valid nevent'); 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; return;
} }
const regex = /^[0-9a-f]{64}$/;
if (!regex.test(eventId)) {
alert('Invalid event ID');
}
try { 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) { } catch (error) {
alert('Error decoding nevent string', error.message); alert('Error fetching reply event', error.message);
return; return;
} }
} else if (activeTab === 'tab3') { } else if (activeTab === 'tab3') {
@@ -834,6 +970,22 @@
alert('Please enter your name'); alert('Please enter your name');
return; 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 { try {
@@ -853,12 +1005,9 @@
eventTemplate = { eventTemplate = {
kind: 1, kind: 1,
content: content, content: content,
tags: [['e', neventData.id, neventData.relays[0], 'root'], ['p', neventData.author]], tags: replyTags,
created_at: Math.floor(Date.now() / 1000) created_at: Math.floor(Date.now() / 1000)
}; };
neventData.relays.slice(1).forEach(relay => {
eventTemplate.tags.push(['r', relay]);
});
break; break;
case 'tab3': // Create Profile case 'tab3': // Create Profile
@@ -878,6 +1027,15 @@
created_at: Math.floor(Date.now() / 1000) created_at: Math.floor(Date.now() / 1000)
}; };
break; 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 // Your existing event publishing logic here
@@ -2084,8 +2242,11 @@
bounceCounter = 0; bounceCounter = 0;
// Clear UI elements // Clear UI elements
document.getElementById('final-content').value = ''; document.getElementById('post-content').value = '';
document.getElementById('final-event-display').textContent = ''; 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 = ''; document.getElementById('bounces-container').innerHTML = '';
// Hide the Add Bounce button since no final event exists // Hide the Add Bounce button since no final event exists