mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
The forms hardcoded the maintainer's PUBLIC contact npub (npub13yarr7j… — the blog/tip-jar/nostr persona, the old DM recipient) as the submission recipient, but the admin Submissions tab queries submissions tagged to ADMIN_SUPER_NPUB (npub15jnttpym…). Mismatch → the tab found nothing. Point all 5 forms' recipient at ADMIN_SUPER_NPUB so submissions reach the admin whose tab lists them. Lint + build clean. Co-Authored-By: Claude Opus 4.8 <[email protected]>
512 lines
18 KiB
HTML
512 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>bitvid Whitelist Application 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>
|
||
<!-- Shared anti-spam npub validation (sets window.validateNpubHex) -->
|
||
<script type="module" src="../../js/utils/validateNpub.js"></script>
|
||
</head>
|
||
<body class="iframe-form" data-ds="new">
|
||
<div class="container">
|
||
<div class="form-container">
|
||
<p>
|
||
bitvid is currently in early access. If you would like to request
|
||
access, please fill out this application. Applications will be
|
||
reviewed manually, and approvals will be based on alignment with
|
||
bitvid’s community values.
|
||
</p>
|
||
<form id="wl-form">
|
||
<!-- 1. Applicant Information -->
|
||
<h2>1. Applicant Information</h2>
|
||
<label for="applicantNpub">Nostr Public Key (npub):</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="applicantNpub"
|
||
placeholder="Enter your npub"
|
||
required
|
||
/>
|
||
|
||
<label for="contactMethod"
|
||
>Preferred Contact Method (if applicable):</label
|
||
>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="contactMethod"
|
||
placeholder="Nostr DM, email, or other"
|
||
/>
|
||
|
||
<label for="username">Username or Alias (if applicable):</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="username"
|
||
placeholder="Enter your preferred name"
|
||
/>
|
||
|
||
<!-- 2. Content Intent -->
|
||
<h2>2. Content Intent</h2>
|
||
<p>
|
||
What type of content do you plan to upload? (Check all that apply)
|
||
</p>
|
||
<div class="checkbox-group">
|
||
<label
|
||
><input
|
||
class="checkbox"
|
||
type="checkbox"
|
||
name="contentType"
|
||
value="Educational"
|
||
/>
|
||
Educational</label
|
||
>
|
||
<label
|
||
><input
|
||
class="checkbox"
|
||
type="checkbox"
|
||
name="contentType"
|
||
value="Entertainment"
|
||
/>
|
||
Entertainment</label
|
||
>
|
||
<label
|
||
><input
|
||
class="checkbox"
|
||
type="checkbox"
|
||
name="contentType"
|
||
value="News & Journalism"
|
||
/>
|
||
News & Journalism</label
|
||
>
|
||
<label
|
||
><input
|
||
class="checkbox"
|
||
type="checkbox"
|
||
name="contentType"
|
||
value="Creative Works"
|
||
/>
|
||
Creative Works</label
|
||
>
|
||
<label
|
||
><input
|
||
class="checkbox"
|
||
type="checkbox"
|
||
name="contentType"
|
||
value="Discussions & Opinions"
|
||
/>
|
||
Discussions & Opinions</label
|
||
>
|
||
<label
|
||
><input
|
||
class="checkbox"
|
||
type="checkbox"
|
||
name="contentType"
|
||
value="Other"
|
||
/>
|
||
Other</label
|
||
>
|
||
</div>
|
||
<label for="otherContent">Other (please specify):</label>
|
||
<input
|
||
class="form-control"
|
||
type="text"
|
||
id="otherContent"
|
||
placeholder="Describe if Other was selected"
|
||
/>
|
||
|
||
<label for="whyJoin">Why do you want to join bitvid?</label>
|
||
<textarea
|
||
class="textarea"
|
||
id="whyJoin"
|
||
rows="3"
|
||
placeholder="Explain your motivation"
|
||
></textarea>
|
||
|
||
<label for="priorExperience"
|
||
>Have you created content on other platforms before?</label
|
||
>
|
||
<select class="select" id="priorExperience">
|
||
<option value="">Select an option</option>
|
||
<option value="Yes">Yes</option>
|
||
<option value="No">No</option>
|
||
</select>
|
||
|
||
<label for="experienceLinks"
|
||
>If yes, provide links or references to your previous work:</label
|
||
>
|
||
<textarea
|
||
class="textarea"
|
||
id="experienceLinks"
|
||
rows="2"
|
||
placeholder="Paste links or describe your work"
|
||
></textarea>
|
||
|
||
<!-- 3. Community Engagement -->
|
||
<h2>3. Community Engagement</h2>
|
||
<label for="familiarGuidelines"
|
||
>Are you familiar with bitvid’s Community Guidelines?</label
|
||
>
|
||
<select class="select" id="familiarGuidelines">
|
||
<option value="">Select an option</option>
|
||
<option value="Yes">Yes</option>
|
||
<option value="No">No</option>
|
||
</select>
|
||
|
||
<label for="agreeGuidelines"
|
||
>Do you agree to follow the guidelines and respect decentralized
|
||
moderation?</label
|
||
>
|
||
<select class="select" id="agreeGuidelines">
|
||
<option value="">Select an option</option>
|
||
<option value="Yes">Yes</option>
|
||
<option value="No">No</option>
|
||
</select>
|
||
|
||
<label for="communityContribution"
|
||
>How do you plan to contribute positively to the bitvid
|
||
community?</label
|
||
>
|
||
<textarea
|
||
class="textarea"
|
||
id="communityContribution"
|
||
rows="3"
|
||
placeholder="Explain your approach"
|
||
></textarea>
|
||
|
||
<!-- 4. Additional Information -->
|
||
<h2>4. Additional Information</h2>
|
||
<label for="specialSkills"
|
||
>Do you have any special skills or interests that could help improve
|
||
bitvid?</label
|
||
>
|
||
<textarea
|
||
class="textarea"
|
||
id="specialSkills"
|
||
rows="3"
|
||
placeholder="e.g., software development, moderation, design, advocacy"
|
||
></textarea>
|
||
|
||
<label for="testFeatures"
|
||
>Would you be interested in helping test new features?</label
|
||
>
|
||
<select class="select" id="testFeatures">
|
||
<option value="">Select an option</option>
|
||
<option value="Yes">Yes</option>
|
||
<option value="No">No</option>
|
||
</select>
|
||
|
||
<!-- 5. Declaration -->
|
||
<h2>5. Declaration</h2>
|
||
<p>
|
||
By submitting this application, you confirm that:
|
||
<br />- The information provided is accurate. <br />- You understand
|
||
that approval is based on community alignment and available
|
||
capacity. <br />- You acknowledge that whitelist status may be
|
||
revoked if guidelines are violated.
|
||
</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 Application</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",
|
||
];
|
||
const pool = new SimplePool();
|
||
|
||
document
|
||
.getElementById("wl-form")
|
||
.addEventListener("submit", async (ev) => {
|
||
ev.preventDefault();
|
||
clear();
|
||
try {
|
||
// Retrieve applicant-provided input.
|
||
const applicantNpub = document
|
||
.getElementById("applicantNpub")
|
||
.value.trim();
|
||
|
||
// Anti-spam: the application identifier must be a valid npub.
|
||
const applicantPubHex =
|
||
typeof window.validateNpubHex === "function"
|
||
? window.validateNpubHex(applicantNpub)
|
||
: null;
|
||
if (!applicantPubHex) {
|
||
log(
|
||
"Please enter a valid Nostr public key (npub1…) before submitting. Your application was not sent.",
|
||
"error"
|
||
);
|
||
document.getElementById("applicantNpub")?.focus();
|
||
return;
|
||
}
|
||
|
||
const contactMethod = document
|
||
.getElementById("contactMethod")
|
||
.value.trim();
|
||
const username = document.getElementById("username").value.trim();
|
||
|
||
// Section 2: Content Intent
|
||
// Get all checked content types
|
||
const contentTypeNodes = document.querySelectorAll(
|
||
'input[name="contentType"]:checked'
|
||
);
|
||
let contentTypes = [];
|
||
contentTypeNodes.forEach((node) => {
|
||
contentTypes.push(node.value);
|
||
});
|
||
const otherContent = document
|
||
.getElementById("otherContent")
|
||
.value.trim();
|
||
const whyJoin = document.getElementById("whyJoin").value.trim();
|
||
const priorExperience = document
|
||
.getElementById("priorExperience")
|
||
.value.trim();
|
||
const experienceLinks = document
|
||
.getElementById("experienceLinks")
|
||
.value.trim();
|
||
|
||
// Section 3: Community Engagement
|
||
const familiarGuidelines = document
|
||
.getElementById("familiarGuidelines")
|
||
.value.trim();
|
||
const agreeGuidelines = document
|
||
.getElementById("agreeGuidelines")
|
||
.value.trim();
|
||
const communityContribution = document
|
||
.getElementById("communityContribution")
|
||
.value.trim();
|
||
|
||
// Section 4: Additional Information
|
||
const specialSkills = document
|
||
.getElementById("specialSkills")
|
||
.value.trim();
|
||
const testFeatures = document
|
||
.getElementById("testFeatures")
|
||
.value.trim();
|
||
|
||
// Section 5: Declaration
|
||
const signature = document
|
||
.getElementById("signature")
|
||
.value.trim();
|
||
const declarationDate = document
|
||
.getElementById("declarationDate")
|
||
.value.trim();
|
||
|
||
// Construct the whitelist application content.
|
||
const applicationContent = `
|
||
# **bitvid Whitelist Application Form**
|
||
|
||
**1. Applicant Information**
|
||
- **Nostr Public Key (npub):** ${applicantNpub}
|
||
- **Pubkey (hex):** ${applicantPubHex}
|
||
- **Preferred Contact Method:** ${contactMethod || "N/A"}
|
||
- **Username or Alias:** ${username || "N/A"}
|
||
|
||
**2. Content Intent**
|
||
- **Planned Content Types:** ${contentTypes.join(", ") || "N/A"}
|
||
- **Other (if applicable):** ${otherContent || "N/A"}
|
||
- **Why do you want to join bitvid?**
|
||
${whyJoin || "N/A"}
|
||
- **Have you created content on other platforms before?** ${
|
||
priorExperience || "N/A"
|
||
}
|
||
- **Links or references to your previous work:**
|
||
${experienceLinks || "N/A"}
|
||
|
||
**3. Community Engagement**
|
||
- **Familiar with bitvid’s Community Guidelines?** ${
|
||
familiarGuidelines || "N/A"
|
||
}
|
||
- **Agree to follow guidelines and respect moderation?** ${
|
||
agreeGuidelines || "N/A"
|
||
}
|
||
- **How do you plan to contribute to the community?**
|
||
${communityContribution || "N/A"}
|
||
|
||
**4. Additional Information**
|
||
- **Special skills or interests:**
|
||
${specialSkills || "N/A"}
|
||
- **Interested in testing new features?** ${testFeatures || "N/A"}
|
||
|
||
**5. Declaration**
|
||
By submitting this application, you confirm that:
|
||
- The information provided is accurate.
|
||
- You understand that approval is based on community alignment and available capacity.
|
||
- You acknowledge that whitelist status may be revoked if guidelines are violated.
|
||
|
||
**Signature (Digital or Written):** ${signature || "N/A"}
|
||
**Date:** ${declarationDate || "N/A"}
|
||
|
||
---
|
||
|
||
**Processing Time:** Applications will be reviewed within **7-14 days**. If approved, you will receive a confirmation via your provided contact method. If additional information is needed, we will reach out.
|
||
|
||
For further questions, contact us through bitvid’s Nostr support channels.
|
||
`.trim();
|
||
|
||
log(
|
||
"[DEBUG] Constructed application content:\n" +
|
||
applicationContent
|
||
);
|
||
|
||
// 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; the admin's
|
||
// Submissions tab lists these and can approve → add to whitelist.
|
||
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", "application"],
|
||
["applicant", applicantNpub],
|
||
["p", targetPubHex],
|
||
["t", "bitvid-submission"],
|
||
["client", "bitvid"],
|
||
],
|
||
content: applicationContent,
|
||
};
|
||
log(
|
||
"[DEBUG] Event template before finalizing: " +
|
||
JSON.stringify(eventTemplate)
|
||
);
|
||
|
||
// Finalize the event (computing the id and signature).
|
||
const event = finalizeEvent(eventTemplate, ephemeralPriv);
|
||
log("[DEBUG] Final event: " + JSON.stringify(event));
|
||
|
||
// Publish the event to all relays.
|
||
log("Publishing the application to relays...");
|
||
await Promise.any(pool.publish(RELAYS, event));
|
||
log("At least one relay accepted the event.", "success");
|
||
|
||
// For each relay, subscribe to verify the event appears.
|
||
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 application 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 application was found in storage, a moderator will review your application 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>
|