mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2025-09-08 23:18:43 +00:00
updated directory structure to fix root Service Worker issues and performance improvements
This commit is contained in:
24
content/about.md
Normal file
24
content/about.md
Normal file
@@ -0,0 +1,24 @@
|
||||

|
||||
|
||||
# About bitvid
|
||||
|
||||
Welcome to bitvid, a new kind of video platform that puts you in control. Unlike traditional video sites that keep your content on their servers, bitvid lets videos flow directly between creators and viewers. Think of it like a digital potluck where everyone brings and shares content directly with each other!
|
||||
|
||||
## What Makes bitvid Different?
|
||||
|
||||
- **You're in Control**: Your videos stay yours. No company owns or controls your content – you share it directly with your viewers.
|
||||
- **Always Available**: Because videos are shared between viewers, popular content actually loads faster instead of slower. No more buffering during peak times!
|
||||
- **Privacy First**: No need for email or password – you log in with a secure key that only you control.
|
||||
- **Support Creators Directly**: Send tips to creators you love without a platform taking a big cut.
|
||||
- **Free and Open**: bitvid's code is open source, meaning anyone can check how it works or help make it better.
|
||||
|
||||
## How Does It Work?
|
||||
|
||||
bitvid uses two main technologies to make this all possible:
|
||||
|
||||
1. **WebTorrent**: This lets viewers stream videos directly from other viewers, like a relay race passing the content from person to person.
|
||||
2. **Nostr**: This handles your login and video details, like a digital ID card that works across many sites.
|
||||
|
||||
## Join the Revolution
|
||||
|
||||
Whether you're tired of traditional platforms controlling your content, care about privacy, or just want a better way to share videos, bitvid offers a fresh alternative. Come be part of the future of video sharing!
|
79
content/community-guidelines.md
Normal file
79
content/community-guidelines.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# **bitvid Community Guidelines**
|
||||
|
||||
Welcome to **bitvid**, a decentralized video-sharing platform built on Nostr. These Community Guidelines outline the types of content allowed and prohibited on the platform. As bitvid is still in early access, enforcement will occur at the client level, meaning violations will result in content being blocked from display rather than removed from relays. These policies will evolve as we implement robust user blocking and reporting features.
|
||||
|
||||
## **1. Content Principles**
|
||||
|
||||
bitvid aims to support **free expression** while maintaining a platform where users feel safe and respected. To achieve this balance, the following principles guide our moderation approach:
|
||||
|
||||
- **Decentralization:** We do not host videos directly but enable peer-to-peer sharing through WebTorrent and Nostr.
|
||||
- **User Moderation:** Users have control over their feeds through subscriptions, blocking, and reporting.
|
||||
- **Transparency:** Enforcement actions at the client level are visible, and policy updates will be communicated openly.
|
||||
|
||||
## **2. Allowed Content**
|
||||
|
||||
bitvid encourages a wide range of content, including but not limited to:
|
||||
|
||||
- **Educational and informative videos** (tech tutorials, history, science, etc.).
|
||||
- **Entertainment** (music, gaming, comedy, reviews, etc.).
|
||||
- **News and journalism** (independent reporting and discussions).
|
||||
- **Creative works** (art, animations, short films, open-source projects, etc.).
|
||||
- **Discussions and opinions** (provided they adhere to respectful discourse).
|
||||
|
||||
## **3. Prohibited Content**
|
||||
|
||||
To maintain a functional and ethical platform, the following types of content are blocked at the client level:
|
||||
|
||||
### **3.1. Illegal Content**
|
||||
|
||||
- Content that violates applicable laws, including but not limited to:
|
||||
- **CSAM (Child Sexual Abuse Material)** (immediate report and block enforcement).
|
||||
- **Human trafficking, exploitation, or abuse.**
|
||||
- **Direct threats or incitements to violence.**
|
||||
- **Explicit doxxing or leaking of private information without consent.**
|
||||
- **Fraudulent or scam content (e.g., Ponzi schemes, fake giveaways, impersonation).**
|
||||
|
||||
### **3.2. Spam and Low-Quality Content**
|
||||
|
||||
- Mass-uploaded, repetitive, or bot-generated content intended to flood the platform.
|
||||
- Clickbait thumbnails/titles that mislead viewers.
|
||||
- Excessive self-promotion with no meaningful engagement.
|
||||
|
||||
### **3.3. NSFW and Sensitive Content**
|
||||
|
||||
- **Pornographic material** (non-artistic sexually explicit content is not allowed at this stage).
|
||||
- **Extreme gore or graphic violence** (unless in an educational or journalistic context).
|
||||
- **Hate speech, racism, or targeted harassment.**
|
||||
- **Calls for harm against individuals or groups based on race, religion, nationality, gender, or sexual orientation.**
|
||||
- **Encouragement of self-harm, suicide, or dangerous activities.**
|
||||
|
||||
### **3.4. AI-Generated and Deepfake Content**
|
||||
|
||||
- **Deepfake impersonations** of real people used for deceptive purposes.
|
||||
- AI-generated content must be **clearly labeled** to avoid misinformation.
|
||||
|
||||
## **4. Enforcement Actions**
|
||||
|
||||
Until robust reporting and user moderation features are fully implemented, bitvid will enforce these guidelines at the **client level**:
|
||||
|
||||
- **Content Filtering:** Prohibited content will not be displayed within the client, though it may still exist on relays.
|
||||
- **Account Restrictions:** Users violating guidelines may be **blocked from the whitelisted early access list**.
|
||||
- **Reporting Mechanism:** Users are encouraged to report violations via NIP-56 reports, which will be reviewed manually.
|
||||
|
||||
## **5. User Moderation Features (In Development)**
|
||||
|
||||
We are working on features that will give users more control over their experience, including:
|
||||
|
||||
- **Personal blocklists** (prevent content from specific creators from appearing in feeds).
|
||||
- **Community-curated moderation lists** (opt-in to trusted content curation groups).
|
||||
- **Tag-based filtering** (users can toggle visibility of certain content categories).
|
||||
|
||||
## **6. Appeals and Feedback**
|
||||
|
||||
As we refine the moderation system, we welcome feedback from early users. If you believe your content was unfairly blocked, you can appeal by reaching out through bitvid’s Nostr channels.
|
||||
|
||||
## **7. Final Notes**
|
||||
|
||||
bitvid is committed to **free speech and open platforms** while acknowledging the need for responsible content moderation. These guidelines will continue to evolve as we develop better tools for user moderation and decentralized governance.
|
||||
|
||||
Thank you for being part of the early bitvid community!
|
49
content/getting-started.md
Normal file
49
content/getting-started.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Getting Started with bitvid
|
||||
|
||||
Ready to jump in? Here's everything you need to know to start watching and sharing videos on bitvid.
|
||||
|
||||
## Watching Videos
|
||||
|
||||
1. Just visit [bitvid.network](https://bitvid.network) or one of our alternate sites like [bitvid.btc.us](https://bitvid.btc.us) and [bitvid.eth.limo](https://bitvid.eth.limo). We also have other instances via [IPNS](ipns.html) gateways you can try.
|
||||
2. Browse the videos on the homepage
|
||||
3. Click any video to start watching
|
||||
That's it! No account needed to watch.
|
||||
|
||||
## Sharing Your Videos
|
||||
|
||||
### Step 1: Set Up Your Account
|
||||
|
||||
> ⚠️ **Note:** We are currently invite-only. Reach out to [**bitvid on Nostr**](https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe) to request to be added to the whitelist.
|
||||
|
||||
1. Install a [Nostr extension](https://nostrapps.com/#signers#all) (like Alby or Nos2x) in your browser
|
||||
2. The extension creates your secure login key automatically
|
||||
3. Click "Login" on bitvid to connect
|
||||
|
||||
### Step 2: Prepare Your Video
|
||||
|
||||
1. Download WebTorrent Desktop app from [webtorrent.io/desktop](https://webtorrent.io/desktop/)
|
||||
2. Open your video file in WebTorrent Desktop
|
||||
3. It will create a special "magnet link" for your video
|
||||
4. Keep WebTorrent Desktop running to share your video
|
||||
|
||||
### Step 3: Share on bitvid
|
||||
|
||||
1. Click "Share a Video" on bitvid
|
||||
2. Paste your video's magnet link
|
||||
3. Add a title, description, and thumbnail
|
||||
4. Click "Post" to share!
|
||||
|
||||
## Tips for Success
|
||||
|
||||
- Keep WebTorrent Desktop running while sharing videos
|
||||
- Add eye-catching thumbnails to attract viewers
|
||||
- Write clear descriptions to help people find your content
|
||||
- Use the "Private" option if you only want to share with specific people
|
||||
|
||||
## Need Help?
|
||||
|
||||
- Visit our [GitHub](https://github.com/PR0M3TH3AN/bitvid) page for technical support
|
||||
- Join our [community](https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe) to connect with other users
|
||||
- Report bugs to help us improve
|
||||
|
||||
Welcome to bitvid – let's start sharing!
|
19
content/ipns.md
Normal file
19
content/ipns.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# IPNS Gateways
|
||||
|
||||
Below is a list of available IPNS gateways you can use with the hash `k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1`:
|
||||
|
||||
1. **[FLK IPFS Gateway](https://k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1.ipns.flk-ipfs.xyz/)**
|
||||
A public gateway that resolves the provided IPNS hash.
|
||||
|
||||
2. **[Aragon IPFS Gateway](https://ipfs.eth.aragon.network/ipfs/bafybeih2ebj55ki3wvasj5i3rhwgjn6e72f6vxsrlrjfqvzezot2eoeqz4/)**
|
||||
A gateway hosted by Aragon for IPFS content resolution.
|
||||
|
||||
3. **[Dweb.link Gateway](https://dweb.link/ipns/k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1)**
|
||||
A subdomain resolution gateway provided by Protocol Labs.
|
||||
|
||||
4. **[IPFS.io Gateway](https://ipfs.io/ipns/k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1)**
|
||||
A public gateway operated by Protocol Labs.
|
||||
|
||||
---
|
||||
|
||||
**Note:** The availability and performance of these gateways may vary.
|
47
content/roadmap.md
Normal file
47
content/roadmap.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Roadmap and Bug List
|
||||
|
||||
## UI Enhancements
|
||||
|
||||
- Add a copy Magnet button labeled "Seed".
|
||||
- add community guidelines page
|
||||
- add links to pop-up modal
|
||||
- Convert "Logged in as" from public key to profile image and username (use npub as fallback).
|
||||
- Add a sidebar for improved UI flexibility.
|
||||
- Customize home screen content via algorithms for better feeds. (trending, new, for you etc.)
|
||||
- Improve UI/UX and CSS.
|
||||
- Add custom color themes and toggle between light and dark mode.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fix public key wrapping issue on smaller screens.
|
||||
- Fix video editing failures.
|
||||
- Resolve issue where reopening the same video doesn't work after closing the video player.
|
||||
- Address "Video playback error: MEDIA_ELEMENT_ERROR: Empty src attribute" error.
|
||||
- Fix "Dev Mode" publishing "Live Mode" notes—add a flag for dev mode posts.
|
||||
|
||||
## Feature Additions
|
||||
|
||||
- Allow users to set custom relay settings, stored in local cache.
|
||||
- Add a "Publish" step in the video editing process.
|
||||
- Add comments to the video modal.
|
||||
- Implement an "Adult Content" flag for note submissions.
|
||||
- Enable custom hashtags in the submission spec and form.
|
||||
- Allow multiple video resolutions with a selector in the video player.
|
||||
- Add a block/unblock list with import/export functionality.
|
||||
- Assign unique URLs to each video.
|
||||
- Add a profile modal for each user/profile.
|
||||
- Introduce a subscription mechanism with notifications.
|
||||
- Add zaps to videos, profiles, and comments.
|
||||
- Implement visibility filtering for videos:
|
||||
- Show only videos whose magnet links have at least **one active peer online**.
|
||||
- Integrate the filtering mechanism into the video list rendering process.
|
||||
- Update the video list dynamically based on real-time peer availability.
|
||||
- Add multi-language support for content and filtration.
|
||||
- Create a settings menu for local account preferences, including relay, adult content, theme, and language.
|
||||
|
||||
## Long-Term Goals
|
||||
|
||||
- Add a system for creating high-quality, algorithm-driven content feeds.
|
||||
- Thoroughly bug test the video editing and submission process.
|
||||
|
||||
If you find a new bug thats not listed here. DM me on [Nostr](https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe).
|
@@ -0,0 +1,261 @@
|
||||
# **bitvid: Enhanced Migration of Note Spec Logic**
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Currently, logic related to the **video note specification** and how data is structured (version, `isPrivate`, encryption placeholders, and so on) is scattered across three files:
|
||||
|
||||
1. **app.js**: Contains UI handling, form submissions, and some note structure (like `version`, `title`, `magnet`).
|
||||
2. **nostr.js**: Builds and edits Nostr events (`publishVideo`, `editVideo`, `deleteVideo`). It also holds methods for “fake encryption” and “fake decryption,” among other utilities.
|
||||
3. **webtorrent.js**: Mostly focuses on torrent streaming but does not handle note logic directly. It rarely touches the note data, so it may not need major restructuring.
|
||||
|
||||
To isolate note-spec-related operations, you can create a new file (for example, `bitvidNoteSpec.js`). This file will have all the code that deals with creating or parsing your event content fields (version, magnet link encryption, etc.). Then `app.js` and `nostr.js` can import those functions.
|
||||
|
||||
---
|
||||
|
||||
## Goals
|
||||
|
||||
- **Centralize the note specification**: Keep details like `version`, `deleted`, `isPrivate`, encryption, and decryption in one place.
|
||||
- **Simplify `app.js`**: Move form building/parsing to new spec-related functions. That way, `app.js` only handles UI and user actions.
|
||||
- **Streamline `nostr.js`**: Shift event creation logic into a function from the new note spec file. Nostr code then just calls that function, signs it, and publishes it.
|
||||
|
||||
---
|
||||
|
||||
## Proposed File: `bitvidNoteSpec.js`
|
||||
|
||||
This new file could export:
|
||||
|
||||
1. **Constants / Defaults** (for example, default `kind=30078`, default `version=2`, etc.).
|
||||
2. **Helper Functions**:
|
||||
- `buildNewNote(data, pubkey)`: Takes basic form data and returns a fully structured Nostr note (an object) ready to be signed.
|
||||
- `buildEditNote(originalEvent, updatedData)`: Merges old note content with new fields.
|
||||
- `softDeleteNote(originalEvent)`: Constructs a note with `deleted = true`.
|
||||
- `encryptMagnet(magnet)`, `decryptMagnet(magnet)`: Real or placeholder encryption functions.
|
||||
- `validateNoteContent(content)`: Ensures essential fields (title, magnet, mode, etc.) are present and valid.
|
||||
|
||||
Because you’re not implementing Version 3 yet, keep your existing version logic. If you do plan to adopt Version 3 later, the new file is where you’d add or change fields without scattering edits across multiple files.
|
||||
|
||||
---
|
||||
|
||||
### 1. Extracting Logic from `app.js`
|
||||
|
||||
In `app.js`, you have code in the `handleSubmit` method that constructs a `formData` object with fields like `version`, `title`, `magnet`, and so on. You can:
|
||||
|
||||
- Remove direct references to `version` or encryption from `handleSubmit`.
|
||||
- Instead, pass the raw form input to a function in `bitvidNoteSpec.js` named, for instance, `prepareNewNote(formInput, pubkey)`.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
// bitvidNoteSpec.js (simplified example)
|
||||
export function prepareNewNote(formInput, pubkey) {
|
||||
// Combine user inputs with defaults
|
||||
const isPrivate = formInput.isPrivate === true;
|
||||
const finalMagnet = isPrivate
|
||||
? encryptMagnet(formInput.magnet)
|
||||
: formInput.magnet;
|
||||
|
||||
return {
|
||||
kind: 30078,
|
||||
pubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
["t", "video"],
|
||||
["d", generateUniqueDTag()]
|
||||
],
|
||||
content: JSON.stringify({
|
||||
version: formInput.version ?? 2,
|
||||
deleted: false,
|
||||
isPrivate,
|
||||
title: formInput.title,
|
||||
magnet: finalMagnet,
|
||||
thumbnail: formInput.thumbnail,
|
||||
description: formInput.description,
|
||||
mode: formInput.mode
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// app.js (handleSubmit excerpt)
|
||||
import { prepareNewNote } from "./bitvidNoteSpec.js";
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.pubkey) {
|
||||
this.showError("Please login to post a video.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formInput = {
|
||||
version: 2,
|
||||
title: document.getElementById("title")?.value.trim() || "",
|
||||
magnet: document.getElementById("magnet")?.value.trim() || "",
|
||||
thumbnail: document.getElementById("thumbnail")?.value.trim() || "",
|
||||
description: document.getElementById("description")?.value.trim() || "",
|
||||
mode: isDevMode ? "dev" : "live",
|
||||
isPrivate: this.isPrivateCheckbox.checked
|
||||
};
|
||||
|
||||
try {
|
||||
const eventToPublish = prepareNewNote(formInput, this.pubkey);
|
||||
await nostrClient.publishNote(eventToPublish);
|
||||
this.submitForm.reset();
|
||||
...
|
||||
} catch (err) {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now `handleSubmit` is only handling the UI, while actual note-building moves to `bitvidNoteSpec.js`.
|
||||
|
||||
---
|
||||
|
||||
### 2. Extracting Logic from `nostr.js`
|
||||
|
||||
In `nostr.js`, you have methods like `publishVideo`, `editVideo`, and `deleteVideo`. They build the note content, sign it, and publish it. You can simplify them:
|
||||
|
||||
- **Rename** them to something more generic (e.g., `publishNote`, `updateNote`, `deleteNote`) if that aligns better with Nostr usage.
|
||||
- **Import** helper functions from `bitvidNoteSpec.js` to build or edit the actual content. That way, `nostr.js` doesn’t need to know about `version`, `deleted`, or encryption details.
|
||||
|
||||
For example, you might do this:
|
||||
|
||||
```js
|
||||
// nostr.js
|
||||
import { buildEditNote, buildDeleteNote } from "./bitvidNoteSpec.js";
|
||||
|
||||
class NostrClient {
|
||||
...
|
||||
async editVideo(originalEvent, updatedData, pubkey) {
|
||||
// 1) Build the note object using shared function
|
||||
const eventToPublish = buildEditNote(originalEvent, updatedData);
|
||||
|
||||
// 2) Sign and publish
|
||||
const signedEvent = await window.nostr.signEvent(eventToPublish);
|
||||
...
|
||||
}
|
||||
|
||||
async deleteVideo(originalEvent, pubkey) {
|
||||
const eventToPublish = buildDeleteNote(originalEvent);
|
||||
const signedEvent = await window.nostr.signEvent(eventToPublish);
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By delegating the actual note-building to `buildEditNote` and `buildDeleteNote`, you keep `nostr.js` focused on signing and relaying events.
|
||||
|
||||
---
|
||||
|
||||
### 3. Minimal Impact on `webtorrent.js`
|
||||
|
||||
`webtorrent.js` deals mostly with streaming and service workers. It does not appear to handle note building or encryption. You likely do not need to change anything there for this refactor, unless you want to move `fakeDecrypt` references. If you do, just import the spec’s encryption/decryption functions where needed.
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Migration Plan
|
||||
|
||||
1. **Create `bitvidNoteSpec.js`:**
|
||||
- Place all code that deals with constructing, editing, or deleting your media note events.
|
||||
- Include minimal encryption/decryption functions if they are purely for magnet links.
|
||||
|
||||
2. **Update `app.js`:**
|
||||
- Remove direct references to building the final note object in `handleSubmit`.
|
||||
- Instead, gather user inputs, call a helper function from `bitvidNoteSpec.js` to produce the final note object, then pass that object to `nostrClient.publishNote` (or a similar method).
|
||||
|
||||
3. **Update `nostr.js`:**
|
||||
- Rename or refactor `publishVideo`, `editVideo`, and `deleteVideo` to call your new helper methods from `bitvidNoteSpec.js`.
|
||||
- Keep the Nostr signing and publishing logic inside `nostr.js`.
|
||||
|
||||
4. **Verify Data Flow:**
|
||||
- Confirm that after form submission, the final event object is built in `bitvidNoteSpec.js`, returned to `app.js`, and forwarded to `nostrClient`.
|
||||
- Ensure you can still subscribe to events and parse them without issues.
|
||||
|
||||
5. **Remove Redundant Code:**
|
||||
- Delete any leftover duplication in `app.js` or `nostr.js` relating to magnet encryption or note structuring.
|
||||
|
||||
6. **Test Thoroughly:**
|
||||
- Create, edit, and delete events to ensure everything behaves the same.
|
||||
- Confirm that private videos are still encrypted as expected.
|
||||
|
||||
---
|
||||
|
||||
## Example File Outline for `bitvidNoteSpec.js`
|
||||
|
||||
Below is a small outline showing how you might organize the new file. The actual details will depend on your existing code and future needs:
|
||||
|
||||
```js
|
||||
// bitvidNoteSpec.js
|
||||
|
||||
// A placeholder or real encryption method
|
||||
export function encryptMagnet(magnet) {
|
||||
return magnet.split("").reverse().join("");
|
||||
}
|
||||
|
||||
export function decryptMagnet(magnet) {
|
||||
return magnet.split("").reverse().join("");
|
||||
}
|
||||
|
||||
function generateUniqueDTag() {
|
||||
return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
||||
}
|
||||
|
||||
// Build a brand-new note
|
||||
export function prepareNewNote(formInput, pubkey) {
|
||||
const isPrivate = formInput.isPrivate === true;
|
||||
const finalMagnet = isPrivate
|
||||
? encryptMagnet(formInput.magnet)
|
||||
: formInput.magnet;
|
||||
|
||||
return {
|
||||
kind: 30078,
|
||||
pubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
["t", "video"],
|
||||
["d", generateUniqueDTag()]
|
||||
],
|
||||
content: JSON.stringify({
|
||||
version: formInput.version ?? 2,
|
||||
deleted: false,
|
||||
isPrivate,
|
||||
title: formInput.title,
|
||||
magnet: finalMagnet,
|
||||
thumbnail: formInput.thumbnail,
|
||||
description: formInput.description,
|
||||
mode: formInput.mode ?? "live"
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Build an edited note using original event data
|
||||
export function buildEditNote(originalEvent, updatedData) {
|
||||
// parse old content
|
||||
// combine with new fields
|
||||
// handle old vs new encryption
|
||||
// return the final event object
|
||||
}
|
||||
|
||||
// Build a deleted note
|
||||
export function buildDeleteNote(originalEvent) {
|
||||
// parse old content
|
||||
// set deleted=true, remove magnet, etc.
|
||||
// return the final event object
|
||||
}
|
||||
|
||||
// Validate content structure
|
||||
export function isValidNoteContent(content) {
|
||||
// check for required fields
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
By keeping these details in a single file, you won’t have to search through `app.js` or `nostr.js` whenever you need to tweak the note structure.
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
Shifting all note spec logic into a dedicated file will make your codebase cleaner and set you up for easier upgrades down the road. You can proceed with the steps above, ensuring you keep each file (UI, Nostr communication, torrent streaming) focused on its primary role. Once done, you’ll be able to implement higher-level changes (like Version 3 or additional content fields) in one place without sifting through unrelated code.
|
@@ -0,0 +1,296 @@
|
||||
# **bitvid: Enhanced Nostr Video/Audio Note Specification: Version 3**
|
||||
|
||||
This specification updates the previous version (Version 2) and introduces:
|
||||
|
||||
1. **videoRootId**: A dedicated field in the JSON content to group related edits and deletes under one “root” post.
|
||||
2. **Support for Audio/Podcast**: Additional fields for music/podcast metadata.
|
||||
3. **Adult Content, Multi-Category, and Extended Metadata**: Optional fields to handle specialized use cases.
|
||||
4. **Backward Compatibility**: Clients implementing Version 2 can still display the basic fields.
|
||||
|
||||
---
|
||||
|
||||
## **1. Overview**
|
||||
|
||||
Nostr posts of **kind = 30078** are used for sharing media (videos, audio, podcasts, etc.). The JSON payload goes into the `content` field, while tags array can store quick references like `["t", "video"]` or `["d", "<unique-id>"]`.
|
||||
|
||||
### **Key Concepts**
|
||||
|
||||
- **videoRootId**: A unique identifier stored in the `content` JSON.
|
||||
- Ensures multiple edits or a delete event are recognized as referring to the same underlying post.
|
||||
- If missing (legacy posts), clients may fall back to the d-tag or event ID to group events, but using `videoRootId` is strongly recommended for consistent overshadow logic.
|
||||
- **Backward Compatibility**: All newly introduced fields are optional. Version 2 clients see fields like `title`, `magnet`, `description`, but ignore new fields such as `adult`, `audioQuality`, etc.
|
||||
|
||||
---
|
||||
|
||||
## **2. Event Structure**
|
||||
|
||||
A typical event:
|
||||
|
||||
| **Field** | **Type** | **Description** |
|
||||
| ------------ | ------------- | ------------------------------------------------------------------------------------------ |
|
||||
| `kind` | Integer | Fixed at `30078` for these media notes. |
|
||||
| `pubkey` | String | Creator’s pubkey in hex. |
|
||||
| `created_at` | Integer | Unix timestamp (seconds). |
|
||||
| `tags` | Array | Includes metadata such as `["t", "video"]` or `["t", "music"]` and `["d", "<unique-id>"]`. |
|
||||
| `content` | String (JSON) | JSON specifying metadata (see table below). |
|
||||
|
||||
---
|
||||
|
||||
## **3. Version 3 Content JSON**
|
||||
|
||||
| **Field** | **Type** | **Description** |
|
||||
| --------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `videoRootId` | String (optional) | A stable root ID used to link multiple versions (edits) and a delete event together. **Recommended** for overshadow logic. |
|
||||
| `version` | Integer | Now set to `3`. |
|
||||
| `deleted` | Boolean | `true` marks the post as “soft deleted.” |
|
||||
| `isPrivate` | Boolean | Indicates if `magnet` (and possibly other fields) are encrypted. |
|
||||
| `title` | String | Display title for the media. |
|
||||
| `magnet` | String | Magnet link for primary media (encrypted if `isPrivate = true`). |
|
||||
| `extraMagnets` | Array (optional) | Additional magnet links (e.g., multiple resolutions). |
|
||||
| `thumbnail` | String (optional) | URL or magnet link to a thumbnail image (encrypted if `isPrivate = true` and `encryptedMeta = true`). |
|
||||
| `description` | String (optional) | A textual description (encrypted if `isPrivate = true` and `encryptedMeta = true`). |
|
||||
| `mode` | String | Typically `live` or `dev`. |
|
||||
| `adult` | Boolean (optional) | `true` if content is adult-only. Default: `false` or omitted. |
|
||||
| `categories` | Array (optional) | Array of categories, e.g. `["comedy", "music"]`. |
|
||||
| `language` | String (optional) | Language code (e.g. `"en"`, `"es"`). |
|
||||
| `payment` | String (optional) | Monetization field (e.g. a Lightning address). |
|
||||
| `i18n` | Object (optional) | Internationalization map (e.g. `{"title_en": "...", "description_es": "..."}`). |
|
||||
| `encryptedMeta` | Boolean (optional) | Indicates if fields like `description` or `thumbnail` are encrypted. |
|
||||
| **Audio/Podcast-Specific Fields** | | |
|
||||
| `contentType` | String (optional) | E.g., `"video"`, `"music"`, `"podcast"`, `"audiobook"`. |
|
||||
| `albumName` | String (optional) | Name of the album (for music). |
|
||||
| `trackNumber` | Integer (optional) | Track number in an album. |
|
||||
| `trackTitle` | String (optional) | Track title if different from `title`. |
|
||||
| `podcastSeries` | String (optional) | Name of the podcast series. |
|
||||
| `seasonNumber` | Integer (optional) | Season number for a podcast. |
|
||||
| `episodeNumber` | Integer (optional) | Episode number for a podcast series. |
|
||||
| `duration` | Integer (optional) | Duration in seconds (useful for audio or video players). |
|
||||
| `artistName` | String (optional) | Artist or presenter name. |
|
||||
| `contributors` | Array (optional) | List of additional contributors, e.g. `[{"name": "X", "role": "Producer"}]`. |
|
||||
| `audioQuality` | Array (optional) | Array of objects indicating different audio bitrates/formats, each with a magnet link. |
|
||||
| `playlist` | Array (optional) | For multi-track or multi-episode sets, e.g. `[ "magnet1", "magnet2" ]`. |
|
||||
|
||||
> **Note**: All fields except `version`, `deleted`, `title`, `magnet`, and `videoRootId` are optional. If an older post lacks `videoRootId`, fallback grouping may rely on the `d` tag or event ID.
|
||||
|
||||
---
|
||||
|
||||
## **4. Using `videoRootId`**
|
||||
|
||||
- **Purpose**: Ensures all edits and a final delete event share the same “root.”
|
||||
- **Edit**: Keep the same `videoRootId` in the new content so clients know it’s an update of the same item.
|
||||
- **Delete**: Reuse the same `videoRootId` (and typically the same `d` tag). Mark `deleted = true` to overshadow the old event.
|
||||
|
||||
**Fallback**: Legacy or older notes might not have a `videoRootId`. Clients can group them by `["d", "<id>"]` or treat the old event’s own ID as its “root.” However, for new posts, **always** set `videoRootId`.
|
||||
|
||||
---
|
||||
|
||||
## **5. Example Post: Version 3 (Video)**
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "npub1...",
|
||||
"created_at": 1700000000,
|
||||
"tags": [
|
||||
["t", "video"],
|
||||
["d", "my-unique-handle"]
|
||||
],
|
||||
"content": "{
|
||||
\"videoRootId\": \"root-1678551042-abc123\",
|
||||
\"version\": 3,
|
||||
\"deleted\": false,
|
||||
\"isPrivate\": false,
|
||||
\"adult\": true,
|
||||
\"categories\": [\"comedy\", \"pranks\"],
|
||||
\"title\": \"Funny Prank Video\",
|
||||
\"magnet\": \"magnet:?xt=urn:btih:examplehash\",
|
||||
\"extraMagnets\": [
|
||||
\"magnet:?xt=urn:btih:examplehashHD\",
|
||||
\"magnet:?xt=urn:btih:examplehashMobile\"
|
||||
],
|
||||
\"thumbnail\": \"https://example.com/thumb.jpg\",
|
||||
\"description\": \"An adult-oriented prank video.\",
|
||||
\"mode\": \"live\",
|
||||
\"language\": \"en\",
|
||||
\"payment\": \"alice@getalby.com\",
|
||||
\"i18n\": {
|
||||
\"title_es\": \"Video de broma chistosa\",
|
||||
\"description_es\": \"Un video de bromas para adultos.\"
|
||||
}
|
||||
}"
|
||||
}
|
||||
```
|
||||
|
||||
- **videoRootId**: `root-1678551042-abc123` ensures future edits or deletes reference the same item.
|
||||
- If `deleted = true`, the client sees it as a soft delete overshadowing the original.
|
||||
|
||||
---
|
||||
|
||||
## **6. Example Post: Version 3 (Audio)**
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "npub1...",
|
||||
"created_at": 1700000001,
|
||||
"tags": [
|
||||
["t", "music"],
|
||||
["d", "my-song-handle"]
|
||||
],
|
||||
"content": "{
|
||||
\"videoRootId\": \"root-1678551042-xyz999\",
|
||||
\"version\": 3,
|
||||
\"deleted\": false,
|
||||
\"isPrivate\": false,
|
||||
\"contentType\": \"music\",
|
||||
\"title\": \"Amazing Track\",
|
||||
\"trackTitle\": \"Amazing Track\",
|
||||
\"albumName\": \"Great Album\",
|
||||
\"trackNumber\": 1,
|
||||
\"artistName\": \"Awesome Artist\",
|
||||
\"duration\": 300,
|
||||
\"magnet\": \"magnet:?xt=urn:btih:examplehash\",
|
||||
\"audioQuality\": [
|
||||
{
|
||||
\"quality\": \"lossless\",
|
||||
\"magnet\": \"magnet:?xt=urn:btih:losslessHash\"
|
||||
},
|
||||
{
|
||||
\"quality\": \"mp3\",
|
||||
\"magnet\": \"magnet:?xt=urn:btih:mp3Hash\"
|
||||
}
|
||||
],
|
||||
\"description\": \"A track from the Great Album.\",
|
||||
\"categories\": [\"music\", \"pop\"],
|
||||
\"contributors\": [
|
||||
{ \"name\": \"John Doe\", \"role\": \"Producer\" },
|
||||
{ \"name\": \"Jane Smith\", \"role\": \"Writer\" }
|
||||
]
|
||||
}"
|
||||
}
|
||||
```
|
||||
|
||||
- **videoRootId**: `root-1678551042-xyz999` used to link edits/deletes.
|
||||
|
||||
---
|
||||
|
||||
## **7. Tagging**
|
||||
|
||||
- `["t", "video"]` or `["t", "music"]` for quick type references.
|
||||
- `["d", "<unique-handle>"]` for a stable “address” pointer.
|
||||
- You can store adult flags or categories in tags, e.g. `["adult", "true"]` or `["category", "comedy"]`.
|
||||
- However, storing them inside `content` (e.g. `adult=true` or `categories=["comedy"]`) is generally recommended so older clients can ignore them gracefully.
|
||||
|
||||
---
|
||||
|
||||
## **8. Handling Edits and Deletes**
|
||||
|
||||
### **8.1 Edits**
|
||||
|
||||
1. **videoRootId**: Keep the same `videoRootId` to overshadow the previous version.
|
||||
2. **version**: Bump from `2` → `3` or `3` → a higher sub-version if you wish to track changes.
|
||||
3. **tags**: Typically reuse `["d", "<unique-handle>"]` or create a new d-tag. The important part is to keep `videoRootId` consistent.
|
||||
|
||||
### **8.2 Deletion**
|
||||
|
||||
1. **deleted = true**: Mark the item as deleted in the `content` JSON.
|
||||
2. **Remove or encrypt** sensitive fields (`magnet`, `description`, etc.).
|
||||
3. **videoRootId**: Must remain the same as the original so clients remove/overshadow the old item.
|
||||
4. **subscribeVideos** Logic:
|
||||
```js
|
||||
if (video.deleted) {
|
||||
// remove from activeMap or overshadow the old entry
|
||||
}
|
||||
```
|
||||
5. **fetchVideos** Logic:
|
||||
```js
|
||||
for (const [id, video] of allEvents.entries()) {
|
||||
if (video.deleted) continue; // skip deleted
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **9. Filtering Logic**
|
||||
|
||||
1. **Adult Content**: If `adult = true`, clients typically hide the post unless the user enables adult content in settings.
|
||||
2. **Categories**: Provide optional grouping or searching by `categories`.
|
||||
3. **Language**: If specified, clients can filter by language.
|
||||
4. **Encryption**: If `isPrivate = true`, some fields (e.g., `magnet`) may need client-side decryption.
|
||||
|
||||
---
|
||||
|
||||
## **10. Submission Flow**
|
||||
|
||||
1. **Client Form**: Title, magnet link, optional category checkboxes, adult toggle, etc.
|
||||
2. **videoRootId**: For new posts, generate a new root ID. For edits, reuse the old post’s root ID.
|
||||
3. **Publish**:
|
||||
- `kind = 30078`
|
||||
- `content` → JSON with `videoRootId`, `version=3`, `title`, `magnet`, etc.
|
||||
- `tags` → e.g., `["t","video"]`, `["d","my-handle"]`.
|
||||
4. **Visibility**: If `adult=true`, hide from minors or default searches. If `deleted=true`, overshadow old entry.
|
||||
|
||||
---
|
||||
|
||||
## **11. Example “Delete” Flow**
|
||||
|
||||
1. **Original Post** (not deleted):
|
||||
```json
|
||||
{
|
||||
"content": "{
|
||||
\"videoRootId\": \"root-1234\",
|
||||
\"version\": 3,
|
||||
\"deleted\": false,
|
||||
\"title\": \"My Video\",
|
||||
\"magnet\": \"magnet:?xt=hash\"
|
||||
// ...
|
||||
}"
|
||||
}
|
||||
```
|
||||
2. **Delete Post** (new event):
|
||||
```jsonc
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "...",
|
||||
"tags": [
|
||||
["t","video"],
|
||||
["d","my-handle"] // same d-tag
|
||||
],
|
||||
"content": "{
|
||||
\"videoRootId\": \"root-1234\", // same root as original
|
||||
\"version\": 3,
|
||||
\"deleted\": true,
|
||||
\"title\": \"My Video\",
|
||||
\"magnet\": \"\", // blank or removed
|
||||
\"description\": \"Video was deleted by creator.\"
|
||||
}"
|
||||
}
|
||||
```
|
||||
3. **subscribeVideos** sees `deleted=true` and overshadow logic removes the old item from active view.
|
||||
|
||||
---
|
||||
|
||||
## **12. Backward Compatibility**
|
||||
|
||||
- **Version 2** fields remain recognized: `title`, `magnet`, `mode`, etc.
|
||||
- **Older Clients**: Ignore fields like `videoRootId`, `categories`, `adult`, etc.
|
||||
- **When Upgrading**: If you add `videoRootId` or adult flags to an older post, older clients still display basic info but won’t filter on the new fields.
|
||||
|
||||
---
|
||||
|
||||
## **13. Summary**
|
||||
|
||||
**Version 3** is a superset of previous versions, adding:
|
||||
|
||||
1. **videoRootId** for overshadow logic and grouping multi-edit threads.
|
||||
2. **Adult content flag**, multi-category, i18n, and other optional fields.
|
||||
3. **Audio/podcast support**: fields like `contentType = "music"`, `podcastSeries`, `episodeNumber`, and `playlist`.
|
||||
|
||||
Clients that implement these features can sort, filter, or display richer media experiences while remaining compatible with older Nostr note readers.
|
||||
|
||||
---
|
||||
|
||||
**End of Document**
|
||||
|
||||
This expanded spec ensures that **videoRootId** is used consistently, clarifies how to handle adult content, and extends the data model for new media types. By following this Version 3 guidance, you’ll maintain backward compatibility while enabling advanced features for media sharing on Nostr.
|
@@ -0,0 +1,149 @@
|
||||
# **bitvid: Enhanced Profile/Channel Views Specification**
|
||||
|
||||
## **Overview**
|
||||
We aim to integrate a multi-view system within `index.html`, allowing smooth navigation between views like the home grid and user profiles. This will leverage JavaScript for dynamic DOM manipulation, maintaining a consistent layout (header, footer, future sidebar) across views.
|
||||
|
||||
---
|
||||
|
||||
## **Structure and Navigation**
|
||||
### **Navigation Logic**
|
||||
1. **Default View (Home Grid):**
|
||||
- A grid showcasing all videos.
|
||||
- Acts as the primary landing page.
|
||||
|
||||
2. **Profile View:**
|
||||
- A user's profile page containing:
|
||||
- Profile banner and information.
|
||||
- Action buttons (Subscribe, Share, Block).
|
||||
- Videos grid (only videos posted by the user).
|
||||
|
||||
3. **Templating System:**
|
||||
- Use dynamic DOM manipulation to switch views without reloading the page.
|
||||
- Structure each "view" as a reusable container rendered based on URL or state.
|
||||
|
||||
---
|
||||
|
||||
## **Dynamic Routing**
|
||||
### **Route Handling**
|
||||
- Use the `hashchange` or `pushState` method to detect and handle navigation.
|
||||
- Route format:
|
||||
- `/#home`: Default home grid view.
|
||||
- `/#profile/{npub}`: Profile view of a specific user, determined by `{npub}`.
|
||||
|
||||
### **Implementation Plan**
|
||||
- Use JavaScript to parse the `window.location.hash` or `window.location.pathname` and determine which view to render.
|
||||
- Example:
|
||||
```javascript
|
||||
const renderView = () => {
|
||||
const hash = window.location.hash;
|
||||
if (hash.startsWith("#profile")) {
|
||||
const npub = hash.split("/")[1];
|
||||
loadProfileView(npub);
|
||||
} else {
|
||||
loadHomeGrid();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("hashchange", renderView);
|
||||
window.addEventListener("load", renderView);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **View Templates**
|
||||
### **Template System**
|
||||
1. **Home Grid Template:**
|
||||
- Container: `#homeGrid`.
|
||||
- Dynamically populate the grid with all videos fetched from Nostr relays.
|
||||
|
||||
2. **Profile Template:**
|
||||
- Container: `#profileView`.
|
||||
- Display user details and their videos based on the `npub`.
|
||||
|
||||
3. **Shared Components:**
|
||||
- Header, footer, and optional sidebar remain static.
|
||||
- Use `display: none` and `block` to toggle view visibility.
|
||||
|
||||
### **HTML Structure**
|
||||
Add placeholders for different views in `index.html`:
|
||||
```html
|
||||
<div id="homeGrid" class="view hidden">
|
||||
<!-- Home grid content -->
|
||||
</div>
|
||||
|
||||
<div id="profileView" class="view hidden">
|
||||
<header class="profile-header">
|
||||
<img id="profileBanner" src="" alt="Banner" />
|
||||
<img id="profileImage" src="" alt="Profile" />
|
||||
<h1 id="profileName"></h1>
|
||||
<p id="profileBio"></p>
|
||||
<button id="subscribeBtn">Subscribe</button>
|
||||
<button id="shareProfileBtn">Share</button>
|
||||
<button id="blockProfileBtn">Block</button>
|
||||
</header>
|
||||
<div id="profileVideos" class="videos-grid">
|
||||
<!-- User's videos -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Functionality**
|
||||
### **Profile Fetching**
|
||||
- Use Nostr protocol (`kind 0`) to fetch profile details.
|
||||
- Display:
|
||||
- Profile picture, name, bio, and website link.
|
||||
- Action buttons for Subscribe, Share, and Block.
|
||||
|
||||
### **Videos Fetching**
|
||||
- Fetch videos (`kind 30078`) filtered by the user's `npub`.
|
||||
|
||||
### **Subscriptions**
|
||||
- Use `kind 30002` to manage the subscription list:
|
||||
- Subscribe: Add user to the list.
|
||||
- Unsubscribe: Remove user.
|
||||
|
||||
### **Implementation Example**
|
||||
```javascript
|
||||
const loadProfileView = async (npub) => {
|
||||
// Fetch profile details
|
||||
const profileEvent = await nostrClient.fetchProfile(npub);
|
||||
const { name, picture, about } = profileEvent.content;
|
||||
|
||||
// Update profile view
|
||||
document.getElementById("profileImage").src = picture;
|
||||
document.getElementById("profileName").textContent = name;
|
||||
document.getElementById("profileBio").textContent = about;
|
||||
|
||||
// Fetch and display user videos
|
||||
const userVideos = await nostrClient.fetchVideosByNpub(npub);
|
||||
renderVideos(userVideos, "profileVideos");
|
||||
|
||||
// Show profile view
|
||||
showView("profileView");
|
||||
};
|
||||
|
||||
const showView = (viewId) => {
|
||||
document.querySelectorAll(".view").forEach((view) => {
|
||||
view.classList.toggle("hidden", view.id !== viewId);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Unique Profile URLs**
|
||||
- Format: `https://bitvid.network/#profile/{npub}`.
|
||||
- Navigation to this URL will directly load the profile view.
|
||||
- Use `history.pushState` or `location.hash` to set the URL.
|
||||
|
||||
---
|
||||
|
||||
## **Next Steps**
|
||||
- **Integrate Dynamic Routing**: Update `app.js` with route handling for views.
|
||||
- **Refactor HTML**: Add placeholders for views in `index.html`.
|
||||
- **Build Profile Fetching Logic**: Use Nostr client to fetch and display user details dynamically.
|
||||
- **Enhance UX**: Smooth transitions between views with CSS animations.
|
||||
|
||||
This setup achieves a modular SPA-like architecture while keeping development lightweight and aligned with your project’s goals.
|
@@ -0,0 +1,287 @@
|
||||
# **bitvid: Enhanced Block, Subscription, and Reporting Specification**
|
||||
|
||||
This document describes how to implement **Block Lists**, **Subscription Lists**, and **Reporting** (NIP-56) for a Nostr-based video platform such as bitvid. It covers how users can manage their own moderation tools and how the platform can apply additional checks.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Subscription List Specification](#subscription-list-specification)
|
||||
1.1 [Purpose](#purpose)
|
||||
1.2 [Event Kind](#event-kind)
|
||||
1.3 [JSON Structure](#json-structure)
|
||||
1.4 [Example](#example)
|
||||
1.5 [Features](#features)
|
||||
|
||||
2. [Block List Specification](#block-list-specification)
|
||||
2.1 [Purpose](#purpose-1)
|
||||
2.2 [Event Kind](#event-kind-1)
|
||||
2.3 [JSON Structure](#json-structure-1)
|
||||
2.4 [Examples](#examples)
|
||||
2.5 [Features](#features-1)
|
||||
|
||||
3. [Reporting with NIP-56](#reporting-with-nip-56)
|
||||
3.1 [Overview](#overview)
|
||||
3.2 [Report Types](#report-types)
|
||||
3.3 [Example Events](#example-events)
|
||||
3.4 [Client and Relay Behavior](#client-and-relay-behavior)
|
||||
|
||||
4. [Implementation Details](#implementation-details)
|
||||
4.1 [Replaceable Events](#replaceable-events)
|
||||
4.2 [Encryption](#encryption)
|
||||
4.3 [Fetching User Lists](#fetching-user-lists)
|
||||
4.4 [Pushing Updates to Relays](#pushing-updates-to-relays)
|
||||
|
||||
5. [UI Integration](#ui-integration)
|
||||
5.1 [Subscription Management](#subscription-management)
|
||||
5.2 [Block Management](#block-management)
|
||||
5.3 [Report Management](#report-management)
|
||||
|
||||
6. [Future Considerations](#future-considerations)
|
||||
|
||||
---
|
||||
|
||||
## Subscription List Specification
|
||||
|
||||
### Purpose
|
||||
A **Subscription List** lets users follow video creators independently of their main “following” list on Nostr. It supports categorization and can be made private via encryption.
|
||||
|
||||
### Event Kind
|
||||
- **Kind**: `30002`
|
||||
- **Description**: “Video Subscription List” (inspired by NIP-51 but for custom lists)
|
||||
|
||||
### JSON Structure
|
||||
**Public Tags**
|
||||
- `["p", <pubkey>]`: Public keys of creators to follow
|
||||
- `["t", <category>]`: Optional categories (e.g., “comedy,” “music”)
|
||||
|
||||
**Private Tags**
|
||||
- Encrypted list of subscriptions using NIP-04
|
||||
|
||||
**Metadata**
|
||||
- Additional information like category names, custom labels, etc.
|
||||
|
||||
### Example
|
||||
```json
|
||||
{
|
||||
"kind": 30002,
|
||||
"tags": [
|
||||
["d", "favorite-creators"],
|
||||
["p", "npub1creator1pubkey"],
|
||||
["p", "npub1creator2pubkey"],
|
||||
["t", "comedy"],
|
||||
["t", "science"]
|
||||
],
|
||||
"content": "Encrypted list content for private subscriptions",
|
||||
"created_at": 1735689600,
|
||||
"pubkey": "your-public-key",
|
||||
"id": "event-id"
|
||||
}
|
||||
```
|
||||
|
||||
### Features
|
||||
1. **Categorization**
|
||||
Users can group subscribed creators by genres or topics.
|
||||
2. **Privacy Options**
|
||||
Private subscriptions can be hidden by encrypting tags.
|
||||
3. **Replaceable Event**
|
||||
Users can update their list by publishing a new event with the same `d` tag (e.g., `["d","favorite-creators"]`) and a later `created_at`.
|
||||
|
||||
---
|
||||
|
||||
## Block List Specification
|
||||
|
||||
### Purpose
|
||||
A **Block List** gives users the ability to mute or block specific creators or users. It supports both public reasons (tags) and private reasons (encrypted content).
|
||||
|
||||
### Event Kind
|
||||
- **Kind**: `10001`
|
||||
- **Description**: “Block or Mute List” (per NIP-51)
|
||||
|
||||
### JSON Structure
|
||||
**Public Tags**
|
||||
- `["p", <pubkey>]`: Public keys of blocked users
|
||||
- `["r", <reason>]`: Optional reasons (spam, harassment, etc.)
|
||||
|
||||
**Private Tags**
|
||||
- Encrypted details for blocking, using NIP-04
|
||||
|
||||
### Examples
|
||||
|
||||
#### Public Block List
|
||||
```json
|
||||
{
|
||||
"kind": 10001,
|
||||
"tags": [
|
||||
["p", "npub1blockeduser1pubkey", "reason", "spam"],
|
||||
["p", "npub1blockeduser2pubkey", "reason", "harassment"]
|
||||
],
|
||||
"content": "",
|
||||
"created_at": 1735689600,
|
||||
"pubkey": "your-public-key",
|
||||
"id": "event-id"
|
||||
}
|
||||
```
|
||||
|
||||
#### Private Block List
|
||||
```json
|
||||
{
|
||||
"kind": 10001,
|
||||
"tags": [
|
||||
["p", "npub1blockeduser1pubkey"],
|
||||
["p", "npub1blockeduser2pubkey"]
|
||||
],
|
||||
"content": "Encrypted reasons for blocking (e.g., personal dispute info)",
|
||||
"created_at": 1735689600,
|
||||
"pubkey": "your-public-key",
|
||||
"id": "event-id"
|
||||
}
|
||||
```
|
||||
|
||||
### Features
|
||||
1. **Integration**
|
||||
- Offer a “Block/Unblock” button in the UI.
|
||||
- Provide a page to manage blocks.
|
||||
2. **Filtering**
|
||||
- Automatically exclude blocked users from feed results.
|
||||
3. **Categorization**
|
||||
- Tag reasons for blocking, such as spam or harassment.
|
||||
4. **Privacy Options**
|
||||
- Keep certain block reasons encrypted if needed.
|
||||
|
||||
---
|
||||
|
||||
## Reporting with NIP-56
|
||||
|
||||
### Overview
|
||||
NIP-56 introduces a **kind `1984`** event that flags content or profiles as objectionable. It’s a flexible way to let users or relays see reports and decide on any actions.
|
||||
|
||||
### Report Types
|
||||
The `p` tag references a pubkey, and the `e` tag references a note. The third element of the tag can be:
|
||||
- `nudity`
|
||||
- `malware`
|
||||
- `profanity`
|
||||
- `illegal`
|
||||
- `spam`
|
||||
- `impersonation`
|
||||
- `other`
|
||||
|
||||
### Example Events
|
||||
|
||||
```jsonc
|
||||
// Reporting a user for nudity
|
||||
{
|
||||
"kind": 1984,
|
||||
"tags": [
|
||||
["p", "<pubkey>", "nudity"]
|
||||
],
|
||||
"content": "Optional comment or additional info.",
|
||||
"created_at": 1735689600,
|
||||
"pubkey": "your-public-key",
|
||||
"id": "report-event-id"
|
||||
}
|
||||
|
||||
// Reporting a note as illegal
|
||||
{
|
||||
"kind": 1984,
|
||||
"tags": [
|
||||
["e", "<eventId>", "illegal"],
|
||||
["p", "<pubkey>"]
|
||||
],
|
||||
"content": "User is breaking local laws.",
|
||||
"created_at": 1735689600,
|
||||
"pubkey": "your-public-key",
|
||||
"id": "report-event-id"
|
||||
}
|
||||
```
|
||||
|
||||
### Client and Relay Behavior
|
||||
- **Clients**
|
||||
- May choose to highlight or hide reported notes if enough trusted users report them.
|
||||
- Could display “flagged content” warnings based on the user’s web-of-trust.
|
||||
- **Relays**
|
||||
- Not mandated to do anything automatically.
|
||||
- An admin could manually block content if a trusted source files many valid reports.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Replaceable Events
|
||||
- **Subscription Lists** (`30002`) and **Block Lists** (`10001`) can be implemented as replaceable events by using a deterministic `d` tag.
|
||||
- For example, `["d","my-blocklist"]` ensures older events with the same `d` are replaced when new ones arrive.
|
||||
|
||||
### Encryption
|
||||
- Use [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) for any private or sensitive data in `content` or tags.
|
||||
- Public reasons for blocking or reporting can remain in tags or clear text.
|
||||
|
||||
### Fetching User Lists
|
||||
1. **On Login**
|
||||
- Query each relay for events matching:
|
||||
```jsonc
|
||||
{
|
||||
"kinds": [10001, 30002],
|
||||
"authors": [<user-pubkey>]
|
||||
}
|
||||
```
|
||||
- Merge and deduplicate block/subscription lists from the resulting events.
|
||||
2. **Processing**
|
||||
- For blocklists, unify all “p” tags into a single set of blocked pubkeys.
|
||||
- For subscriptions, unify “p” tags or categories if the user merges multiple lists.
|
||||
|
||||
### Pushing Updates to Relays
|
||||
1. **Create Event**
|
||||
- Include kind, pubkey, timestamp, tags, and optional content.
|
||||
2. **Sign Event**
|
||||
- Use `window.nostr.signEvent` or your own signing library.
|
||||
3. **Publish**
|
||||
- Send the signed event to each configured relay.
|
||||
- Example in pseudocode:
|
||||
```javascript
|
||||
const signedEvent = await signEvent(myEvent);
|
||||
for (const relay of relays) {
|
||||
pool.publish([relay], signedEvent);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI Integration
|
||||
|
||||
### Subscription Management
|
||||
- **List View**
|
||||
- Show subscribed creators with categories if applicable.
|
||||
- **Add/Remove**
|
||||
- Allow users to add an `npub` to their list.
|
||||
- Save as a new replaceable event.
|
||||
|
||||
### Block Management
|
||||
- **Block/Unblock Button**
|
||||
- Quick action to add or remove a pubkey from the user’s blocklist.
|
||||
- **Blocklist Editor**
|
||||
- Display current blocks.
|
||||
- Optionally show reasons (public or private).
|
||||
- Publish changes via a new replaceable event.
|
||||
|
||||
### Report Management
|
||||
- **Report Button**
|
||||
- Attached to each video or note.
|
||||
- Triggers a “kind:1984” event with the chosen category (spam, nudity, etc.).
|
||||
- **Displaying Reports**
|
||||
- Optionally show how many “trusted friends” have reported a user/note.
|
||||
- Let users decide whether to hide or blur content with certain flags.
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
1. **Paid Subscriptions**
|
||||
- Could layer subscription tiers on top of `30002` events.
|
||||
2. **Global Block Lists**
|
||||
- Let users publish or subscribe to a curated blocklist (or share one in a group).
|
||||
3. **Web of Trust**
|
||||
- Filter reports based on which reporters a user trusts.
|
||||
|
||||
---
|
||||
|
||||
### Summary
|
||||
By leveraging Nostr event kinds (`30002` for subscriptions, `10001` for blocks, and `1984` for reports), bitvid can maintain a decentralized, user-controlled moderation system. Users can sync their lists across devices through relays, while administrators can choose how to handle flagged content on a platform level. This approach keeps moderation flexible and transparent.
|
@@ -0,0 +1,243 @@
|
||||
# **bitvid: Enhanced Video Comment System Specification**
|
||||
|
||||
### **Objective**
|
||||
To implement a decentralized comment system for videos shared on a Nostr-based platform, combining multiple NIPs to provide structured, interactive, and scalable functionality for video comments, reactions, live discussions, and metadata tagging.
|
||||
|
||||
---
|
||||
|
||||
### **Features**
|
||||
1. **Post Comments**:
|
||||
- Users can post comments on videos.
|
||||
- Comments are associated with a specific video event using the `e` tag.
|
||||
|
||||
2. **Structured Threading**:
|
||||
- Comments support threading by referencing parent comments using NIP-27 conventions.
|
||||
- Threaded replies are visually nested.
|
||||
|
||||
3. **Reactions**:
|
||||
- Users can react to comments (e.g., upvote, downvote) using NIP-25.
|
||||
|
||||
4. **Live Discussions**:
|
||||
- Real-time public chat using NIP-28 for live video events.
|
||||
|
||||
5. **Optional Metadata**:
|
||||
- Extra metadata fields are included for user preferences or administrative tags using NIP-24.
|
||||
|
||||
6. **Real-Time Updates**:
|
||||
- Comments, reactions, and live chats update in real-time using Nostr subscriptions.
|
||||
|
||||
7. **Moderation**:
|
||||
- Support for flagging or hiding inappropriate comments.
|
||||
|
||||
8. **Privacy**:
|
||||
- Encrypted comments for private videos (optional).
|
||||
|
||||
---
|
||||
|
||||
### **Technical Specifications**
|
||||
|
||||
#### **1. Event Structure**
|
||||
Each component (comments, reactions, live chat) is represented as a Nostr event.
|
||||
|
||||
##### **Comment Event**:
|
||||
```json
|
||||
{
|
||||
"kind": 1311,
|
||||
"pubkey": "abcdef1234567890...",
|
||||
"created_at": 1675000000,
|
||||
"tags": [
|
||||
["e", "video-event-id"], // Reference to the video
|
||||
["e", "parent-comment-id"], // Reference to the parent comment (optional, for replies)
|
||||
["p", "commenter-pubkey"] // Optional: commenter pubkey
|
||||
],
|
||||
"content": "This is a great video!"
|
||||
}
|
||||
```
|
||||
|
||||
##### **Reaction Event**:
|
||||
```json
|
||||
{
|
||||
"kind": 7,
|
||||
"pubkey": "abcdef1234567890...",
|
||||
"created_at": 1675000000,
|
||||
"tags": [
|
||||
["e", "comment-event-id"], // Reference to the comment being reacted to
|
||||
["p", "reactor-pubkey"]
|
||||
],
|
||||
"content": "+" // + for upvote, - for downvote
|
||||
}
|
||||
```
|
||||
|
||||
##### **Live Chat Event**:
|
||||
```json
|
||||
{
|
||||
"kind": 42,
|
||||
"pubkey": "abcdef1234567890...",
|
||||
"created_at": 1675000000,
|
||||
"tags": [
|
||||
["e", "video-event-id"], // Reference to the live video
|
||||
["p", "participant-pubkey"]
|
||||
],
|
||||
"content": "What a great discussion!"
|
||||
}
|
||||
```
|
||||
|
||||
##### **Metadata (Optional)**:
|
||||
Metadata tags are added to comments or live chat events as needed:
|
||||
```json
|
||||
{
|
||||
"kind": 1311,
|
||||
"pubkey": "abcdef1234567890...",
|
||||
"created_at": 1675000000,
|
||||
"tags": [
|
||||
["e", "video-event-id"],
|
||||
["m", "featured-comment"], // Example metadata tag
|
||||
["a", "admin-tag"] // Administrative tag
|
||||
],
|
||||
"content": "Highlighted comment for this video."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Implementation Details**
|
||||
|
||||
#### **1. Posting a Comment**
|
||||
To post a comment:
|
||||
1. The client constructs a comment event using NIP-22 for `created_at` validation.
|
||||
2. The event includes references to the video (mandatory) and parent comments (optional).
|
||||
3. The event is signed and published to relays.
|
||||
|
||||
##### API Example:
|
||||
```javascript
|
||||
async function postComment(videoId, commentText, parentCommentId = null) {
|
||||
const event = {
|
||||
kind: 1311,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
['e', videoId],
|
||||
...(parentCommentId ? [['e', parentCommentId]] : []),
|
||||
],
|
||||
content: commentText
|
||||
};
|
||||
|
||||
const signedEvent = await nostrClient.signEvent(event);
|
||||
await nostrClient.pool.publish(nostrClient.relays, signedEvent);
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. Reacting to a Comment**
|
||||
Reactions use NIP-25.
|
||||
|
||||
##### API Example:
|
||||
```javascript
|
||||
async function reactToComment(commentId, reaction) {
|
||||
const event = {
|
||||
kind: 7,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [['e', commentId]],
|
||||
content: reaction // Use "+" for upvote, "-" for downvote
|
||||
};
|
||||
|
||||
const signedEvent = await nostrClient.signEvent(event);
|
||||
await nostrClient.pool.publish(nostrClient.relays, signedEvent);
|
||||
}
|
||||
```
|
||||
|
||||
#### **3. Fetching Comments**
|
||||
Comments are retrieved using `REQ` messages filtered by the `e` tag for the video’s event ID and optionally by parent comment IDs for threading.
|
||||
|
||||
##### API Example:
|
||||
```javascript
|
||||
async function fetchComments(videoId) {
|
||||
const filter = {
|
||||
kinds: [1311],
|
||||
'#e': [videoId],
|
||||
limit: 100
|
||||
};
|
||||
|
||||
const comments = await nostrClient.pool.list(nostrClient.relays, [filter]);
|
||||
return comments.sort((a, b) => a.created_at - b.created_at);
|
||||
}
|
||||
```
|
||||
|
||||
#### **4. Live Chat for Live Videos**
|
||||
Real-time public chats for live videos use NIP-28.
|
||||
|
||||
##### API Example:
|
||||
```javascript
|
||||
function subscribeToLiveChat(videoId) {
|
||||
const sub = nostrClient.pool.sub(nostrClient.relays, [
|
||||
{
|
||||
kinds: [42],
|
||||
'#e': [videoId]
|
||||
}
|
||||
]);
|
||||
|
||||
sub.on('event', event => {
|
||||
console.log('New chat message:', event);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### **5. Metadata Integration**
|
||||
Use NIP-24 to attach metadata to events for administrative or user-specific tags.
|
||||
|
||||
---
|
||||
|
||||
### **Data Flow**
|
||||
1. **Posting a Comment**:
|
||||
- User creates a comment event.
|
||||
- Client signs and publishes the event to relays.
|
||||
2. **Fetching Comments**:
|
||||
- Client requests comments for a video by filtering events with the `e` tag.
|
||||
3. **Reacting to Comments**:
|
||||
- Users react to comments by posting reaction events.
|
||||
4. **Live Discussions**:
|
||||
- Live chat messages are sent and received in real-time using NIP-28.
|
||||
5. **Metadata Management**:
|
||||
- Metadata is attached during event creation or editing.
|
||||
|
||||
---
|
||||
|
||||
### **UI/UX Considerations**
|
||||
|
||||
1. **Comment Form**:
|
||||
- Input field for comments.
|
||||
- Button to post comments.
|
||||
- Optional reply button for threaded comments.
|
||||
|
||||
2. **Reactions**:
|
||||
- Upvote and downvote icons next to each comment.
|
||||
- Display reaction counts dynamically.
|
||||
|
||||
3. **Live Chat**:
|
||||
- Real-time message updates below live videos.
|
||||
- Highlight important messages using metadata tags.
|
||||
|
||||
4. **Nested Threading**:
|
||||
- Indent replies to show comment hierarchy.
|
||||
|
||||
---
|
||||
|
||||
### **Testing and Validation**
|
||||
|
||||
1. Validate:
|
||||
- Posting, retrieving, and displaying threaded comments.
|
||||
- Reactions and live chat events.
|
||||
- Metadata tagging.
|
||||
2. Test:
|
||||
- Pagination for large comment threads.
|
||||
- Performance under high comment or chat volume.
|
||||
3. Simulate:
|
||||
- Various timestamp scenarios to ensure NIP-22 compliance.
|
||||
|
||||
---
|
||||
|
||||
### **Benefits**
|
||||
- Decentralized and censorship-resistant.
|
||||
- Fully interoperable with other Nostr clients and relays.
|
||||
- Extensible with reactions, live discussions, and metadata features.
|
||||
|
@@ -0,0 +1,394 @@
|
||||
# **bitvid: Updated Plan with VRR (View, Rating, and Retention) Penalty Scoring**
|
||||
|
||||
## **Project Overview**
|
||||
|
||||
**Objective**
|
||||
Enable a decentralized video platform where every view and engagement action is logged via the Nostr protocol (NIP-78). This system incorporates both interval-based watch tracking and a penalty scoring method for short watch times (potentially due to slow loading or immediate disinterest).
|
||||
|
||||
**Key Features**
|
||||
1. Track video views and user retention at regular intervals.
|
||||
2. Assign penalty scores if a viewer leaves within certain short-watch thresholds.
|
||||
3. Use this data to power more accurate recommendations.
|
||||
4. Maintain user privacy by supporting logged-in (persistent pubkey) and session-based (temporary pubkey) tracking.
|
||||
|
||||
---
|
||||
|
||||
## **Technical Requirements**
|
||||
|
||||
1. **Video View Tracking**
|
||||
- **Event Trigger**
|
||||
- A “view” event is sent to a relay whenever a user starts playing a video.
|
||||
- Subsequent engagement events (interval updates, exit events) follow.
|
||||
- **Data Logged**
|
||||
- Video ID or magnet URI.
|
||||
- Viewer’s pubkey (for logged-in) or session-based key (for guests).
|
||||
- Timestamps and session details.
|
||||
- **Nostr Protocol Usage**
|
||||
- Build on NIP-78, using `kind: 30078` events.
|
||||
|
||||
2. **Non-Logged-In User Tracking**
|
||||
- Generate an ephemeral key pair on session start.
|
||||
- Mark the event with something like `["session", "true"]` to distinguish it from a persistent account.
|
||||
- Discard the key pair when the session ends.
|
||||
|
||||
3. **Recommendations**
|
||||
- **Algorithm**
|
||||
- Combine tags, watch intervals, exit penalty scores, and metadata for ranking.
|
||||
- **Presentation**
|
||||
- Show recommended content in a sidebar or beneath the video player.
|
||||
- **Filters**
|
||||
- Exclude deleted or private content unless the user owns it.
|
||||
|
||||
4. **User Privacy**
|
||||
- Provide an opt-in or opt-out setting for tracking.
|
||||
- Collect minimal data to preserve user autonomy.
|
||||
- Consider encryption or obfuscation for private data.
|
||||
|
||||
5. **Performance Considerations**
|
||||
- Batching events to avoid overwhelming relays (e.g., every 5 seconds or in small groups).
|
||||
- Index or cache data to speed up recommendations.
|
||||
|
||||
---
|
||||
|
||||
## **Functional Components**
|
||||
|
||||
1. **Event Structure**
|
||||
|
||||
**Regular View Event (Start)**
|
||||
```json
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "user_pubkey_or_temp_key",
|
||||
"created_at": 1672531200,
|
||||
"tags": [
|
||||
["t", "view"],
|
||||
["video", "video_id"]
|
||||
],
|
||||
"content": "{\"videoId\":\"video_id\",\"timestamp\":1672531200}"
|
||||
}
|
||||
```
|
||||
|
||||
**Interval/Retention Event (5-second, 10-second, etc.)**
|
||||
```json
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "user_pubkey_or_temp_key",
|
||||
"created_at": 1672531234,
|
||||
"tags": [
|
||||
["t", "video-watch"],
|
||||
["video", "video_id"]
|
||||
],
|
||||
"content": "{\"timestamp\":1672531234,\"currentWatchSeconds\":10}"
|
||||
}
|
||||
```
|
||||
|
||||
**Exit Event (With Penalty Score)**
|
||||
```json
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "user_pubkey_or_temp_key",
|
||||
"created_at": 1672531300,
|
||||
"tags": [
|
||||
["t", "vrr-exit"],
|
||||
["video", "video_id"]
|
||||
],
|
||||
"content": "{\"watchTime\":4,\"score\":25}"
|
||||
}
|
||||
```
|
||||
|
||||
2. **UI Updates**
|
||||
- **Video Player**
|
||||
- Trigger events when the video starts, at regular intervals, and upon exit.
|
||||
- **Recommendations**
|
||||
- Fetch and rank videos based on a combination of watch intervals, exit scores, and content tags.
|
||||
|
||||
3. **Integration Points**
|
||||
- **Frontend**
|
||||
- Implement functions like `trackViewEvent()` and `applyPenaltyScore()` in a dedicated module.
|
||||
- **Backend/Relay**
|
||||
- Store, relay, and retrieve events for analytics and recommendation algorithms.
|
||||
|
||||
4. **Development Workflow**
|
||||
- **videoTracker.js**
|
||||
- Contains the VRR methods: initial view events, interval tracking, penalty scoring on exit.
|
||||
- **app.js**
|
||||
- Orchestrates user interactions, calls into `videoTracker.js` on playback, stopping, etc.
|
||||
- **HTML/CSS**
|
||||
- Builds out a recommendations display area or scoreboard for each video.
|
||||
|
||||
---
|
||||
|
||||
## **VRR (View, Rating, and Retention) Method**
|
||||
|
||||
### **Penalty (or Bonus) Scoring for Early Exits**
|
||||
|
||||
| Watch Time <br>(seconds) | Score |
|
||||
|--------------------------|-------|
|
||||
| 0–5 | +25 |
|
||||
| 5–10 | +20 |
|
||||
| 10–15 | +15 |
|
||||
| 15–20 | +10 |
|
||||
| 20–25 | +5 |
|
||||
| 25–30 | +0 |
|
||||
|
||||
- Immediately grant **+1** view when the video starts.
|
||||
- If the user leaves before the thresholds listed above, publish an exit event with the corresponding score.
|
||||
|
||||
### **Interval-Based Retention Tracking**
|
||||
- Send an update every 5 seconds (5s, 10s, 15s, etc.) while a video is actively playing.
|
||||
- Each interval event stores current watch progress for analytics.
|
||||
|
||||
### **Example Score Usage**
|
||||
- Final overall “video health” may sum interval engagement with exit penalties.
|
||||
- Videos consistently abandoned at under 5 seconds might be flagged for slow loading or poor content.
|
||||
- The combination of interval events and exit events builds a complete retention profile.
|
||||
|
||||
---
|
||||
|
||||
## **Implementation Examples**
|
||||
|
||||
### **1. `videoTracker.js`**
|
||||
|
||||
```javascript
|
||||
// videoTracker.js
|
||||
|
||||
class VideoTracker {
|
||||
constructor(videoId, totalDuration, nostrClient) {
|
||||
this.videoId = videoId;
|
||||
this.totalDuration = totalDuration;
|
||||
this.nostrClient = nostrClient;
|
||||
this.startTimestamp = null;
|
||||
this.trackingInterval = null;
|
||||
}
|
||||
|
||||
startVideo() {
|
||||
this.startTimestamp = Date.now();
|
||||
|
||||
// Send initial "view" event (+1 View)
|
||||
const startEvent = {
|
||||
kind: 30078,
|
||||
tags: [
|
||||
["t", "view"],
|
||||
["video", this.videoId]
|
||||
],
|
||||
content: JSON.stringify({
|
||||
videoId: this.videoId,
|
||||
timestamp: this.startTimestamp
|
||||
})
|
||||
};
|
||||
this.nostrClient.publish(startEvent);
|
||||
|
||||
// Kick off interval tracking every 5 seconds
|
||||
this.trackingInterval = setInterval(() => {
|
||||
const currentTime = Date.now();
|
||||
const watchSeconds = Math.floor((currentTime - this.startTimestamp) / 1000);
|
||||
|
||||
const watchEvent = {
|
||||
kind: 30078,
|
||||
tags: [
|
||||
["t", "video-watch"],
|
||||
["video", this.videoId]
|
||||
],
|
||||
content: JSON.stringify({
|
||||
timestamp: currentTime,
|
||||
currentWatchSeconds: watchSeconds
|
||||
})
|
||||
};
|
||||
this.nostrClient.publish(watchEvent);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
stopVideo() {
|
||||
// Clear interval-based tracking
|
||||
if (this.trackingInterval) {
|
||||
clearInterval(this.trackingInterval);
|
||||
this.trackingInterval = null;
|
||||
}
|
||||
|
||||
// Compute how long the user actually watched
|
||||
if (!this.startTimestamp) return;
|
||||
const stopTimestamp = Date.now();
|
||||
const totalWatchSeconds = Math.floor((stopTimestamp - this.startTimestamp) / 1000);
|
||||
|
||||
// Apply penalty scoring
|
||||
let score = 0;
|
||||
if (totalWatchSeconds < 5) {
|
||||
score = 25;
|
||||
} else if (totalWatchSeconds < 10) {
|
||||
score = 20;
|
||||
} else if (totalWatchSeconds < 15) {
|
||||
score = 15;
|
||||
} else if (totalWatchSeconds < 20) {
|
||||
score = 10;
|
||||
} else if (totalWatchSeconds < 25) {
|
||||
score = 5;
|
||||
}
|
||||
|
||||
// Publish exit event with score
|
||||
const exitEvent = {
|
||||
kind: 30078,
|
||||
tags: [
|
||||
["t", "vrr-exit"],
|
||||
["video", this.videoId]
|
||||
],
|
||||
content: JSON.stringify({
|
||||
watchTime: totalWatchSeconds,
|
||||
score
|
||||
})
|
||||
};
|
||||
this.nostrClient.publish(exitEvent);
|
||||
|
||||
// Reset startTimestamp for the next session
|
||||
this.startTimestamp = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoTracker;
|
||||
```
|
||||
|
||||
### **2. Integrating with `app.js`**
|
||||
|
||||
```javascript
|
||||
// app.js
|
||||
|
||||
import VideoTracker from './videoTracker.js';
|
||||
|
||||
class BitvidApp {
|
||||
constructor(nostrClient) {
|
||||
this.nostrClient = nostrClient;
|
||||
this.videoTracker = null;
|
||||
}
|
||||
|
||||
playVideo(video) {
|
||||
// video might have id and totalDuration properties
|
||||
// e.g., { id: 'abc123', totalDuration: 300 }
|
||||
this.videoTracker = new VideoTracker(video.id, video.totalDuration, this.nostrClient);
|
||||
this.videoTracker.startVideo();
|
||||
|
||||
// ... code to actually play the video in the UI ...
|
||||
}
|
||||
|
||||
stopVideo() {
|
||||
if (this.videoTracker) {
|
||||
this.videoTracker.stopVideo();
|
||||
this.videoTracker = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BitvidApp;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## **Recommendation Data Flow**
|
||||
|
||||
1. **Collect**:
|
||||
- Gather interval events and exit penalty events from each viewer.
|
||||
2. **Analyze**:
|
||||
- Identify average watch times and drop-off points.
|
||||
- Rank videos that hold attention for longer while penalizing those with frequent early exits.
|
||||
3. **Display**:
|
||||
- Show suggested content to viewers in a recommendations section.
|
||||
- Optionally expose a public graph or chart showing user retention for each video.
|
||||
|
||||
---
|
||||
|
||||
Below is a combined reference that shows two separate Mermaid flowcharts:
|
||||
|
||||
1. **Load Time Penalty** — for users who abandon the video **before** playback actually begins.
|
||||
2. **Retention Tracking** — for users who start watching and either trigger a short-watch penalty or get a “no penalty” bonus for watching beyond 25 seconds.
|
||||
|
||||
---
|
||||
|
||||
## **1. Load Time Penalty**
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A((User Presses Play)) --> B[Start Measuring Load Time]
|
||||
B --> C{User Waits or Leaves?}
|
||||
C -->|Leaves Before Playback| D[+25 Slow Load Penalty]
|
||||
C -->|Playback Starts| E[Go to Retention Phase]
|
||||
```
|
||||
|
||||
**Explanation**
|
||||
- The moment the viewer clicks "Play," you begin tracking load time.
|
||||
- If the user abandons before the video actually begins, you apply a **“slow load penalty”** (e.g., +25).
|
||||
- If playback starts successfully, move on to the retention phase.
|
||||
|
||||
---
|
||||
|
||||
## **2. Retention Tracking (Penalty or Bonus)**
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A([Playback Begins]) --> B([+1 View Logged])
|
||||
B --> C{"User Exits?\n(X seconds watched)"}
|
||||
C -- "<5s" --> D([+25 Penalty])
|
||||
C -- "<10s" --> E([+20 Penalty])
|
||||
C -- "<15s" --> F([+15 Penalty])
|
||||
C -- "<20s" --> G([+10 Penalty])
|
||||
C -- "<25s" --> H([+5 Penalty])
|
||||
C -- ">=25s" --> I([No Penalty or Bonus])
|
||||
```
|
||||
|
||||
**Explanation**
|
||||
1. **Immediate View (+1)**: Once playback starts, you record an initial view.
|
||||
2. **Exit Thresholds**:
|
||||
- If the viewer leaves under 5 seconds, assign +25.
|
||||
- If under 10 seconds, +20; 15 seconds, +15; etc.
|
||||
3. **Bonus at ≥ 25s**:
|
||||
- Watching at least 25 seconds yields **no penalty**, which you can treat as a “bonus” scenario in your scoring logic—meaning it doesn’t accumulate any additional negative score.
|
||||
|
||||
---
|
||||
|
||||
### **How They Fit Together**
|
||||
|
||||
1. **Load Check**: Start measuring from the moment the user hits Play. If they bail out **before** playback, log a slow-load penalty (+25).
|
||||
2. **Playback & Retention**: As soon as playback initiates, log **+1** to indicate a valid view. From here, short exits accumulate penalties; longer watch times result in less or no penalty.
|
||||
3. **Interval Notes**: During actual viewing (after playback starts), send additional events (e.g., every 5 seconds) to track retention and watch progress.
|
||||
|
||||
This two-stage approach ensures that slow-loading videos are penalized separately from videos that load quickly but are abandoned due to other reasons.
|
||||
|
||||
---
|
||||
|
||||
## **Testing Plan**
|
||||
|
||||
1. **Unit Tests**
|
||||
- Ensure `startVideo()` and `stopVideo()` generate correct event structures.
|
||||
- Verify that penalty scores are assigned according to the specified thresholds.
|
||||
2. **Integration Tests**
|
||||
- Confirm the events flow properly to relays and can be retrieved for recommendation logic.
|
||||
- Check that the UI reflects the recommended videos accurately.
|
||||
3. **Performance Tests**
|
||||
- Simulate multiple viewers to see if the 5-second interval events cause any relay overload.
|
||||
- Adjust batching or intervals if required.
|
||||
|
||||
---
|
||||
|
||||
## **Future Enhancements**
|
||||
|
||||
- **Advanced Metrics**
|
||||
- Heatmaps to show which parts of a video users often skip.
|
||||
- Correlate viewer comments or likes with retention.
|
||||
- **ML-Based Recommendations**
|
||||
- Expand beyond simple threshold scoring, possibly using collaborative filtering.
|
||||
- **Monetization Features**
|
||||
- Integrate tipping (e.g., Lightning-based zaps) for creators who maintain high average watch times.
|
||||
|
||||
---
|
||||
|
||||
## **Timeline**
|
||||
|
||||
| **Task** | **Time Estimate** |
|
||||
|----------------------------------------|-------------------|
|
||||
| Implementation of VRR + penalty logic | 1 week |
|
||||
| UI/Recommendation Updates | 1 week |
|
||||
| Testing and Optimization | 1 week |
|
||||
| Deployment/Relay Configuration | 1 week |
|
||||
|
||||
---
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
This plan integrates short-watch penalties and interval-based retention metrics into bitvid’s decentralized architecture. By logging early exits with specific scores, you can better measure how each video performs—even if playback fails or loads slowly. The final result is a transparent system that respects user privacy while providing data-driven recommendations.
|
@@ -0,0 +1,245 @@
|
||||
# **bitvid: Enhanced Dynamic Home Page and Video Tracking Specification**
|
||||
|
||||
This document outlines how to implement a dynamic home page for bitvid using new video tracking methods. It focuses on reading video views via Nostr events rather than relying on active WebTorrent peer counts. The goal is to display personalized, popular, and trending content, all while preserving a single-page architecture and maintaining a consistent layout.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### Main Objectives
|
||||
1. **Personalized Feeds**: Recommend videos and channels by analyzing user subscriptions and view logs.
|
||||
2. **Video Tracking**: Log video views using Nostr events (kind `30078`) so the system can determine popularity and trending content.
|
||||
3. **Consistent Layout**: Use a single-page approach with views for home, profiles, and other sections. A templating or view-switching system will let the header, footer, and future sidebar remain unchanged.
|
||||
4. **Privacy Support**: Track views with either a logged-in public key or a temporary session-based key for non-logged-in users.
|
||||
|
||||
---
|
||||
|
||||
## 2. Home Page Views
|
||||
|
||||
The home page will be composed of multiple sections that show different collections of videos. All sections can be rendered within `index.html`, controlled by view logic. These sections might include:
|
||||
|
||||
1. **For You**
|
||||
- Videos from subscriptions.
|
||||
- Personalized based on user watch history or tags.
|
||||
2. **Trending**
|
||||
- Videos that have grown in views over a certain timeframe.
|
||||
- Uses view logs to gauge growth trends.
|
||||
3. **Popular**
|
||||
- Videos with the highest total views.
|
||||
- Sorted based on aggregated play events.
|
||||
|
||||
Each section can be wrapped in its own HTML container. JavaScript will fetch the relevant data, sort or filter it, then populate the DOM dynamically.
|
||||
|
||||
---
|
||||
|
||||
## 3. Video View Tracking
|
||||
|
||||
Rather than measuring active peers, bitvid now counts views by logging them as Nostr events. This enables a transparent and decentralized way to track engagement while still allowing for privacy controls.
|
||||
|
||||
### 3.1 Event Structure
|
||||
|
||||
- **Kind**: `30078` (existing kind used for video-related data).
|
||||
- **Tags**:
|
||||
- `["t", "view"]` to identify a view event.
|
||||
- `["video", "<video_id>"]` to map the event to a specific video.
|
||||
- `["session", "true"]` if the viewer is non-logged-in using a temporary session.
|
||||
- **Content**: May include a JSON object with the video ID, timestamp, and optional metadata.
|
||||
|
||||
**Example View Event (Logged In):**
|
||||
```json
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "user_pubkey",
|
||||
"created_at": 1672531200,
|
||||
"tags": [
|
||||
["t", "view"],
|
||||
["video", "video_abc123"]
|
||||
],
|
||||
"content": "{\"videoId\":\"video_abc123\",\"timestamp\":1672531200}"
|
||||
}
|
||||
```
|
||||
|
||||
**Example View Event (Temporary Session):**
|
||||
```json
|
||||
{
|
||||
"kind": 30078,
|
||||
"pubkey": "temporary_pubkey",
|
||||
"created_at": 1672531200,
|
||||
"tags": [
|
||||
["t", "view"],
|
||||
["video", "video_abc123"],
|
||||
["session", "true"]
|
||||
],
|
||||
"content": "{\"videoId\":\"video_abc123\",\"timestamp\":1672531200}"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Ranking Logic
|
||||
|
||||
1. **Popular Videos**
|
||||
- Sort by the total number of view events.
|
||||
- A simple approach is to query all view events for each video, then rank them by the count of events.
|
||||
|
||||
2. **Trending Videos**
|
||||
- Evaluate growth in view events over a recent window (for example, the last 24 hours).
|
||||
- Compare the count of new views against a previous period, or calculate the rate of increase.
|
||||
|
||||
3. **For You**
|
||||
- Look at what the user watched or subscribed to (via kind `30002` subscription lists).
|
||||
- Track the user’s or session’s recent view tags, then recommend videos with overlapping tags or from the same channels.
|
||||
|
||||
---
|
||||
|
||||
## 5. Home Page Layout and Rendering
|
||||
|
||||
### 5.1 HTML Structure
|
||||
|
||||
Your `index.html` might include placeholders for each section:
|
||||
|
||||
```html
|
||||
<div id="homeContainer">
|
||||
<!-- For You -->
|
||||
<section id="forYouSection">
|
||||
<h2>For You</h2>
|
||||
<div id="forYouGrid"></div>
|
||||
</section>
|
||||
|
||||
<!-- Trending -->
|
||||
<section id="trendingSection">
|
||||
<h2>Trending</h2>
|
||||
<div id="trendingGrid"></div>
|
||||
</section>
|
||||
|
||||
<!-- Popular -->
|
||||
<section id="popularSection">
|
||||
<h2>Popular</h2>
|
||||
<div id="popularGrid"></div>
|
||||
</section>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 5.2 JavaScript Flow
|
||||
|
||||
1. **Fetch Data**
|
||||
- Pull video metadata from kind `30078` events that contain video info or from your existing approach.
|
||||
- Pull subscription data (kind `30002`).
|
||||
- Pull view events (kind `30078` with `"t", "view"`).
|
||||
|
||||
2. **Process Rankings**
|
||||
- Tally views per video.
|
||||
- Track growth rates for trending.
|
||||
- Filter or sort data.
|
||||
|
||||
3. **Render Sections**
|
||||
- Populate each section with relevant videos.
|
||||
- A typical approach:
|
||||
|
||||
```js
|
||||
async function loadHomePage() {
|
||||
const videos = await fetchAllVideos(); // from Nostr or local data
|
||||
const viewEvents = await fetchAllViews(); // filter by "t=view"
|
||||
|
||||
const forYouData = getForYouRecommendations(videos, viewEvents);
|
||||
renderVideoGrid(forYouData, "forYouGrid");
|
||||
|
||||
const trendingData = getTrendingVideos(videos, viewEvents);
|
||||
renderVideoGrid(trendingData, "trendingGrid");
|
||||
|
||||
const popularData = getPopularVideos(videos, viewEvents);
|
||||
renderVideoGrid(popularData, "popularGrid");
|
||||
}
|
||||
```
|
||||
|
||||
- Each function (`getForYouRecommendations`, `getTrendingVideos`, `getPopularVideos`) calculates the appropriate subset of videos.
|
||||
|
||||
---
|
||||
|
||||
## 6. Single-Page View Structure
|
||||
|
||||
### 6.1 Profile and Other Views
|
||||
|
||||
- Keep the header, footer, and any sidebar in place.
|
||||
- Change the visible view container to switch between the home grid, a user’s profile, or other screens.
|
||||
- Use JavaScript routing (hash-based or history API) to detect and render the correct view in `index.html`.
|
||||
|
||||
**Example:**
|
||||
```js
|
||||
function handleRouteChange() {
|
||||
const hash = window.location.hash;
|
||||
|
||||
if (hash.startsWith("#profile")) {
|
||||
const npub = hash.split("/")[1];
|
||||
loadProfileView(npub);
|
||||
} else {
|
||||
// Default: show home
|
||||
loadHomePage();
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", handleRouteChange);
|
||||
window.addEventListener("load", handleRouteChange);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Recommendations and Personalized Feeds
|
||||
|
||||
### 7.1 Logged-In Users
|
||||
|
||||
- Use their public key to track watch history and subscription data.
|
||||
- Query view events authored by that pubkey.
|
||||
- Match frequent tags, channels, or categories.
|
||||
|
||||
### 7.2 Non-Logged-In Users
|
||||
|
||||
- Generate a session-based key pair.
|
||||
- Tag view events with `["session", "true"]`.
|
||||
- Maintain in-memory or localStorage.
|
||||
- Provide basic recommendations for the session.
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation Steps
|
||||
|
||||
1. **View Logging**:
|
||||
- Update the video player code to publish a view event when a user starts or confirms playback.
|
||||
|
||||
2. **Data Fetching**:
|
||||
- Build or adapt functions to query view events from your Nostr relays.
|
||||
- Merge the resulting data with your video metadata.
|
||||
|
||||
3. **Ranking Functions**:
|
||||
- Create functions to rank videos by total views (popular) or view velocity (trending).
|
||||
- Create a function that looks at subscription data and view history for personalized feeds.
|
||||
|
||||
4. **Rendering**:
|
||||
- Implement `renderVideoGrid(data, containerId)` to fill the given section with video cards.
|
||||
- Keep the layout responsive and consistent.
|
||||
|
||||
5. **Routes and Views**:
|
||||
- Integrate the home feed with other views (e.g., user profiles, single video modals) within the same page.
|
||||
- Use a simple router or your existing JavaScript structure to swap out sections.
|
||||
|
||||
---
|
||||
|
||||
## 9. Future Enhancements
|
||||
|
||||
1. **Advanced Recommendations**
|
||||
- Add more factors like video tags, watch duration, or user engagement events (likes, zaps).
|
||||
|
||||
2. **Analytics Dashboard**
|
||||
- Provide creators with a summary of total views, trending periods, and audience insights.
|
||||
|
||||
3. **Community Features**
|
||||
- Collaborative playlists, comments, or shared watch parties, all tracked in a decentralized manner.
|
||||
|
||||
4. **Optimized Relay Usage**
|
||||
- Implement batching or caching to limit the load on relays when publishing or querying events.
|
||||
|
||||
---
|
||||
|
||||
### Conclusion
|
||||
|
||||
This updated plan replaces the old peer-count method with view event tracking. It outlines how to fetch and render dynamic sections on the home page, switch between views, and generate personalized recommendations. By tracking views via Nostr events and rendering multiple content sections in a single-page architecture, bitvid can maintain a flexible interface while providing a richer user experience.
|
@@ -0,0 +1,205 @@
|
||||
# **bitvid: Enhanced NIP-35 + WebRTC Check Integration**
|
||||
|
||||
### Summary
|
||||
This NIP introduces **`kind=2003`** events as a standardized way to index BitTorrent metadata on Nostr. Such events typically include enough information for Nostr-based clients or indexers to display, categorize, and construct magnet links. They do **not** contain `.torrent` files themselves, only the metadata.
|
||||
|
||||
---
|
||||
|
||||
## 1. Torrent Events
|
||||
|
||||
**`kind=2003`** events are interpreted as “Torrent” posts. Clients can index, display, and search them. The essential tags are:
|
||||
|
||||
1. **`x`** (required)
|
||||
- The BitTorrent V1 info hash (hex-encoded).
|
||||
- Matches the `xt=urn:btih:HASH` parameter in magnet links.
|
||||
|
||||
2. **`file`** (optional, multiple)
|
||||
- Each `["file", "<filename>", "<filesize-in-bytes>"]` entry describes a file within the torrent.
|
||||
|
||||
3. **`tracker`** (recommended, multiple)
|
||||
- Each `["tracker", "<tracker-url>"]` entry specifies a tracker (UDP/HTTP/HTTPS/WSS).
|
||||
- If you want to support **WebTorrent/WebRTC** streaming, include at least one `wss://` tracker such as `wss://tracker.webtorrent.io`.
|
||||
|
||||
4. **`title`** (optional)
|
||||
- A short user-facing title or release name.
|
||||
|
||||
5. **`t`** (optional, multiple)
|
||||
- Simple categories or tags, e.g., `movie`, `tv`, `4k`, `3d`, `adult`.
|
||||
|
||||
### Example
|
||||
```jsonc
|
||||
{
|
||||
"kind": 2003,
|
||||
"content": "Extended cut with behind-the-scenes footage.",
|
||||
"tags": [
|
||||
["title", "The Outer Space (Extended Cut) 2025 UHD"],
|
||||
["x", "abc123def4567890abc123def4567890abc123de"],
|
||||
["file", "outer_space_2025_UHD.mkv", "78000000000"],
|
||||
["file", "extra_features", "2000000000"],
|
||||
["tracker", "udp://tracker.openbittorrent.com:6969/announce"],
|
||||
["tracker", "wss://tracker.webtorrent.io"], // WebRTC-compatible
|
||||
["t", "movie"],
|
||||
["t", "uhd"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Tag Prefixes for Extra Metadata
|
||||
|
||||
Beyond the basic torrent info, **NIP-35** supports optional tag prefixes to map the event to external data sources or richer category structures:
|
||||
|
||||
- **`tcat:`** A comma-separated category path (e.g., `["i", "tcat:video,movie,4k"]`).
|
||||
- **`newznab:`** [newznab](https://github.com/Prowlarr/Prowlarr/blob/develop/src/NzbDrone.Core/Indexers/NewznabStandardCategory.cs) category IDs.
|
||||
- **`tmdb:`, `ttvdb:`, `imdb:`, `mal:`, `anilist:`** References to well-known movie, TV, or anime databases.
|
||||
- May include specific media types, like `tmdb:movie:693134` or `mal:anime:9253`.
|
||||
|
||||
These references are optional but help indexers or other clients correlate torrent data with known database records.
|
||||
|
||||
### Example with prefixes
|
||||
```jsonc
|
||||
{
|
||||
"kind": 2003,
|
||||
"content": "Full UHD release with commentary track.",
|
||||
"tags": [
|
||||
["title", "Dune Part Two 2160p HDR"],
|
||||
["x", "abcdef0123456789..."],
|
||||
["file", "Dune.Part.Two.2024.UHD.mkv", "50000000000"],
|
||||
["tracker", "udp://tracker.openbittorrent.com:6969"],
|
||||
["tracker", "wss://tracker.webtorrent.io"],
|
||||
["i", "tcat:video,movie,4k"],
|
||||
["i", "tmdb:movie:693134"],
|
||||
["i", "ttvdb:movie:290272"],
|
||||
["t", "movie"],
|
||||
["t", "4k"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Constructing Magnet Links
|
||||
|
||||
A **kind=2003** torrent event provides data to build a magnet link, typically:
|
||||
|
||||
```
|
||||
magnet:?xt=urn:btih:<info-hash>&tr=<tracker1>&tr=<tracker2>...
|
||||
```
|
||||
|
||||
- **`x`** tag → `<info-hash>`
|
||||
- **`tracker`** tags → `&tr=<tracker-url>`
|
||||
|
||||
If a torrent is intended for **WebTorrent** streaming, it **must** have a `wss://` tracker in its `tracker` tags.
|
||||
|
||||
### Example Magnet
|
||||
```
|
||||
magnet:?xt=urn:btih:abc123def...&tr=wss%3A%2F%2Ftracker.webtorrent.io&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A6969
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. WebTorrent/WebRTC Considerations
|
||||
|
||||
1. **Include at least one `wss://` tracker** in your `tracker` tags. Example:
|
||||
```jsonc
|
||||
["tracker", "wss://tracker.webtorrent.io"]
|
||||
```
|
||||
2. **Active WebRTC seeder**: Even with a `wss://` tracker, the torrent must be seeded in real time by at least one WebTorrent/WebRTC peer. Without a WebRTC seed, browser-based clients (like Bitvid or other WebTorrent-powered apps) cannot download or stream.
|
||||
|
||||
3. **No `.torrent` file on Nostr**: This NIP is purely metadata (info hash, tracker URLs). Traditional BitTorrent clients can still use the magnet link if it has UDP/HTTP trackers.
|
||||
|
||||
---
|
||||
|
||||
## 5. Searching & Indexing
|
||||
|
||||
Implementers can:
|
||||
|
||||
- **Index** `kind=2003` events from multiple relays.
|
||||
- Provide an interface to **search** by:
|
||||
- Title (`["title", ...]`)
|
||||
- Info hash (`["x", ...]`)
|
||||
- File names (`["file", ...]`)
|
||||
- Categories (`["t", ...]`, `["i", "tcat:..."]`, etc.)
|
||||
- External references (`["i", "tmdb:movie:..."]`, `["i", "imdb:..."]`)
|
||||
- **Filter** adult content by a specific tag or category if desired.
|
||||
|
||||
---
|
||||
|
||||
## 6. Torrent Comments (kind=2004)
|
||||
|
||||
A **kind=2004** event is used to reply to a torrent event. It functions much like a regular text note (kind=1), but:
|
||||
- References the torrent event via NIP-10 reply tags.
|
||||
- Often used to discuss quality, seeding, or additional info about the torrent.
|
||||
|
||||
---
|
||||
|
||||
## 7. Blocklists & Adult Filtering
|
||||
|
||||
While the protocol is open, clients or aggregators may apply local policies:
|
||||
- **Blocklists** to ignore certain pubkeys or known spam.
|
||||
- **Adult filtering** to hide or show certain categories or tags (like `["t", "adult"]` or `["i", "tcat:video,adult"]`).
|
||||
|
||||
This is **not** enforced at the protocol level but is left to each implementer’s discretion.
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation Notes
|
||||
|
||||
1. **No `.torrent` Files**
|
||||
`.torrent` files are not stored on Nostr—just the info hash and any relevant tracker URLs.
|
||||
|
||||
2. **Announce Sets**
|
||||
Clients should collect **all** `["tracker", ...]` tags to build a comprehensive announce set in the magnet link. This ensures maximum connectivity for both UDP/HTTP and WebRTC trackers.
|
||||
|
||||
3. **Fallback**
|
||||
If a torrent lacks `wss://` trackers, WebTorrent clients can’t stream it unless an external service or user rehosts it with a WebRTC seed/tracker combination.
|
||||
|
||||
4. **Metadata Matching**
|
||||
If `["i", "tmdb:movie:693134"]` or similar tags are present, clients may fetch additional metadata (like cover art) from that external database. This is optional and outside the core scope of NIP-35 but encouraged for better user experience.
|
||||
|
||||
5. **Periodic Refresh**
|
||||
Since torrents are seeded in real time, a WebTorrent client (e.g., in a browser) may show a torrent as “active” only if at least one WebRTC seed is online. Clients/aggregators might periodically check the presence of active WebRTC peers to update their UI.
|
||||
|
||||
---
|
||||
|
||||
## 9. Example JSON
|
||||
|
||||
Below is a more complete JSON example following NIP-35 conventions, demonstrating various optional fields:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 2003,
|
||||
"content": "Full UHD release. Contains behind-the-scenes extras, commentary track, and sample clips.",
|
||||
"tags": [
|
||||
["title", "Dune Part Two 2160p HDR"],
|
||||
["x", "abcdef0123456789abcdef0123456789abcdef01"],
|
||||
["file", "Dune.Part.Two.2024.UHD.mkv", "50000000000"],
|
||||
["file", "Behind.The.Scenes.mp4", "2000000000"],
|
||||
["tracker", "udp://tracker.openbittorrent.com:6969"],
|
||||
["tracker", "wss://tracker.webtorrent.io"],
|
||||
["tracker", "udp://tracker.coppersurfer.tk:6969"],
|
||||
["i", "tcat:video,movie,uhd"],
|
||||
["i", "tmdb:movie:693134"],
|
||||
["i", "imdb:tt15239678"],
|
||||
["t", "movie"],
|
||||
["t", "uhd"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. References & Links
|
||||
|
||||
- **[BitTorrent Magnet Link Specification (BEP-0053)](https://www.bittorrent.org/beps/bep_0053.html)**
|
||||
- **[WebTorrent Documentation](https://webtorrent.io/)**
|
||||
- **[NIP-10: Replies and Mentions](https://github.com/nostr-protocol/nips/blob/master/10.md)**
|
||||
- **[dtan.xyz Repository](https://git.v0l.io/Kieran/dtan)**
|
||||
- **[nostrudel.ninja Implementation](https://github.com/hzrd149/nostrudel/tree/next/src/views/torrents)**
|
||||
|
||||
---
|
||||
|
||||
### End of Updated Spec Sheet
|
||||
|
||||
This revision aims to integrate WebTorrent/WebRTC considerations seamlessly into the existing NIP-35 structure. It remains optional to provide `wss://` trackers, but doing so is strongly recommended if you want real-time streaming in browser-based clients like Bitvid or other WebTorrent-powered frontends.
|
Reference in New Issue
Block a user