mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-09 15:38:44 +00:00
update
This commit is contained in:
@@ -279,7 +279,7 @@ class NosTubeApp {
|
|||||||
magnet: document.getElementById('magnet') ? document.getElementById('magnet').value.trim() : '',
|
magnet: document.getElementById('magnet') ? document.getElementById('magnet').value.trim() : '',
|
||||||
thumbnail: document.getElementById('thumbnail') ? document.getElementById('thumbnail').value.trim() : '',
|
thumbnail: document.getElementById('thumbnail') ? document.getElementById('thumbnail').value.trim() : '',
|
||||||
description: descriptionElement ? descriptionElement.value.trim() : '',
|
description: descriptionElement ? descriptionElement.value.trim() : '',
|
||||||
mode: isDevMode ? 'dev' : 'live',
|
mode: isDevMode ? 'dev' : 'live'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debugging Log: Check formData
|
// Debugging Log: Check formData
|
||||||
@@ -430,6 +430,16 @@ class NosTubeApp {
|
|||||||
};
|
};
|
||||||
const timeAgo = this.formatTimeAgo(video.created_at);
|
const timeAgo = this.formatTimeAgo(video.created_at);
|
||||||
|
|
||||||
|
// Only show "Edit" button if this user owns the video (video.pubkey === this.pubkey)
|
||||||
|
const canEdit = (video.pubkey === this.pubkey);
|
||||||
|
const editButton = canEdit
|
||||||
|
? `<button
|
||||||
|
class="mt-2 text-sm text-blue-400 hover:text-blue-300"
|
||||||
|
onclick="app.handleEditVideo(${index})">
|
||||||
|
Edit
|
||||||
|
</button>`
|
||||||
|
: '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="video-card bg-gray-900 rounded-lg overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300">
|
<div class="video-card bg-gray-900 rounded-lg overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300">
|
||||||
<div class="aspect-w-16 aspect-h-9 bg-gray-800 cursor-pointer relative group"
|
<div class="aspect-w-16 aspect-h-9 bg-gray-800 cursor-pointer relative group"
|
||||||
@@ -448,11 +458,11 @@ class NosTubeApp {
|
|||||||
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-opacity duration-300"></div>
|
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-opacity duration-300"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h3 class="text-lg font-bold text-white mb-3 line-clamp-2 hover:text-blue-400 cursor-pointer"
|
<h3 class="text-lg font-bold text-white mb-2 line-clamp-2 hover:text-blue-400 cursor-pointer"
|
||||||
onclick="app.playVideo('${encodeURIComponent(video.magnet)}')">
|
onclick="app.playVideo('${encodeURIComponent(video.magnet)}')">
|
||||||
${this.escapeHTML(video.title)}
|
${this.escapeHTML(video.title)}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex space-x-3 items-center">
|
<div class="flex space-x-3 items-center mb-2">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<div class="w-8 h-8 rounded-full bg-gray-700 overflow-hidden">
|
<div class="w-8 h-8 rounded-full bg-gray-700 overflow-hidden">
|
||||||
<img src="${this.escapeHTML(profile.picture)}" alt="${profile.name}" class="w-full h-full object-cover">
|
<img src="${this.escapeHTML(profile.picture)}" alt="${profile.name}" class="w-full h-full object-cover">
|
||||||
@@ -467,6 +477,7 @@ class NosTubeApp {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${editButton}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -708,6 +719,75 @@ class NosTubeApp {
|
|||||||
setTimeout(() => this.updateTorrentStatus(torrent), 1000);
|
setTimeout(() => this.updateTorrentStatus(torrent), 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the user to edit a video note (only if they are the owner).
|
||||||
|
* We reuse the note's existing d tag via nostrClient.editVideo.
|
||||||
|
* @param {number} index - The index of the video in the rendered list
|
||||||
|
*/
|
||||||
|
async handleEditVideo(index) {
|
||||||
|
try {
|
||||||
|
const videos = await nostrClient.fetchVideos();
|
||||||
|
const video = videos[index];
|
||||||
|
|
||||||
|
if (!this.pubkey) {
|
||||||
|
this.showError('Please login to edit videos.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (video.pubkey !== this.pubkey) {
|
||||||
|
this.showError('You do not own this video.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt for new fields, but leave old value if user cancels or leaves blank.
|
||||||
|
const newTitle = prompt('New Title? (Leave blank to keep existing)', video.title);
|
||||||
|
const newMagnet = prompt('New Magnet Link? (Leave blank to keep existing)', video.magnet);
|
||||||
|
const newThumbnail = prompt('New Thumbnail URL? (Leave blank to keep existing)', video.thumbnail);
|
||||||
|
const newDescription = prompt('New Description? (Leave blank to keep existing)', video.description);
|
||||||
|
|
||||||
|
// If user cancels ANY prompt, it returns `null`.
|
||||||
|
// If user typed nothing and clicked OK, it’s an empty string ''.
|
||||||
|
// So we do checks to keep the old value if needed:
|
||||||
|
const title = (newTitle === null || newTitle.trim() === '')
|
||||||
|
? video.title
|
||||||
|
: newTitle.trim();
|
||||||
|
|
||||||
|
const magnet = (newMagnet === null || newMagnet.trim() === '')
|
||||||
|
? video.magnet
|
||||||
|
: newMagnet.trim();
|
||||||
|
|
||||||
|
const thumbnail = (newThumbnail === null || newThumbnail.trim() === '')
|
||||||
|
? video.thumbnail
|
||||||
|
: newThumbnail.trim();
|
||||||
|
|
||||||
|
const description = (newDescription === null || newDescription.trim() === '')
|
||||||
|
? video.description
|
||||||
|
: newDescription.trim();
|
||||||
|
|
||||||
|
// Build updated data
|
||||||
|
const updatedData = {
|
||||||
|
title,
|
||||||
|
magnet,
|
||||||
|
thumbnail,
|
||||||
|
description,
|
||||||
|
mode: isDevMode ? 'dev' : 'live'
|
||||||
|
};
|
||||||
|
|
||||||
|
const originalEvent = {
|
||||||
|
id: video.id,
|
||||||
|
pubkey: video.pubkey,
|
||||||
|
tags: video.tags // Must include ["d","someValue"] to reuse the same note
|
||||||
|
};
|
||||||
|
|
||||||
|
await nostrClient.editVideo(originalEvent, updatedData, this.pubkey);
|
||||||
|
this.showSuccess('Video updated successfully!');
|
||||||
|
await this.loadVideos();
|
||||||
|
} catch (err) {
|
||||||
|
this.log('Failed to edit video:', err.message);
|
||||||
|
this.showError('Failed to edit video. Please try again later.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const app = new NosTubeApp();
|
export const app = new NosTubeApp();
|
||||||
|
102
src/js/nostr.js
102
src/js/nostr.js
@@ -143,7 +143,7 @@ class NostrClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes a new video event to all relays.
|
* Publishes a new video event to all relays (creates a new note).
|
||||||
*/
|
*/
|
||||||
async publishVideo(videoData, pubkey) {
|
async publishVideo(videoData, pubkey) {
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
@@ -212,6 +212,81 @@ class NostrClient {
|
|||||||
throw new Error('Failed to sign event.');
|
throw new Error('Failed to sign event.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits an existing video event by reusing its "d" tag.
|
||||||
|
* @param {Object} originalEvent - The entire event object you're editing.
|
||||||
|
* @param {Object} updatedVideoData - The updated fields (title, magnet, etc.).
|
||||||
|
* @param {string} pubkey - The user's pubkey (must match originalEvent.pubkey).
|
||||||
|
*/
|
||||||
|
async editVideo(originalEvent, updatedVideoData, pubkey) {
|
||||||
|
if (!pubkey) {
|
||||||
|
throw new Error('User is not logged in.');
|
||||||
|
}
|
||||||
|
if (originalEvent.pubkey !== pubkey) {
|
||||||
|
throw new Error('You do not own this event (different pubkey).');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugging log
|
||||||
|
if (isDevMode) {
|
||||||
|
console.log('Editing video event:', originalEvent);
|
||||||
|
console.log('New video data:', updatedVideoData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab the d tag from the original event
|
||||||
|
const dTag = originalEvent.tags.find(tag => tag[0] === 'd');
|
||||||
|
if (!dTag) {
|
||||||
|
throw new Error('This event has no "d" tag, cannot edit as addressable kind=30078.');
|
||||||
|
}
|
||||||
|
const existingD = dTag[1];
|
||||||
|
|
||||||
|
// Build the updated event with the same (kind, pubkey, d) so relays see it as an update
|
||||||
|
const event = {
|
||||||
|
kind: 30078,
|
||||||
|
pubkey,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [
|
||||||
|
['t', 'video'],
|
||||||
|
['d', existingD]
|
||||||
|
],
|
||||||
|
content: JSON.stringify(updatedVideoData)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDevMode) {
|
||||||
|
console.log('Reusing d tag:', existingD);
|
||||||
|
console.log('Updated event content:', event.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Sign the new updated event
|
||||||
|
const signedEvent = await window.nostr.signEvent(event);
|
||||||
|
|
||||||
|
if (isDevMode) {
|
||||||
|
console.log('Signed edited event:', signedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish the edited event to all relays
|
||||||
|
await Promise.all(this.relays.map(async url => {
|
||||||
|
try {
|
||||||
|
await this.pool.publish([url], signedEvent);
|
||||||
|
if (isDevMode) {
|
||||||
|
console.log(`Edited event published to ${url} (d="${existingD}")`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (isDevMode) {
|
||||||
|
console.error(`Failed to publish edited event to ${url}:`, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return signedEvent;
|
||||||
|
} catch (error) {
|
||||||
|
if (isDevMode) {
|
||||||
|
console.error('Failed to sign edited event:', error.message);
|
||||||
|
}
|
||||||
|
throw new Error('Failed to sign edited event.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches videos from all configured relays.
|
* Fetches videos from all configured relays.
|
||||||
@@ -227,7 +302,6 @@ class NostrClient {
|
|||||||
// Use a Map so duplicates (same event ID) across multiple relays don't overwrite each other
|
// Use a Map so duplicates (same event ID) across multiple relays don't overwrite each other
|
||||||
const videoEvents = new Map();
|
const videoEvents = new Map();
|
||||||
|
|
||||||
// Optional: Only log if in dev mode (to avoid flooding console in production).
|
|
||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
console.log('[fetchVideos] Starting fetch from all relays...');
|
console.log('[fetchVideos] Starting fetch from all relays...');
|
||||||
console.log('[fetchVideos] Filter:', filter);
|
console.log('[fetchVideos] Filter:', filter);
|
||||||
@@ -237,24 +311,20 @@ class NostrClient {
|
|||||||
// Fetch from each relay in parallel
|
// Fetch from each relay in parallel
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.relays.map(async (url) => {
|
this.relays.map(async (url) => {
|
||||||
// Log relay being queried
|
|
||||||
if (isDevMode) console.log(`[fetchVideos] Querying relay: ${url}`);
|
if (isDevMode) console.log(`[fetchVideos] Querying relay: ${url}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const events = await this.pool.list([url], [filter]);
|
const events = await this.pool.list([url], [filter]);
|
||||||
|
|
||||||
// How many events came back from this relay?
|
|
||||||
if (isDevMode) {
|
if (isDevMode) {
|
||||||
console.log(`Events from ${url}:`, events.length);
|
console.log(`Events from ${url}:`, events.length);
|
||||||
}
|
if (events.length > 0) {
|
||||||
|
events.forEach((evt, idx) => {
|
||||||
// For deeper insight, you can log each event
|
console.log(
|
||||||
if (isDevMode && events.length > 0) {
|
`[fetchVideos] [${url}] Event[${idx}] ID: ${evt.id} | pubkey: ${evt.pubkey} | created_at: ${evt.created_at}`
|
||||||
events.forEach((evt, idx) => {
|
);
|
||||||
console.log(
|
});
|
||||||
`[fetchVideos] [${url}] Event[${idx}] ID: ${evt.id} | pubkey: ${evt.pubkey} | created_at: ${evt.created_at}`
|
}
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each event
|
// Process each event
|
||||||
@@ -272,7 +342,9 @@ class NostrClient {
|
|||||||
description: content.description || '',
|
description: content.description || '',
|
||||||
mode: content.mode || 'live',
|
mode: content.mode || 'live',
|
||||||
pubkey: event.pubkey,
|
pubkey: event.pubkey,
|
||||||
created_at: event.created_at
|
created_at: event.created_at,
|
||||||
|
// Keep the original tags array in case we need them later (e.g. for editing)
|
||||||
|
tags: event.tags
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
@@ -330,7 +402,7 @@ class NostrClient {
|
|||||||
typeof content.mode === 'string' &&
|
typeof content.mode === 'string' &&
|
||||||
['dev', 'live'].includes(content.mode) &&
|
['dev', 'live'].includes(content.mode) &&
|
||||||
(typeof content.thumbnail === 'string' || typeof content.thumbnail === 'undefined') &&
|
(typeof content.thumbnail === 'string' || typeof content.thumbnail === 'undefined') &&
|
||||||
(typeof content.description === 'string' || typeof content.description === 'undefined') // Ensure description is optional
|
(typeof content.description === 'string' || typeof content.description === 'undefined')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isDevMode && !isValid) {
|
if (isDevMode && !isValid) {
|
||||||
|
Reference in New Issue
Block a user