mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 01:11:25 +00:00
add media back
This commit is contained in:
parent
d712dc99bb
commit
50b6e49934
3
client/.gitignore
vendored
3
client/.gitignore
vendored
@ -24,4 +24,5 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
.env
|
@ -9,7 +9,9 @@
|
|||||||
"@types/node": "^17.0.45",
|
"@types/node": "^17.0.45",
|
||||||
"@types/react": "^18.2.21",
|
"@types/react": "^18.2.21",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"axios": "^1.7.5",
|
||||||
"link-preview-js": "^3.0.5",
|
"link-preview-js": "^3.0.5",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"nostr-tools": "2.5.1",
|
"nostr-tools": "2.5.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@ -59,6 +61,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.6",
|
"@tailwindcss/forms": "^0.5.6",
|
||||||
|
"@types/lodash": "^4.17.7",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,13 +122,13 @@ const NewNoteCard = ({
|
|||||||
const { handleSubmit: originalHandleSubmit, doingWorkProp, hashrate, bestPow, signedPoWEvent } = useSubmitForm(unsigned, difficulty);
|
const { handleSubmit: originalHandleSubmit, doingWorkProp, hashrate, bestPow, signedPoWEvent } = useSubmitForm(unsigned, difficulty);
|
||||||
|
|
||||||
const handleSubmit = async (event: React.FormEvent) => {
|
const handleSubmit = async (event: React.FormEvent) => {
|
||||||
await originalHandleSubmit(event);
|
|
||||||
|
|
||||||
// Check if tagType is 'Quote' and update comment
|
// Check if tagType is 'Quote' and update comment
|
||||||
if (tagType === 'Quote' && refEvent) {
|
if (tagType === 'Quote' && refEvent) {
|
||||||
setComment(prevComment => prevComment + '\nnostr:' + nip19.noteEncode(refEvent.id));
|
setComment(prevComment => prevComment + '\nnostr:' + nip19.noteEncode(refEvent.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await originalHandleSubmit(event);
|
||||||
|
|
||||||
setComment("");
|
setComment("");
|
||||||
setUnsigned(prevUnsigned => ({
|
setUnsigned(prevUnsigned => ({
|
||||||
...prevUnsigned,
|
...prevUnsigned,
|
||||||
@ -222,7 +222,7 @@ const NewNoteCard = ({
|
|||||||
{hashrate && <span>{hashrate > 100000 ? `${(hashrate / 1000).toFixed(0)}k` : hashrate}</span>}H/s
|
{hashrate && <span>{hashrate > 100000 ? `${(hashrate / 1000).toFixed(0)}k` : hashrate}</span>}H/s
|
||||||
<span className="pl-1"> (PB:{bestPow}</span><CpuChipIcon className="h-4 w-4" />,
|
<span className="pl-1"> (PB:{bestPow}</span><CpuChipIcon className="h-4 w-4" />,
|
||||||
<div className="text-xs text-gray-300 pl-1">
|
<div className="text-xs text-gray-300 pl-1">
|
||||||
~{timeToGoEst(difficulty, hashrate)}
|
~{timeToGoEst(difficulty, hashrate)} total
|
||||||
</div>)
|
</div>)
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -2,7 +2,7 @@ import { parseContent } from "../../../utils/content";
|
|||||||
import { Event } from "nostr-tools";
|
import { Event } from "nostr-tools";
|
||||||
import { getMetadata } from "../../../utils/getMetadata";
|
import { getMetadata } from "../../../utils/getMetadata";
|
||||||
import ContentPreview from "./TextModal";
|
import ContentPreview from "./TextModal";
|
||||||
import { renderMedia } from "../../../utils/FileUpload";
|
import RenderMedia from "../MediaRender";
|
||||||
import { getIconFromHash, timeAgo } from "../../../utils/cardUtils";
|
import { getIconFromHash, timeAgo } from "../../../utils/cardUtils";
|
||||||
import { CpuChipIcon } from "@heroicons/react/24/outline";
|
import { CpuChipIcon } from "@heroicons/react/24/outline";
|
||||||
import { verifyPow } from "../../../utils/mine";
|
import { verifyPow } from "../../../utils/mine";
|
||||||
@ -30,6 +30,7 @@ const QuoteEmbed = ({
|
|||||||
<div className="flex flex-col break-words">
|
<div className="flex flex-col break-words">
|
||||||
<ContentPreview key={event.id} eventdata={event} />
|
<ContentPreview key={event.id} eventdata={event} />
|
||||||
</div>
|
</div>
|
||||||
|
<RenderMedia files={files} />
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
{metadataParsed ?
|
{metadataParsed ?
|
||||||
<img
|
<img
|
||||||
|
@ -50,12 +50,6 @@ const ContentPreview = ({ key, eventdata }: { key: string; eventdata: Event }) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const findUrl = comment.match(/\bhttps?:\/\/\S+/gi);
|
|
||||||
if (findUrl && findUrl.length > 0) {
|
|
||||||
setUrl(findUrl[0]);
|
|
||||||
setFinalComment(finalComment.replace(findUrl[0], "").trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = comment.match(/\bnostr:(?:nevent1|note1)([a-z0-9]+)/i);
|
const match = comment.match(/\bnostr:(?:nevent1|note1)([a-z0-9]+)/i);
|
||||||
const nostrURI = match && match[1];
|
const nostrURI = match && match[1];
|
||||||
if (nostrURI && quoteEvents.length === 0) {
|
if (nostrURI && quoteEvents.length === 0) {
|
||||||
|
113
client/src/components/modals/MediaRender.tsx
Normal file
113
client/src/components/modals/MediaRender.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
// Function to check media against the API
|
||||||
|
const checkMedia = async (url: string) => {
|
||||||
|
try {
|
||||||
|
const token = process.env.REACT_APP_NSFW_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
console.error("NSFW token is not set in environment variables");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('https://nsfw-detector-api-latest.onrender.com/predict', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ url }),
|
||||||
|
});
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking media:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const RenderMedia = ({ files }: { files: string[] }) => {
|
||||||
|
const gridTemplateColumns = files.length > 1 ? 'repeat(2, 1fr)' : 'repeat(1, 1fr)';
|
||||||
|
const gridTemplateRows = files.length > 2 ? 'repeat(2, 1fr)' : 'repeat(1, 1fr)';
|
||||||
|
const whitelistImageURL = ["nostr.build", "void.cat", "blossom.oxtr", "image.nostr.build"];
|
||||||
|
const [mediaCheckResults, setMediaCheckResults] = useState<Record<string, any>>({});
|
||||||
|
|
||||||
|
// Function to toggle blur on click
|
||||||
|
const toggleBlur = (event: React.MouseEvent<HTMLImageElement>) => {
|
||||||
|
event.currentTarget.classList.toggle('no-blur');
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const performMediaChecks = async () => {
|
||||||
|
for (const file of files) {
|
||||||
|
const result = await checkMedia(file);
|
||||||
|
console.log(`Result for ${file}:`, result);
|
||||||
|
if (result && result.data && result.data.predictedLabel) {
|
||||||
|
setMediaCheckResults(prev => ({
|
||||||
|
...prev,
|
||||||
|
[file]: { predictedLabel: result.data.predictedLabel }
|
||||||
|
}));
|
||||||
|
console.error(`Unexpected result structure for ${file}:`, result.data.predictedLabel);
|
||||||
|
} else {
|
||||||
|
console.error(`Unexpected result structure for ${file}:`, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(mediaCheckResults).length === 0) {
|
||||||
|
performMediaChecks();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns, gridTemplateRows, gap: '2px' }}>
|
||||||
|
{files.map((file, index) => {
|
||||||
|
// Check if the file is from allowed domains
|
||||||
|
const isFromAllowedDomain = whitelistImageURL.some(domain => file.includes(domain));
|
||||||
|
const mediaCheckResult = mediaCheckResults[file];
|
||||||
|
|
||||||
|
// Only render if predictedLabel is neutral
|
||||||
|
if (mediaCheckResult && mediaCheckResult.predictedLabel !== 'neutral') {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="text-center text-red-500 text-xs">Attached media has been flagged as not safe for work.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file && (file.endsWith(".mp4") || file.endsWith(".webm")) && mediaCheckResult && mediaCheckResult.predictedLabel === 'neutral') {
|
||||||
|
return (
|
||||||
|
<video
|
||||||
|
key={index}
|
||||||
|
controls
|
||||||
|
muted
|
||||||
|
src={file + "#t=0.1"}
|
||||||
|
preload="metadata"
|
||||||
|
className="thumb mt-1 rounded-md w-full"
|
||||||
|
>
|
||||||
|
<source src={file} type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
);
|
||||||
|
} else if (file && mediaCheckResult && mediaCheckResult.predictedLabel === 'neutral') {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
key={index}
|
||||||
|
alt="Invalid thread"
|
||||||
|
loading="lazy"
|
||||||
|
className={`thumb mt-2 max-w-64 min-h-64 mx-auto rounded-md`}
|
||||||
|
src={file}
|
||||||
|
onClick={isFromAllowedDomain ? undefined : toggleBlur} // Only add onClick if blur is applied
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="text-center text-white-500 text-xs">Checking media...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderMedia;
|
@ -1,14 +1,14 @@
|
|||||||
import { FolderIcon, CpuChipIcon } from "@heroicons/react/24/outline";
|
import { FolderIcon, CpuChipIcon } from "@heroicons/react/24/outline";
|
||||||
// import { parseContent } from "../../utils/content";
|
import { parseContent } from "../../utils/content";
|
||||||
import { Event, nip19 } from "nostr-tools";
|
import { Event, nip19 } from "nostr-tools";
|
||||||
import { getMetadata } from "../../utils/getMetadata";
|
import { getMetadata } from "../../utils/getMetadata";
|
||||||
// import { renderMedia } from "../../utils/FileUpload";
|
|
||||||
import { getIconFromHash, timeAgo } from "../../utils/cardUtils";
|
import { getIconFromHash, timeAgo } from "../../utils/cardUtils";
|
||||||
import { verifyPow } from "../../utils/mine";
|
import { verifyPow } from "../../utils/mine";
|
||||||
import { uniqBy } from "../../utils/otherUtils";
|
import { uniqBy } from "../../utils/otherUtils";
|
||||||
import ContentPreview from "./CardModals/TextModal";
|
import ContentPreview from "./CardModals/TextModal";
|
||||||
import CardContainer from "./CardContainer";
|
import CardContainer from "./CardContainer";
|
||||||
import { useState, useEffect } from "react";
|
import React, { useState, useEffect, useMemo} from "react";
|
||||||
|
import RenderMedia from "./MediaRender";
|
||||||
|
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
key?: string | number;
|
key?: string | number;
|
||||||
@ -27,7 +27,7 @@ const PostCard = ({
|
|||||||
repliedTo,
|
repliedTo,
|
||||||
type
|
type
|
||||||
}: CardProps) => {
|
}: CardProps) => {
|
||||||
// const { files } = parseContent(event);
|
const { files } = parseContent(event);
|
||||||
const icon = getIconFromHash(event.pubkey);
|
const icon = getIconFromHash(event.pubkey);
|
||||||
const metadataParsed = metadata ? getMetadata(metadata) : null;
|
const metadataParsed = metadata ? getMetadata(metadata) : null;
|
||||||
const [relatedEvents, setRelatedEvents] = useState<Event[]>([]);
|
const [relatedEvents, setRelatedEvents] = useState<Event[]>([]);
|
||||||
@ -65,13 +65,13 @@ const PostCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContainer>
|
<CardContainer>
|
||||||
<div className={`flex flex-col gap-2`} key={key}>
|
<div className={`flex flex-col gap-2`} key={key}>
|
||||||
<div className={`flex flex-col break-words ${type !== "OP" ? 'hover:cursor-pointer' : ''}`} onClick={handleClick}>
|
<div className={`flex flex-col break-words ${type !== "OP" ? 'hover:cursor-pointer' : ''}`} onClick={handleClick}>
|
||||||
<ContentPreview key={parsedEvent.id} eventdata={parsedEvent} />
|
<ContentPreview key={parsedEvent.id} eventdata={parsedEvent} />
|
||||||
</div>
|
</div>
|
||||||
|
<RenderMedia files={files} />
|
||||||
{repliedTo && <div className="flex items-center mt-1" >
|
{repliedTo && <div className="flex items-center mt-1" >
|
||||||
<span className="text-xs text-gray-500">Reply to: </span>
|
<span className="text-xs text-gray-500">Reply to: </span>
|
||||||
{uniqBy(repliedTo, 'pubkey').map((parsedEvent, index) => {
|
{uniqBy(repliedTo, 'pubkey').map((parsedEvent, index) => {
|
||||||
|
@ -79,7 +79,7 @@ const Settings = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
value={filterDifficulty}
|
value={filterDifficulty}
|
||||||
onChange={e => setFilterDifficulty(e.target.value)}
|
onChange={e => setFilterDifficulty(e.target.value)}
|
||||||
min={21}
|
min={22}
|
||||||
className="w-full px-3 py-2 border rounded-md bg-black"
|
className="w-full px-3 py-2 border rounded-md bg-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -94,7 +94,7 @@ const Settings = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
value={difficulty}
|
value={difficulty}
|
||||||
onChange={e => setDifficulty(e.target.value)}
|
onChange={e => setDifficulty(e.target.value)}
|
||||||
min={21}
|
min={22}
|
||||||
className="w-full px-3 py-2 border rounded-md bg-black"
|
className="w-full px-3 py-2 border rounded-md bg-black"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1 +1 @@
|
|||||||
export const DEFAULT_DIFFICULTY = 21;
|
export const DEFAULT_DIFFICULTY = 22;
|
@ -1,128 +0,0 @@
|
|||||||
import { generateSecretKey, getPublicKey, finalizeEvent } from "nostr-tools";
|
|
||||||
import { base64 } from "@scure/base";
|
|
||||||
|
|
||||||
export interface UploadResult {
|
|
||||||
url?: string;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const whitelistImageURL = ["nostr.build", "void.cat", "blossom.oxtr"];
|
|
||||||
/**
|
|
||||||
* Upload file to void.cat
|
|
||||||
* https://void.cat/swagger/index.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default async function FileUpload(file: File): Promise<UploadResult> {
|
|
||||||
const sk = generateSecretKey();
|
|
||||||
const fileBuffer = await file.arrayBuffer();
|
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', fileBuffer);
|
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
||||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
||||||
|
|
||||||
const auth = async () => {
|
|
||||||
const authEvent = {
|
|
||||||
kind: 24242,
|
|
||||||
content: "Upload " + file.name + " from getwired.app",
|
|
||||||
tags: [
|
|
||||||
["t", "upload"],
|
|
||||||
["x", hashHex],
|
|
||||||
["expiration", (Math.floor(Date.now() / 1000) + 24 * 60 * 60).toString()]
|
|
||||||
],
|
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
|
||||||
pubkey: getPublicKey(sk),
|
|
||||||
}
|
|
||||||
const authString = JSON.stringify(finalizeEvent(authEvent, sk));
|
|
||||||
const authBase64 = base64.encode(new TextEncoder().encode(authString));
|
|
||||||
return `Nostr ${authBase64}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = await fetch("https://blossom.oxtr.dev/upload", {
|
|
||||||
body: file,
|
|
||||||
method: "PUT",
|
|
||||||
headers: {
|
|
||||||
"authorization": await auth() // Use the encoded authorization header
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (req.ok) {
|
|
||||||
const fileExtension = file.name.split(".").pop(); // Extracting the file extension
|
|
||||||
const resultUrl = `https://blossom.oxtr.dev/${hashHex}.${fileExtension}`;
|
|
||||||
return { url: resultUrl };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
error: "Upload failed",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const renderMedia = (files: string[]) => {
|
|
||||||
const gridTemplateColumns = files.length > 1 ? 'repeat(2, 1fr)' : 'repeat(1, 1fr)';
|
|
||||||
const gridTemplateRows = files.length > 2 ? 'repeat(2, 1fr)' : 'repeat(1, 1fr)';
|
|
||||||
|
|
||||||
// Function to toggle blur on click
|
|
||||||
const toggleBlur = (event: React.MouseEvent<HTMLImageElement>) => {
|
|
||||||
event.currentTarget.classList.toggle('no-blur');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns, gridTemplateRows, gap: '2px' }}>
|
|
||||||
{files.map((file, index) => {
|
|
||||||
// Check if the file is from allowed domains
|
|
||||||
const isFromAllowedDomain = whitelistImageURL.some(domain => file.includes(domain));
|
|
||||||
|
|
||||||
if (file && (file.endsWith(".mp4") || file.endsWith(".webm"))) {
|
|
||||||
return (
|
|
||||||
<video
|
|
||||||
key={index}
|
|
||||||
controls
|
|
||||||
muted
|
|
||||||
src={file + "#t=0.1"}
|
|
||||||
preload="metadata"
|
|
||||||
className="thumb mt-1 rounded-md w-full"
|
|
||||||
>
|
|
||||||
<source src={file} type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
);
|
|
||||||
} else if (!file.includes("http")) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
key={index}
|
|
||||||
alt="Invalid thread"
|
|
||||||
loading="lazy"
|
|
||||||
className={`thumb mt-2 rounded-md w-full ${!isFromAllowedDomain ? "blur" : ""}`}
|
|
||||||
src={file}
|
|
||||||
onClick={isFromAllowedDomain ? undefined : toggleBlur} // Only add onClick if blur is applied
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function attachFile(file_input: File | null): Promise<string> {
|
|
||||||
if (!file_input) {
|
|
||||||
throw new Error("No file provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const rx = await FileUpload(file_input);
|
|
||||||
|
|
||||||
if (rx.error) {
|
|
||||||
throw new Error(rx.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rx.url || "No URL returned from FileUpload";
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
throw new Error(`File upload failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Unknown error occurred during file upload");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UploadResult {
|
|
||||||
url?: string;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
@ -7,12 +7,12 @@ function extractMediaUrls(content: string): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseContent(event: Event) {
|
export function parseContent(event: Event) {
|
||||||
const files: string[] = []; // extractMediaUrls(event.content);
|
const files = extractMediaUrls(event.content);
|
||||||
let contentWithoutFiles = event.content;
|
let contentWithoutFiles = event.content;
|
||||||
|
|
||||||
// files.forEach(file => {
|
files.forEach(file => {
|
||||||
// contentWithoutFiles = contentWithoutFiles.replace(file, '');
|
contentWithoutFiles = contentWithoutFiles.replace(file, '');
|
||||||
// });
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
comment: contentWithoutFiles.trim(),
|
comment: contentWithoutFiles.trim(),
|
||||||
|
@ -13,7 +13,7 @@ export const subGlobalFeed = (onEvent: SubCallback, age: number) => {
|
|||||||
const now = Math.floor(Date.now() * 0.001);
|
const now = Math.floor(Date.now() * 0.001);
|
||||||
const pubkeys = new Set<string>();
|
const pubkeys = new Set<string>();
|
||||||
const notes = new Set<string>();
|
const notes = new Set<string>();
|
||||||
const prefix = 4; // 4 bits in each '0' character
|
const prefix = 6; // 4 bits in each '0' character
|
||||||
sub({ // get past events
|
sub({ // get past events
|
||||||
cb: (evt, relay) => {
|
cb: (evt, relay) => {
|
||||||
pubkeys.add(evt.pubkey);
|
pubkeys.add(evt.pubkey);
|
||||||
@ -300,7 +300,7 @@ export const subHashtagFeed = (
|
|||||||
unsub: true
|
unsub: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const prefix = 4; // 4 bits in each '0' character
|
const prefix = 6; // 4 bits in each '0' character
|
||||||
sub({ // get past events
|
sub({ // get past events
|
||||||
cb: (evt, relay) => {
|
cb: (evt, relay) => {
|
||||||
pubkeys.add(evt.pubkey);
|
pubkeys.add(evt.pubkey);
|
||||||
|
Loading…
Reference in New Issue
Block a user