mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
428 lines
13 KiB
JavaScript
428 lines
13 KiB
JavaScript
import { devLogger } from "../utils/logger.js";
|
|
import { SHORT_TIMEOUT_MS } from "../constants.js";
|
|
|
|
export default class EngagementController {
|
|
constructor({ services = {}, ui = {}, state = {} } = {}) {
|
|
this.services = {
|
|
nostrClient: services.nostrClient,
|
|
};
|
|
this.ui = {
|
|
showError: ui.showError || (() => {}),
|
|
showSuccess: ui.showSuccess || (() => {}),
|
|
showStatus: ui.showStatus || (() => {}),
|
|
};
|
|
this.state = {
|
|
getCurrentVideo: state.getCurrentVideo || (() => null),
|
|
getCurrentVideoPointer: state.getCurrentVideoPointer || (() => null),
|
|
};
|
|
}
|
|
|
|
derivePointerFromDataset(dataset = {}, context = "") {
|
|
const type =
|
|
typeof dataset.pointerType === "string" ? dataset.pointerType.trim() : "";
|
|
const value =
|
|
typeof dataset.pointerValue === "string"
|
|
? dataset.pointerValue.trim()
|
|
: "";
|
|
const relay =
|
|
typeof dataset.pointerRelay === "string"
|
|
? dataset.pointerRelay.trim()
|
|
: "";
|
|
|
|
if (type && value) {
|
|
return relay ? [type, value, relay] : [type, value];
|
|
}
|
|
|
|
const currentVideoPointer = this.state.getCurrentVideoPointer();
|
|
if (
|
|
context === "modal" &&
|
|
Array.isArray(currentVideoPointer) &&
|
|
currentVideoPointer.length >= 2
|
|
) {
|
|
return currentVideoPointer;
|
|
}
|
|
|
|
const currentVideo = this.state.getCurrentVideo();
|
|
if (
|
|
context === "modal" &&
|
|
Array.isArray(currentVideo?.pointer) &&
|
|
currentVideo.pointer.length >= 2
|
|
) {
|
|
return currentVideo.pointer;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async handleRepostAction(dataset = {}) {
|
|
const context = typeof dataset.context === "string" ? dataset.context : "";
|
|
const explicitEventId =
|
|
typeof dataset.eventId === "string" && dataset.eventId.trim()
|
|
? dataset.eventId.trim()
|
|
: "";
|
|
|
|
const currentVideo = this.state.getCurrentVideo();
|
|
const fallbackEventId =
|
|
context === "modal" && currentVideo?.id ? currentVideo.id : "";
|
|
const targetEventId = explicitEventId || fallbackEventId;
|
|
|
|
if (!targetEventId) {
|
|
this.ui.showError("No event is available to repost.");
|
|
return;
|
|
}
|
|
|
|
const pointer = this.derivePointerFromDataset(dataset, context);
|
|
|
|
let author =
|
|
typeof dataset.author === "string" ? dataset.author.trim() : "";
|
|
if (!author && context === "modal" && currentVideo?.pubkey) {
|
|
author = currentVideo.pubkey;
|
|
}
|
|
|
|
const rawKindValue = (() => {
|
|
if (typeof dataset.kind === "string" && dataset.kind.trim()) {
|
|
const parsed = Number.parseInt(dataset.kind.trim(), 10);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|
|
if (Number.isFinite(dataset.kind)) {
|
|
return Number(dataset.kind);
|
|
}
|
|
if (Number.isFinite(currentVideo?.kind)) {
|
|
return Number(currentVideo.kind);
|
|
}
|
|
return null;
|
|
})();
|
|
|
|
const options = {
|
|
pointer,
|
|
pointerType: dataset.pointerType,
|
|
pointerValue: dataset.pointerValue,
|
|
pointerRelay: dataset.pointerRelay,
|
|
authorPubkey: author,
|
|
};
|
|
|
|
if (Number.isFinite(rawKindValue)) {
|
|
options.kind = Math.floor(rawKindValue);
|
|
}
|
|
|
|
try {
|
|
const result = await this.services.nostrClient.repostEvent(
|
|
targetEventId,
|
|
options
|
|
);
|
|
|
|
if (!result?.ok) {
|
|
const code = result?.error || "repost-failed";
|
|
switch (code) {
|
|
case "invalid-event-id":
|
|
this.ui.showError("No event is available to repost.");
|
|
break;
|
|
case "missing-actor":
|
|
this.ui.showError(
|
|
"Cannot sign the repost right now. Please refresh and try again."
|
|
);
|
|
break;
|
|
case "pool-unavailable":
|
|
this.ui.showError(
|
|
"Cannot reach relays right now. Please try again later."
|
|
);
|
|
break;
|
|
case "publish-rejected":
|
|
this.ui.showError("No relay accepted the repost attempt.");
|
|
break;
|
|
case "signing-failed":
|
|
this.ui.showError("Failed to sign the repost. Please try again.");
|
|
break;
|
|
default:
|
|
this.ui.showError(
|
|
"Failed to repost the video. Please try again later."
|
|
);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const acceptedCount = Array.isArray(result.summary?.accepted)
|
|
? result.summary.accepted.length
|
|
: 0;
|
|
const relayCount =
|
|
acceptedCount > 0
|
|
? acceptedCount
|
|
: Array.isArray(result.relays)
|
|
? result.relays.length
|
|
: acceptedCount;
|
|
|
|
const fragments = [];
|
|
if (relayCount > 0) {
|
|
fragments.push(
|
|
`Reposted to ${relayCount} relay${relayCount === 1 ? "" : "s"}.`
|
|
);
|
|
} else {
|
|
fragments.push("Reposted.");
|
|
}
|
|
|
|
if (result.sessionActor) {
|
|
fragments.push("Boost as session user.");
|
|
}
|
|
|
|
this.ui.showSuccess(fragments.join(" ").trim());
|
|
} catch (error) {
|
|
devLogger.warn("[EngagementController] Repost action failed:", error);
|
|
this.ui.showError("Failed to repost the video. Please try again later.");
|
|
}
|
|
}
|
|
|
|
async handleMirrorAction(dataset = {}) {
|
|
const context = typeof dataset.context === "string" ? dataset.context : "";
|
|
const explicitEventId =
|
|
typeof dataset.eventId === "string" && dataset.eventId.trim()
|
|
? dataset.eventId.trim()
|
|
: "";
|
|
|
|
const currentVideo = this.state.getCurrentVideo();
|
|
const fallbackEventId =
|
|
context === "modal" && currentVideo?.id ? currentVideo.id : "";
|
|
const targetEventId = explicitEventId || fallbackEventId;
|
|
|
|
if (!targetEventId) {
|
|
this.ui.showError("No event is available to mirror.");
|
|
return;
|
|
}
|
|
|
|
const explicitUrl =
|
|
typeof dataset.url === "string" && dataset.url.trim()
|
|
? dataset.url.trim()
|
|
: "";
|
|
const fallbackUrl =
|
|
context === "modal" && typeof currentVideo?.url === "string"
|
|
? currentVideo.url.trim()
|
|
: "";
|
|
const targetUrl = explicitUrl || fallbackUrl;
|
|
|
|
if (!targetUrl) {
|
|
this.ui.showError("This video does not expose a hosted URL to mirror.");
|
|
return;
|
|
}
|
|
|
|
const rawMagnet =
|
|
typeof dataset.magnet === "string" && dataset.magnet.trim()
|
|
? dataset.magnet.trim()
|
|
: "";
|
|
const fallbackMagnet =
|
|
context === "modal"
|
|
? currentVideo?.magnet || currentVideo?.originalMagnet || ""
|
|
: "";
|
|
const magnet = rawMagnet || fallbackMagnet;
|
|
|
|
const thumbnail =
|
|
typeof dataset.thumbnail === "string" && dataset.thumbnail.trim()
|
|
? dataset.thumbnail.trim()
|
|
: context === "modal" && typeof currentVideo?.thumbnail === "string"
|
|
? currentVideo.thumbnail.trim()
|
|
: "";
|
|
|
|
const description =
|
|
typeof dataset.description === "string" && dataset.description.trim()
|
|
? dataset.description.trim()
|
|
: context === "modal" && typeof currentVideo?.description === "string"
|
|
? currentVideo.description.trim()
|
|
: "";
|
|
|
|
const title =
|
|
typeof dataset.title === "string" && dataset.title.trim()
|
|
? dataset.title.trim()
|
|
: context === "modal" && typeof currentVideo?.title === "string"
|
|
? currentVideo.title.trim()
|
|
: "";
|
|
|
|
const datasetPrivate =
|
|
dataset.isPrivate === "true" || dataset.isPrivate === true ? true : false;
|
|
const fallbackPrivate =
|
|
context === "modal" && currentVideo?.isPrivate === true;
|
|
const isPrivate = datasetPrivate || fallbackPrivate;
|
|
|
|
if (isPrivate) {
|
|
this.ui.showError("Mirroring is unavailable for private videos.");
|
|
return;
|
|
}
|
|
|
|
const options = {
|
|
url: targetUrl,
|
|
magnet,
|
|
thumbnail,
|
|
description,
|
|
title,
|
|
isPrivate,
|
|
};
|
|
|
|
try {
|
|
const result = await this.services.nostrClient.mirrorVideoEvent(
|
|
targetEventId,
|
|
options
|
|
);
|
|
|
|
if (!result?.ok) {
|
|
const code = result?.error || "mirror-failed";
|
|
switch (code) {
|
|
case "invalid-event-id":
|
|
this.ui.showError("No event is available to mirror.");
|
|
break;
|
|
case "missing-url":
|
|
this.ui.showError(
|
|
"This video does not expose a hosted URL to mirror."
|
|
);
|
|
break;
|
|
case "missing-actor":
|
|
this.ui.showError(
|
|
"Cannot sign the mirror right now. Please refresh and try again."
|
|
);
|
|
break;
|
|
case "pool-unavailable":
|
|
this.ui.showError(
|
|
"Cannot reach relays right now. Please try again later."
|
|
);
|
|
break;
|
|
case "publish-rejected":
|
|
this.ui.showError("No relay accepted the mirror attempt.");
|
|
break;
|
|
case "signing-failed":
|
|
this.ui.showError("Failed to sign the mirror. Please try again.");
|
|
break;
|
|
default:
|
|
this.ui.showError(
|
|
"Failed to mirror the video. Please try again later."
|
|
);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const acceptedCount = Array.isArray(result.summary?.accepted)
|
|
? result.summary.accepted.length
|
|
: 0;
|
|
const relayCount =
|
|
acceptedCount > 0
|
|
? acceptedCount
|
|
: Array.isArray(result.relays)
|
|
? result.relays.length
|
|
: acceptedCount;
|
|
|
|
const fragments = [];
|
|
if (relayCount > 0) {
|
|
fragments.push(
|
|
`Mirrored to ${relayCount} relay${relayCount === 1 ? "" : "s"}.`
|
|
);
|
|
} else {
|
|
fragments.push("Mirrored.");
|
|
}
|
|
|
|
if (result.sessionActor) {
|
|
fragments.push("Boost as session user.");
|
|
}
|
|
|
|
this.ui.showSuccess(fragments.join(" ").trim());
|
|
} catch (error) {
|
|
devLogger.warn("[EngagementController] Mirror action failed:", error);
|
|
this.ui.showError("Failed to mirror the video. Please try again later.");
|
|
}
|
|
}
|
|
|
|
async handleEnsurePresenceAction(dataset = {}) {
|
|
const context = typeof dataset.context === "string" ? dataset.context : "";
|
|
const explicitEventId =
|
|
typeof dataset.eventId === "string" && dataset.eventId.trim()
|
|
? dataset.eventId.trim()
|
|
: "";
|
|
|
|
const currentVideo = this.state.getCurrentVideo();
|
|
const fallbackEventId =
|
|
context === "modal" && currentVideo?.id ? currentVideo.id : "";
|
|
const targetEventId = explicitEventId || fallbackEventId;
|
|
|
|
if (!targetEventId) {
|
|
this.ui.showError("No event is available to rebroadcast.");
|
|
return;
|
|
}
|
|
|
|
const explicitPubkey =
|
|
typeof dataset.pubkey === "string" && dataset.pubkey.trim()
|
|
? dataset.pubkey.trim()
|
|
: "";
|
|
const datasetAuthor =
|
|
typeof dataset.author === "string" && dataset.author.trim()
|
|
? dataset.author.trim()
|
|
: "";
|
|
const fallbackPubkey =
|
|
context === "modal" && typeof currentVideo?.pubkey === "string"
|
|
? currentVideo.pubkey
|
|
: datasetAuthor;
|
|
const targetPubkey = explicitPubkey || fallbackPubkey || "";
|
|
|
|
try {
|
|
const result = await this.services.nostrClient.rebroadcastEvent(
|
|
targetEventId,
|
|
{
|
|
pubkey: targetPubkey,
|
|
}
|
|
);
|
|
|
|
if (result?.throttled) {
|
|
const remainingMs = Math.max(
|
|
0,
|
|
Number(result?.cooldown?.remainingMs) || 0
|
|
);
|
|
const remainingSeconds = Math.ceil(remainingMs / 1000);
|
|
const message =
|
|
remainingSeconds > 0
|
|
? `Rebroadcast is cooling down. Try again in ${remainingSeconds}s.`
|
|
: "Rebroadcast is cooling down. Try again soon.";
|
|
this.ui.showStatus(message);
|
|
if (
|
|
typeof window !== "undefined" &&
|
|
typeof window.setTimeout === "function"
|
|
) {
|
|
window.setTimeout(() => {
|
|
this.ui.showStatus("");
|
|
}, SHORT_TIMEOUT_MS);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!result?.ok) {
|
|
const code = result?.error || "rebroadcast-failed";
|
|
switch (code) {
|
|
case "event-not-found":
|
|
this.ui.showError(
|
|
"Original event payload is unavailable. Reload and try again."
|
|
);
|
|
break;
|
|
case "publish-rejected":
|
|
this.ui.showError("No relay accepted the rebroadcast attempt.");
|
|
break;
|
|
case "pool-unavailable":
|
|
this.ui.showError(
|
|
"Cannot reach relays right now. Please try again later."
|
|
);
|
|
break;
|
|
default:
|
|
this.ui.showError(
|
|
"Failed to rebroadcast. Please try again later."
|
|
);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (result?.alreadyPresent) {
|
|
this.ui.showSuccess("Relays already have this revision.");
|
|
return;
|
|
}
|
|
|
|
this.ui.showSuccess("Rebroadcast requested across relays.");
|
|
} catch (error) {
|
|
devLogger.warn("[EngagementController] Rebroadcast action failed:", error);
|
|
this.ui.showError("Failed to rebroadcast. Please try again later.");
|
|
}
|
|
}
|
|
}
|