mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Submissions are signed by throwaway ephemeral keys and public relays prune events from pubkeys with no social graph, so the queue quietly evaporated. - Forms: also publish to nos.lol (better retention, already in the app's query set) alongside snort/damus/primal. - submissionFacade: on each fetch, re-broadcast the still-pending signed events back to our relays (publishEventToRelays) so pruning doesn't drop them. Best-effort, fire-and-forget — never blocks or fails the fetch; no-ops when no relay pool is available. - test: fetch still returns the pending queue when a pool is present. Co-Authored-By: Claude Opus 4.8 <[email protected]>
445 lines
16 KiB
HTML
445 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>bitvid Content Appeals Form</title>
|
||
<script type="module" src="./theme-sync.js"></script>
|
||
<!-- Link to your main stylesheet -->
|
||
<link rel="stylesheet" href="../../css/tailwind.generated.css" />
|
||
<!-- Load nostr‑tools v2.17.0 -->
|
||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/nostr.bundle.min.js"></script>
|
||
</head>
|
||
<body class="iframe-form" data-ds="new">
|
||
<div class="container">
|
||
<div class="form-container">
|
||
<p>
|
||
If you believe your content was unfairly blocked or restricted on
|
||
bitvid, please complete this form. Appeals will be reviewed manually,
|
||
and decisions will be communicated back to you.
|
||
</p>
|
||
<form id="dm-form">
|
||
<!-- NPUB input removed as the recipient is now set by the code -->
|
||
<h2>1. User Information</h2>
|
||
<label for="contactMethod">Contact Method (if applicable):</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="contactMethod"
|
||
placeholder="Nostr DM, email, or other"
|
||
/>
|
||
|
||
<h2>2. Content Details</h2>
|
||
<label for="videoTitle">Title of the Video:</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="videoTitle"
|
||
placeholder="Enter the exact title"
|
||
/>
|
||
|
||
<label for="magnetLink">Magnet Link:</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="magnetLink"
|
||
placeholder="Enter the magnet link"
|
||
/>
|
||
|
||
<label for="eventPointer">Blocked video link or event ID:</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="eventPointer"
|
||
placeholder="Paste the video's share link, nevent1…, note1…, or 64-char event id"
|
||
/>
|
||
<p class="form-hint">
|
||
Required — open the blocked video's ⋯ menu, choose “Copy link”, and
|
||
paste it here so a moderator can locate and unblock the exact video.
|
||
</p>
|
||
|
||
<label for="submissionDate">Date of Content Submission:</label>
|
||
<input class="form-control" type="date" id="submissionDate" />
|
||
|
||
<h2>3. Reason for Appeal</h2>
|
||
<label for="reasonBlocked"
|
||
>Why do you believe your content was unfairly blocked?</label
|
||
>
|
||
<textarea
|
||
class="textarea"
|
||
id="reasonBlocked"
|
||
rows="3"
|
||
placeholder="Explain in detail"
|
||
></textarea>
|
||
|
||
<label for="fitsGuidelines"
|
||
>Does your content fit within bitvid's Community Guidelines?</label
|
||
>
|
||
<select class="select" id="fitsGuidelines">
|
||
<option value="">Select an option</option>
|
||
<option value="Yes">Yes</option>
|
||
<option value="No">No</option>
|
||
</select>
|
||
|
||
<label for="guidelinesCited"
|
||
>If yes, which guideline(s) support your appeal?</label
|
||
>
|
||
<textarea
|
||
class="textarea"
|
||
id="guidelinesCited"
|
||
rows="2"
|
||
placeholder="Cite the specific guidelines"
|
||
></textarea>
|
||
|
||
<label for="editedContent"
|
||
>Was this content edited after being blocked?</label
|
||
>
|
||
<select class="select" id="editedContent">
|
||
<option value="">Select an option</option>
|
||
<option value="Yes">Yes</option>
|
||
<option value="No">No</option>
|
||
</select>
|
||
|
||
<label for="changesMade">If yes, what changes were made?</label>
|
||
<textarea
|
||
class="textarea"
|
||
id="changesMade"
|
||
rows="2"
|
||
placeholder="Describe the modifications"
|
||
></textarea>
|
||
|
||
<h2>4. Additional Context</h2>
|
||
<label for="misunderstanding"
|
||
>Was there any misunderstanding or misclassification?</label
|
||
>
|
||
<textarea
|
||
class="textarea"
|
||
id="misunderstanding"
|
||
rows="2"
|
||
placeholder="Provide context"
|
||
></textarea>
|
||
|
||
<label for="externalReferences"
|
||
>Are there external references that validate your appeal?</label
|
||
>
|
||
<textarea
|
||
class="textarea"
|
||
id="externalReferences"
|
||
rows="2"
|
||
placeholder="Links, citations, or additional info"
|
||
></textarea>
|
||
|
||
<h2>5. Declaration</h2>
|
||
<p>
|
||
By submitting this appeal, you confirm that:
|
||
<br />- You are the original creator or authorized representative of
|
||
the content. <br />- Your appeal is submitted in good faith and
|
||
aligns with bitvid’s policies. <br />- You understand that final
|
||
decisions are at the discretion of bitvid’s moderation process.
|
||
</p>
|
||
<label for="signature">Signature (Digital or Written):</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="signature"
|
||
placeholder="Your signature"
|
||
/>
|
||
|
||
<label for="declarationDate">Date:</label>
|
||
<input class="form-control" type="date" id="declarationDate" />
|
||
|
||
<button class="btn" type="submit">Submit Appeal</button>
|
||
</form>
|
||
<div id="status"></div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
// Provide no-op fallback loggers so embeds stay functional if bitvidLogger is unavailable.
|
||
const noopLogger = {
|
||
log: () => {},
|
||
info: () => {},
|
||
warn: () => {},
|
||
error: () => {},
|
||
};
|
||
const dev = window.bitvidLogger?.dev || noopLogger;
|
||
const user = window.bitvidLogger?.user || noopLogger;
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
// Logging functions for both on-page and console output.
|
||
function log(msg, type = "info") {
|
||
const div = document.createElement("div");
|
||
div.classList.add("status-line");
|
||
if (type === "error") div.classList.add("error");
|
||
if (type === "success") div.classList.add("success");
|
||
if (type === "warn") div.classList.add("warn");
|
||
div.textContent = msg;
|
||
document.getElementById("status").appendChild(div);
|
||
const formatted = `[${type.toUpperCase()}] ${msg}`;
|
||
if (type === "error") {
|
||
user.error(formatted);
|
||
} else if (type === "warn") {
|
||
user.warn(formatted);
|
||
} else if (type === "success") {
|
||
user.log(formatted);
|
||
} else if (type === "info") {
|
||
dev.info(formatted);
|
||
} else {
|
||
dev.log(formatted);
|
||
}
|
||
}
|
||
function clear() {
|
||
document.getElementById("status").innerHTML = "";
|
||
}
|
||
if (!window.NostrTools) {
|
||
log("NostrTools not loaded. Check console or ad-blockers.", "error");
|
||
return;
|
||
}
|
||
const {
|
||
generateSecretKey,
|
||
getPublicKey,
|
||
finalizeEvent,
|
||
nip04,
|
||
nip19,
|
||
SimplePool,
|
||
Relay,
|
||
} = window.NostrTools;
|
||
|
||
// Set the recipient's NPUB (your personal NPUB) here.
|
||
const recipientNpub =
|
||
"npub15jnttpymeytm80hatjqcvhhqhzrhx6gxp8pq0wn93rhnu8s9h9dsha32lx";
|
||
|
||
const RELAYS = [
|
||
"wss://relay.snort.social",
|
||
"wss://relay.damus.io",
|
||
"wss://relay.primal.net",
|
||
"wss://nos.lol",
|
||
];
|
||
const pool = new SimplePool();
|
||
|
||
// Validate + normalize the blocked-video reference to a 64-char hex
|
||
// event id. Accepts a bitvid share link (which embeds an nevent), a raw
|
||
// nevent1…/note1… pointer, or a bare hex id. Returns "" when nothing
|
||
// valid is found so the caller can block submission.
|
||
const HEX_EVENT_ID = /^[0-9a-f]{64}$/i;
|
||
function extractEventHex(input) {
|
||
const trimmed = String(input || "").trim();
|
||
if (!trimmed) {
|
||
return "";
|
||
}
|
||
if (HEX_EVENT_ID.test(trimmed)) {
|
||
return trimmed.toLowerCase();
|
||
}
|
||
// Pull an nevent/note token out of a URL or free text.
|
||
const match = trimmed.match(/(nevent1[0-9a-z]+|note1[0-9a-z]+)/i);
|
||
const token = match ? match[1] : trimmed;
|
||
try {
|
||
const decoded = nip19.decode(token);
|
||
if (decoded.type === "nevent" && HEX_EVENT_ID.test(decoded.data?.id || "")) {
|
||
return decoded.data.id.toLowerCase();
|
||
}
|
||
if (decoded.type === "note" && HEX_EVENT_ID.test(decoded.data || "")) {
|
||
return decoded.data.toLowerCase();
|
||
}
|
||
} catch (err) {
|
||
// not a decodable pointer
|
||
}
|
||
return "";
|
||
}
|
||
|
||
document
|
||
.getElementById("dm-form")
|
||
.addEventListener("submit", async (ev) => {
|
||
ev.preventDefault();
|
||
clear();
|
||
try {
|
||
// Note: The customer's NPUB input is removed.
|
||
// Use the recipientNpub constant to get the public key.
|
||
|
||
const contactMethod = document
|
||
.getElementById("contactMethod")
|
||
.value.trim();
|
||
const videoTitle = document
|
||
.getElementById("videoTitle")
|
||
.value.trim();
|
||
const magnetLink = document
|
||
.getElementById("magnetLink")
|
||
.value.trim();
|
||
const eventPointer = document
|
||
.getElementById("eventPointer")
|
||
.value.trim();
|
||
const targetEventHex = extractEventHex(eventPointer);
|
||
if (!targetEventHex) {
|
||
log(
|
||
"Please paste a valid video share link, nevent1…, note1…, or 64-char event id so a moderator can locate the blocked video.",
|
||
"error"
|
||
);
|
||
return;
|
||
}
|
||
const submissionDate = document
|
||
.getElementById("submissionDate")
|
||
.value.trim();
|
||
const reasonBlocked = document
|
||
.getElementById("reasonBlocked")
|
||
.value.trim();
|
||
const fitsGuidelines = document
|
||
.getElementById("fitsGuidelines")
|
||
.value.trim();
|
||
const guidelinesCited = document
|
||
.getElementById("guidelinesCited")
|
||
.value.trim();
|
||
const editedContent = document
|
||
.getElementById("editedContent")
|
||
.value.trim();
|
||
const changesMade = document
|
||
.getElementById("changesMade")
|
||
.value.trim();
|
||
const misunderstanding = document
|
||
.getElementById("misunderstanding")
|
||
.value.trim();
|
||
const externalReferences = document
|
||
.getElementById("externalReferences")
|
||
.value.trim();
|
||
const signature = document
|
||
.getElementById("signature")
|
||
.value.trim();
|
||
const declarationDate = document
|
||
.getElementById("declarationDate")
|
||
.value.trim();
|
||
|
||
// Construct the appeal content.
|
||
const appealContent = `
|
||
# **bitvid Content Appeals Form**
|
||
|
||
**1. User Information**
|
||
- **Contact Method:** ${contactMethod || "N/A"}
|
||
|
||
**2. Content Details**
|
||
- **Title of the Video:** ${videoTitle}
|
||
- **Magnet Link:** ${magnetLink}
|
||
- **Blocked Event ID:** ${targetEventHex}
|
||
- **Date of Content Submission:** ${submissionDate}
|
||
|
||
**3. Reason for Appeal**
|
||
- **Why do you believe your content was unfairly blocked?**
|
||
${reasonBlocked}
|
||
- **Does your content fit within bitvid's Community Guidelines?**
|
||
${fitsGuidelines}
|
||
- **If yes, which guideline(s) support your appeal?**
|
||
${guidelinesCited}
|
||
- **Was this content edited after being blocked?**
|
||
${editedContent}
|
||
- **If yes, what changes were made?**
|
||
${changesMade}
|
||
|
||
**4. Additional Context**
|
||
- **Was there any misunderstanding or misclassification?**
|
||
${misunderstanding}
|
||
- **Are there external references that validate your appeal?**
|
||
${externalReferences}
|
||
|
||
**5. Declaration**
|
||
By submitting this appeal, you confirm that:
|
||
- You are the original creator or an authorized representative of the content.
|
||
- Your appeal is submitted in good faith and aligns with bitvid’s policies.
|
||
- You understand that final decisions are at the discretion of bitvid’s moderation process.
|
||
|
||
**Signature (Digital or Written):** ${signature}
|
||
**Date:** ${declarationDate}
|
||
|
||
---
|
||
|
||
**Processing Time:** Appeals will be reviewed within **7-14 days**. If additional information is required, you will be contacted via your provided contact method.
|
||
|
||
For further questions, reach out through bitvid’s Nostr support channels.
|
||
`.trim();
|
||
|
||
log("[DEBUG] Constructed appeal content:\n" + appealContent);
|
||
|
||
// Decode the recipient NPUB to get the public key.
|
||
log("Decoding recipient npub...");
|
||
const decoded = nip19.decode(recipientNpub);
|
||
log("[DEBUG] Decoded npub: " + JSON.stringify(decoded));
|
||
if (decoded.type !== "npub") {
|
||
throw new Error("Decoded type is not npub.");
|
||
}
|
||
const targetPubHex = decoded.data;
|
||
log("Recipient pubkey: " + targetPubHex.slice(0, 16) + "...");
|
||
|
||
// Generate an ephemeral key pair.
|
||
log("Generating ephemeral key...");
|
||
const ephemeralPriv = generateSecretKey();
|
||
const ephemeralPubHex = getPublicKey(ephemeralPriv);
|
||
log("Ephemeral pubkey: " + ephemeralPubHex.slice(0, 16) + "...");
|
||
|
||
// Build a structured bitvid submission (kind 30083, PUBLIC — no
|
||
// encryption). Published from the ephemeral key.
|
||
log("Building appeal submission (kind 30083, public)...");
|
||
const now = Math.floor(Date.now() / 1000);
|
||
const submissionId =
|
||
"sub-" +
|
||
now.toString(36) +
|
||
"-" +
|
||
Math.random().toString(36).slice(2, 10);
|
||
const eventTemplate = {
|
||
kind: 30083,
|
||
created_at: now,
|
||
tags: [
|
||
["d", submissionId],
|
||
["k", "appeal"],
|
||
["p", targetPubHex],
|
||
["e", targetEventHex],
|
||
["t", "bitvid-submission"],
|
||
["client", "bitvid"],
|
||
],
|
||
content: appealContent,
|
||
};
|
||
log(
|
||
"[DEBUG] Event template before finalizing: " +
|
||
JSON.stringify(eventTemplate)
|
||
);
|
||
|
||
// Finalize the event.
|
||
const event = finalizeEvent(eventTemplate, ephemeralPriv);
|
||
log("[DEBUG] Final event: " + JSON.stringify(event));
|
||
|
||
// Publish the event to all relays.
|
||
log("Publishing the appeal to relays...");
|
||
await Promise.any(pool.publish(RELAYS, event));
|
||
log("At least one relay accepted the event.", "success");
|
||
|
||
// Subscribe to each relay.
|
||
for (const url of RELAYS) {
|
||
log("Connecting to " + url + " for subscription...");
|
||
const relay = await Relay.connect(url);
|
||
relay.subscribe([{ authors: [ephemeralPubHex], kinds: [30083] }], {
|
||
onEvent(foundEvent) {
|
||
if (foundEvent.id === event.id) {
|
||
log(
|
||
"[" +
|
||
url +
|
||
"] => Found our appeal in storage! ID: " +
|
||
foundEvent.id.slice(0, 8) +
|
||
"...",
|
||
"success"
|
||
);
|
||
}
|
||
},
|
||
onEose() {
|
||
relay.close();
|
||
},
|
||
});
|
||
}
|
||
|
||
log(
|
||
"Done. If the logs show that at least one relay accepted the event and the appeal was found in storage, a moderator will review your appeal within 7-14 days."
|
||
);
|
||
} catch (err) {
|
||
log("Error: " + err.message, "error");
|
||
user.error(err);
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
|
||
</body>
|
||
</html>
|