diff --git a/src/element/goal.tsx b/src/element/goal.tsx index feb21d0..a241fc6 100644 --- a/src/element/goal.tsx +++ b/src/element/goal.tsx @@ -6,19 +6,20 @@ import Confetti from "react-confetti"; import { type NostrEvent } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; -import { findTag } from "utils"; +import { eventToLink, findTag } from "utils"; import { formatSats } from "number"; import usePreviousValue from "hooks/usePreviousValue"; import { SendZapsDialog } from "element/send-zap"; -import { useZaps } from "hooks/goals"; import { getName } from "element/profile"; import { Icon } from "./icon"; import { FormattedMessage } from "react-intl"; +import { useZaps } from "hooks/zaps"; export function Goal({ ev }: { ev: NostrEvent }) { const profile = useUserProfile(ev.pubkey); const zapTarget = profile?.lud16 ?? profile?.lud06; - const zaps = useZaps(ev, true); + const link = eventToLink(ev); + const zaps = useZaps(link, true); const goalAmount = useMemo(() => { const amount = findTag(ev, "amount"); return amount ? Number(amount) / 1000 : null; diff --git a/src/element/live-chat.tsx b/src/element/live-chat.tsx index 2721654..1caf24e 100644 --- a/src/element/live-chat.tsx +++ b/src/element/live-chat.tsx @@ -1,7 +1,8 @@ import "./live-chat.css"; +import { FormattedMessage } from "react-intl"; import { EventKind, NostrPrefix, NostrLink, ParsedZap, NostrEvent, parseZap, encodeTLV } from "@snort/system"; -import { unixNow, unwrap } from "@snort/shared"; -import { useEffect, useMemo } from "react"; +import { unixNow } from "@snort/shared"; +import { useMemo } from "react"; import uniqBy from "lodash.uniqby"; import { Icon } from "element/icon"; @@ -18,13 +19,12 @@ import { useLiveChatFeed } from "hooks/live-chat"; import { useMutedPubkeys } from "hooks/lists"; import { useBadges } from "hooks/badges"; import { useLogin } from "hooks/login"; -import useTopZappers from "hooks/top-zappers"; import { useAddress } from "hooks/event"; import { formatSats } from "number"; import { WEEK, LIVE_STREAM_CHAT } from "const"; import { findTag, getTagValues, getHost } from "utils"; import { System } from "index"; -import { FormattedMessage } from "react-intl"; +import { TopZappers } from "element/top-zappers"; export interface LiveChatOptions { canWrite?: boolean; @@ -49,28 +49,6 @@ function BadgeAward({ ev }: { ev: NostrEvent }) { ); } -function TopZappers({ zaps }: { zaps: ParsedZap[] }) { - const zappers = useTopZappers(zaps); - - return ( - <> - {zappers.map(({ pubkey, total }) => { - return ( -
- {pubkey === "anon" ? ( -

Anon

- ) : ( - - )} - -

{formatSats(total)}

-
- ); - })} - - ); -} - export function LiveChat({ link, ev, @@ -87,11 +65,6 @@ export function LiveChat({ const host = getHost(ev); const feed = useLiveChatFeed(link, goal ? [goal.id] : undefined); const login = useLogin(); - useEffect(() => { - const pubkeys = [...new Set(feed.zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))]; - System.ProfileLoader.TrackMetadata(pubkeys); - return () => System.ProfileLoader.UntrackMetadata(pubkeys); - }, [feed.zaps]); const started = useMemo(() => { const starts = findTag(ev, "starts"); return starts ? Number(starts) : unixNow() - WEEK; @@ -111,7 +84,6 @@ export function LiveChat({ const events = useMemo(() => { return [...feed.messages, ...feed.zaps, ...awards].sort((a, b) => b.created_at - a.created_at); }, [feed.messages, feed.zaps, awards]); - const streamer = getHost(ev); const naddr = useMemo(() => { if (ev) { return encodeTLV(NostrPrefix.Address, findTag(ev, "d") ?? "", undefined, ev.kind, ev.pubkey); @@ -145,7 +117,7 @@ export function LiveChat({ {goal && } - {login?.pubkey === streamer && } + {login?.pubkey === host && } )}
@@ -159,7 +131,7 @@ export function LiveChat({ b.id === a.id && b.receiver === streamer); + const zap = zaps.find(b => b.id === a.id && b.receiver === host); if (zap) { return ; } diff --git a/src/element/top-zappers.tsx b/src/element/top-zappers.tsx new file mode 100644 index 0000000..6003648 --- /dev/null +++ b/src/element/top-zappers.tsx @@ -0,0 +1,27 @@ +import { ParsedZap } from "@snort/system"; +import useTopZappers from "hooks/top-zappers"; +import { formatSats } from "number"; +import { Icon } from "./icon"; +import { Profile } from "./profile"; + +export function TopZappers({ zaps, limit }: { zaps: ParsedZap[], limit?: number }) { + const zappers = useTopZappers(zaps); + + return ( + <> + {zappers.slice(0, limit ?? 10).map(({ pubkey, total }) => { + return ( +
+ {pubkey === "anon" ? ( +

Anon

+ ) : ( + + )} + +

{formatSats(total)}

+
+ ); + })} + + ); +} \ No newline at end of file diff --git a/src/hooks/goals.ts b/src/hooks/goals.ts index 6fe1b12..76d067e 100644 --- a/src/hooks/goals.ts +++ b/src/hooks/goals.ts @@ -1,30 +1,8 @@ import { useMemo } from "react"; -import { - EventKind, - NostrEvent, - RequestBuilder, - NoteCollection, - ReplaceableNoteStore, - NostrLink, - parseZap, -} from "@snort/system"; +import { RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system"; import { useRequestBuilder } from "@snort/system-react"; import { unwrap } from "@snort/shared"; import { GOAL } from "const"; -import { System } from "index"; - -export function useZaps(goal: NostrEvent, leaveOpen = false) { - const sub = useMemo(() => { - const b = new RequestBuilder(`goal-zaps:${goal.id.slice(0, 12)}`); - b.withOptions({ leaveOpen }); - b.withFilter().kinds([EventKind.ZapReceipt]).tag("e", [goal.id]).since(goal.created_at); - return b; - }, [goal, leaveOpen]); - - const { data } = useRequestBuilder(NoteCollection, sub); - - return data?.map(ev => parseZap(ev, System.ProfileLoader.Cache)).filter(z => z && z.valid) ?? []; -} export function useZapGoal(host: string, link?: NostrLink, leaveOpen = false) { const sub = useMemo(() => { diff --git a/src/hooks/live-chat.tsx b/src/hooks/live-chat.tsx index 9e7b790..0a33353 100644 --- a/src/hooks/live-chat.tsx +++ b/src/hooks/live-chat.tsx @@ -1,10 +1,12 @@ import { NostrLink, RequestBuilder, EventKind, NoteCollection } from "@snort/system"; -import { useRequestBuilder } from "@snort/system-react"; -import { unixNow } from "@snort/shared"; -import { useMemo } from "react"; +import { SnortContext, useRequestBuilder } from "@snort/system-react"; +import { unixNow, unwrap } from "@snort/shared"; +import { useContext, useEffect, useMemo } from "react"; import { LIVE_STREAM_CHAT, WEEK } from "const"; +import { findTag } from "utils"; export function useLiveChatFeed(link: NostrLink, eZaps?: Array) { + const system = useContext(SnortContext); const since = useMemo(() => unixNow() - WEEK, [link.id]); const sub = useMemo(() => { const rb = new RequestBuilder(`live:${link.id}:${link.author}`); @@ -44,6 +46,12 @@ export function useLiveChatFeed(link: NostrLink, eZaps?: Array) { return rb; }, [etags]); + useEffect(() => { + const pubkeys = [...new Set(zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))]; + system.ProfileLoader.TrackMetadata(pubkeys); + return () => system.ProfileLoader.UntrackMetadata(pubkeys); + }, [zaps]); + const reactionsSub = useRequestBuilder(NoteCollection, esub); const reactions = reactionsSub.data ?? []; diff --git a/src/hooks/stream-link.ts b/src/hooks/stream-link.ts new file mode 100644 index 0000000..fcc6ae7 --- /dev/null +++ b/src/hooks/stream-link.ts @@ -0,0 +1,30 @@ +import { fetchNip05Pubkey, hexToBech32 } from "@snort/shared"; +import { NostrLink, tryParseNostrLink, NostrPrefix } from "@snort/system"; +import { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; + +export function useStreamLink() { + const params = useParams(); + const [link, setLink] = useState(); + + useEffect(() => { + if (params.id) { + const parsedLink = tryParseNostrLink(params.id); + if (parsedLink) { + setLink(parsedLink); + } else { + const [handle, domain] = (params.id.includes("@") ? params.id : `${params.id}@zap.stream`).split("@"); + fetchNip05Pubkey(handle, domain).then(d => { + if (d) { + setLink({ + id: d, + type: NostrPrefix.PublicKey, + encode: () => hexToBech32(NostrPrefix.PublicKey, d), + } as NostrLink); + } + }); + } + } + }, [params.id]); + return link; +} diff --git a/src/hooks/zaps.ts b/src/hooks/zaps.ts new file mode 100644 index 0000000..8995278 --- /dev/null +++ b/src/hooks/zaps.ts @@ -0,0 +1,40 @@ +import { unwrap } from "@snort/shared"; +import { NostrLink, RequestBuilder, NostrPrefix, EventKind, NoteCollection, parseZap } from "@snort/system"; +import { SnortContext, useRequestBuilder } from "@snort/system-react"; +import { System } from "index"; +import { useContext, useMemo, useEffect } from "react"; +import { findTag } from "utils"; + +export function useZaps(link?: NostrLink, leaveOpen = false) { + const system = useContext(SnortContext); + const sub = useMemo(() => { + if (link) { + const b = new RequestBuilder(`zaps:${link.id}`); + b.withOptions({ leaveOpen }); + if (link.type === NostrPrefix.Event || link.type === NostrPrefix.Note) { + b.withFilter().kinds([EventKind.ZapReceipt]).tag("e", [link.id]); + } else if (link.type === NostrPrefix.Address) { + b.withFilter() + .kinds([EventKind.ZapReceipt]) + .tag("a", [`${link.kind}:${link.author}:${link.id}`]); + } + return b; + } + return null; + }, [link, leaveOpen]); + + const { data: zaps } = useRequestBuilder(NoteCollection, sub); + + useEffect(() => { + const pubkeys = zaps ? [...new Set(zaps.flatMap(a => [a.pubkey, unwrap(findTag(a, "p"))]))] : []; + system.ProfileLoader.TrackMetadata(pubkeys); + return () => system.ProfileLoader.UntrackMetadata(pubkeys); + }, [zaps]); + + return ( + [...(zaps ?? [])] + .sort((a, b) => (b.created_at > a.created_at ? 1 : -1)) + .map(ev => parseZap(ev, System.ProfileLoader.Cache)) + .filter(z => z && z.valid) ?? [] + ); +} diff --git a/src/index.css b/src/index.css index 5549569..da04b4a 100644 --- a/src/index.css +++ b/src/index.css @@ -76,6 +76,10 @@ a { color: white; } +.g8 { + gap: 8px; +} + .g24 { gap: 24px; } diff --git a/src/index.tsx b/src/index.tsx index 635e587..af86920 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -20,6 +20,8 @@ import { defaultRelays } from "const"; import { CatchAllRoutePage } from "pages/catch-all"; import { register } from "serviceWorker"; import { IntlProvider } from "intl"; +import { WidgetsPage } from "pages/widgets"; +import { AlertsPage } from "pages/alerts"; export enum StreamState { Live = "live", @@ -65,6 +67,10 @@ const router = createBrowserRouter([ path: "/providers/:id?", element: , }, + { + path: "/widgets", + element: + }, { path: "*", element: , @@ -74,7 +80,19 @@ const router = createBrowserRouter([ { path: "/chat/:id", element: , + loader: async () => { + await System.Init(); + return null; + }, }, + { + path: "/alert/:id/:type", + element: , + loader: async () => { + await System.Init(); + return null; + }, + } ]); const root = ReactDOM.createRoot(document.getElementById("root") as HTMLDivElement); root.render( diff --git a/src/lang.json b/src/lang.json index f291da5..04ae068 100644 --- a/src/lang.json +++ b/src/lang.json @@ -143,6 +143,9 @@ "QceMQZ": { "defaultMessage": "Goal: {amount}" }, + "Qe1MJu": { + "defaultMessage": "{name} with {amount}" + }, "RJOmzk": { "defaultMessage": "I have read and agree with {provider}''s {terms}." }, @@ -194,6 +197,9 @@ "hGQqkW": { "defaultMessage": "Schedule" }, + "hpl4BP": { + "defaultMessage": "Chat Widget" + }, "ieGrWo": { "defaultMessage": "Follow" }, @@ -245,12 +251,18 @@ "rfC1Zq": { "defaultMessage": "Save card" }, + "rgsbu9": { + "defaultMessage": "Current Viewers" + }, "s5ksS7": { "defaultMessage": "Image Link" }, "s7V+5p": { "defaultMessage": "Confirm your age" }, + "tG1ST3": { + "defaultMessage": "Incoming Zap" + }, "thsiMl": { "defaultMessage": "terms and conditions" }, @@ -277,5 +289,8 @@ }, "x82IOl": { "defaultMessage": "Mute" + }, + "zVDHAu": { + "defaultMessage": "Zap Alert" } } diff --git a/src/pages/alerts.css b/src/pages/alerts.css new file mode 100644 index 0000000..75b66b3 --- /dev/null +++ b/src/pages/alerts.css @@ -0,0 +1,78 @@ +.zap-alert-widgets .zap-alert { + animation: cssAnimation 0s ease-in 5s forwards; + animation-fill-mode: forwards; +} + +.zap-alert { + display: inline-flex; + flex-direction: column; + align-items: center; +} + +.zap-alert > div:nth-of-type(1) { + width: fit-content; + font-size: 21px; + font-weight: 600; + border-radius: 33px; + background: linear-gradient(135deg, #882bff 0%, #f83838 100%); + margin: 0; + padding: 8px 24px; + margin-bottom: -11px; + z-index: 2; +} + +.zap-alert > div:nth-of-type(2) { + z-index: 1; + display: inline-flex; + justify-content: center; + align-items: center; + border-radius: 17px; + background: #2d2d2d; + padding: 27px 90px; + font-size: 29px; + font-weight: 600; +} + +.zap-alert .highlight { + color: #ff4468; +} + +@keyframes cssAnimation { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.views { + display: inline-block; + border-radius: 16px; + background: #222; + padding: 16px 24px; + font-size: 21px; + font-weight: 600; + line-height: 32px; +} + +.top-zappers-widget { + display: inline-flex; + border-radius: 16px; + background: #222; + padding: 16px 24px; + font-size: 21px; + font-weight: 600; + align-items: center; + gap: 16px; +} + +.top-zappers-widget .top-zapper { + background-color: #3f3f3f; + border: unset; +} + +.top-zappers-widget .profile > img { + width: 24px; + height: 24px; +} diff --git a/src/pages/alerts.tsx b/src/pages/alerts.tsx new file mode 100644 index 0000000..9164219 --- /dev/null +++ b/src/pages/alerts.tsx @@ -0,0 +1,29 @@ +import "./alerts.css"; +import Spinner from "element/spinner"; +import { useStreamLink } from "hooks/stream-link"; +import { useParams } from "react-router-dom"; +import { ZapAlerts } from "./widgets/zaps"; +import { Views } from "./widgets/views"; +import { TopZappersWidget } from "./widgets/top-zappers"; + +export function AlertsPage() { + const params = useParams(); + const link = useStreamLink(); + + if (!link) { + return + } + + switch (params.type) { + case "zaps": { + return + } + case "views": { + return + } + case "top-zappers": { + return + } + } + return null; +} diff --git a/src/pages/stream-page.tsx b/src/pages/stream-page.tsx index 63e4d28..3c4c824 100644 --- a/src/pages/stream-page.tsx +++ b/src/pages/stream-page.tsx @@ -1,11 +1,10 @@ import "./stream-page.css"; -import { NostrLink, NostrPrefix, TaggedNostrEvent, tryParseNostrLink } from "@snort/system"; -import { fetchNip05Pubkey } from "@snort/shared"; -import { useLocation, useNavigate, useParams } from "react-router-dom"; +import { NostrLink, TaggedNostrEvent } from "@snort/system"; +import { useLocation, useNavigate } from "react-router-dom"; import { Helmet } from "react-helmet"; import { LiveVideoPlayer } from "element/live-video-player"; -import { createNostrLink, findTag, getEventFromLocationState, getHost, hexToBech32 } from "utils"; +import { eventToLink, findTag, getEventFromLocationState, getHost } from "utils"; import { Profile, getName } from "element/profile"; import { LiveChat } from "element/live-chat"; import AsyncButton from "element/async-button"; @@ -24,7 +23,7 @@ import { StreamTimer } from "element/stream-time"; import { ShareMenu } from "element/share-menu"; import { ContentWarningOverlay, isContentWarningAccepted } from "element/content-warning"; import { useCurrentStreamFeed } from "hooks/current-stream-feed"; -import { useEffect, useState } from "react"; +import { useStreamLink } from "hooks/stream-link"; function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent }) { const login = useLogin(); @@ -97,30 +96,9 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent }) } export function StreamPageHandler() { - const params = useParams(); const location = useLocation(); const evPreload = getEventFromLocationState(location.state); - const [link, setLink] = useState(); - - useEffect(() => { - if (params.id) { - const parsedLink = tryParseNostrLink(params.id); - if (parsedLink) { - setLink(parsedLink); - } else { - const [handle, domain] = (params.id.includes("@") ? params.id : `${params.id}@zap.stream`).split("@"); - fetchNip05Pubkey(handle, domain).then(d => { - if (d) { - setLink({ - id: d, - type: NostrPrefix.PublicKey, - encode: () => hexToBech32(NostrPrefix.PublicKey, d), - } as NostrLink); - } - }); - } - } - }, [params.id]); + const link = useStreamLink(); if (link) { return ; @@ -130,7 +108,8 @@ export function StreamPageHandler() { export function StreamPage({ link, evPreload }: { evPreload?: NostrEvent; link: NostrLink }) { const ev = useCurrentStreamFeed(link, true, evPreload); const host = getHost(ev); - const goal = useZapGoal(host, createNostrLink(ev), true); + const evLink = ev ? eventToLink(ev) : undefined; + const goal = useZapGoal(host, evLink, true); const title = findTag(ev, "title"); const summary = findTag(ev, "summary"); @@ -161,7 +140,7 @@ export function StreamPage({ link, evPreload }: { evPreload?: NostrEvent; link:
- + ); } diff --git a/src/pages/widgets.css b/src/pages/widgets.css new file mode 100644 index 0000000..524ab5c --- /dev/null +++ b/src/pages/widgets.css @@ -0,0 +1,10 @@ +.widgets { + display: grid; + grid-template-columns: repeat(4, 1fr); +} + +.widgets > div { + background-color: #3f3f3f; + border-radius: 16px; + padding: 8px 12px; +} \ No newline at end of file diff --git a/src/pages/widgets.tsx b/src/pages/widgets.tsx new file mode 100644 index 0000000..5d627cb --- /dev/null +++ b/src/pages/widgets.tsx @@ -0,0 +1,58 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import "./widgets.css"; +import { NostrPrefix, createNostrLink } from "@snort/system"; +import Copy from "element/copy"; +import { useCurrentStreamFeed } from "hooks/current-stream-feed"; +import { useLogin } from "hooks/login"; +import { FormattedMessage } from "react-intl"; +import { eventToLink, hexToBech32 } from "utils"; +import { ZapAlertItem } from "./widgets/zaps"; +import { TopZappersWidget } from "./widgets/top-zappers"; +import { Views } from "./widgets/views"; + +export function WidgetsPage() { + const login = useLogin(); + const profileLink = createNostrLink(NostrPrefix.PublicKey, login?.pubkey ?? ""); + const current = useCurrentStreamFeed(profileLink); + const currentLink = current ? eventToLink(current) : undefined; + const npub = hexToBech32("npub", login?.pubkey); + + const baseUrl = `${window.location.protocol}//${window.location.host}`; + return
+
+

+ +

+ +
+
+

+ +

+ + +
+
+

+ +

+ + {currentLink && } +
+
+

+ +

+ + {currentLink && } +
+
+} \ No newline at end of file diff --git a/src/pages/widgets/top-zappers.tsx b/src/pages/widgets/top-zappers.tsx new file mode 100644 index 0000000..f6be45e --- /dev/null +++ b/src/pages/widgets/top-zappers.tsx @@ -0,0 +1,19 @@ +import { NostrLink } from "@snort/system"; +import { TopZappers } from "element/top-zappers"; +import { useCurrentStreamFeed } from "hooks/current-stream-feed"; +import { useZaps } from "hooks/zaps"; +import { FormattedMessage } from "react-intl"; +import { eventToLink } from "utils"; + +export function TopZappersWidget({ link }: { link: NostrLink }) { + const currentEvent = useCurrentStreamFeed(link, true); + const zaps = useZaps(currentEvent ? eventToLink(currentEvent) : undefined, true); + return
+
+ +
+
+ +
+
; +} \ No newline at end of file diff --git a/src/pages/widgets/views.tsx b/src/pages/widgets/views.tsx new file mode 100644 index 0000000..828c2d4 --- /dev/null +++ b/src/pages/widgets/views.tsx @@ -0,0 +1,13 @@ +import { NostrLink } from "@snort/system"; +import { useCurrentStreamFeed } from "hooks/current-stream-feed"; +import { FormattedMessage } from "react-intl"; +import { findTag } from "utils"; + +export function Views({ link }: { link: NostrLink }) { + const current = useCurrentStreamFeed(link); + + const viewers = findTag(current, "current_participants"); + return
+ +
+} \ No newline at end of file diff --git a/src/pages/widgets/zaps.tsx b/src/pages/widgets/zaps.tsx new file mode 100644 index 0000000..3d6a2fb --- /dev/null +++ b/src/pages/widgets/zaps.tsx @@ -0,0 +1,34 @@ +import { hexToBech32 } from "@snort/shared"; +import { NostrLink, ParsedZap } from "@snort/system"; +import { useUserProfile } from "@snort/system-react"; +import { useCurrentStreamFeed } from "hooks/current-stream-feed"; +import { useZaps } from "hooks/zaps"; +import { formatSats } from "number"; +import { FormattedMessage } from "react-intl"; +import { eventToLink } from "utils"; + +export function ZapAlerts({ link }: { link: NostrLink }) { + const currentEvent = useCurrentStreamFeed(link, true); + const currentLink = currentEvent ? eventToLink(currentEvent) : undefined; + const zaps = useZaps(currentLink, true); + + return
+ {zaps.slice(0, 5).map(v => )} +
+} + +export function ZapAlertItem({ item }: { item: ParsedZap }) { + const profile = useUserProfile(item.sender); + if (!profile) return; + return
+
+ +
+
+ {profile?.name ?? hexToBech32("npub", item?.sender ?? "").slice(0, 12)} , + amount:  {formatSats(item.amount)} + }} /> +
+
+} \ No newline at end of file diff --git a/src/translations/en.json b/src/translations/en.json index c59dac1..47f9363 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -47,6 +47,7 @@ "QRHNuF": "What are we steaming today?", "QRRCp0": "Stream URL", "QceMQZ": "Goal: {amount}", + "Qe1MJu": "{name} with {amount}", "RJOmzk": "I have read and agree with {provider}''s {terms}.", "RXQdxR": "Please login to write messages!", "RrCui3": "Summary", @@ -64,6 +65,7 @@ "ebmhes": "Nostr Extension", "fBI91o": "Zap", "hGQqkW": "Schedule", + "hpl4BP": "Chat Widget", "ieGrWo": "Follow", "itPgxd": "Profile", "izWS4J": "Unfollow", @@ -81,8 +83,10 @@ "rWBFZA": "Sexually explicit material ahead!", "rbrahO": "Close", "rfC1Zq": "Save card", + "rgsbu9": "Current Viewers", "s5ksS7": "Image Link", "s7V+5p": "Confirm your age", + "tG1ST3": "Incoming Zap", "thsiMl": "terms and conditions", "tzMNF3": "Status", "uYw2LD": "Stream", @@ -91,5 +95,6 @@ "wEQDC6": "Edit", "wOy57k": "Add stream goal", "wzWWzV": "Top zappers", - "x82IOl": "Mute" + "x82IOl": "Mute", + "zVDHAu": "Zap Alert" } \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 7dc194b..00a21fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,9 @@ -import { NostrEvent, NostrPrefix, TaggedNostrEvent, encodeTLV, parseNostrLink } from "@snort/system"; +import { NostrEvent, NostrPrefix, TaggedNostrEvent, createNostrLink, encodeTLV } from "@snort/system"; import * as utils from "@noble/curves/abstract/utils"; import { bech32 } from "@scure/base"; import type { Tag, Tags } from "types"; import { LIVE_STREAM } from "const"; +import { unwrap } from "@snort/shared"; export function toAddress(e: NostrEvent): string { if (e.kind && e.kind >= 30000 && e.kind <= 40000) { @@ -76,11 +77,6 @@ export function eventLink(ev: NostrEvent | TaggedNostrEvent) { } } -export function createNostrLink(ev?: NostrEvent) { - if (!ev) return; - return parseNostrLink(eventLink(ev)); -} - export function getHost(ev?: NostrEvent) { return ev?.tags.find(a => a[0] === "p" && a[3] === "host")?.[1] ?? ev?.pubkey ?? ""; } @@ -114,3 +110,11 @@ export function getEventFromLocationState(state: unknown | undefined | null) { ? (state as NostrEvent) : undefined; } + +export function eventToLink(ev: NostrEvent) { + if (ev.kind >= 30_000 && ev.kind < 40_000) { + const dTag = unwrap(findTag(ev, "d")); + return createNostrLink(NostrPrefix.Address, dTag, undefined, ev.kind, ev.pubkey); + } + return createNostrLink(NostrPrefix.Event, ev.id, undefined, ev.kind, ev.pubkey); +} diff --git a/webpack.config.js b/webpack.config.js index 5a6793b..7274007 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -62,9 +62,11 @@ const config = { __XXX: process.env["__XXX"] || JSON.stringify(false), __XXX_HOST: JSON.stringify("https://xxzap.com"), }), - new WorkboxPlugin.InjectManifest({ - swSrc: "./src/service-worker.ts", - }), + isProduction + ? new WorkboxPlugin.InjectManifest({ + swSrc: "./src/service-worker.ts", + }) + : false, ], module: { rules: [