diff --git a/src/element/new-stream.css b/src/element/new-stream.css index 4076643..53bf2f2 100644 --- a/src/element/new-stream.css +++ b/src/element/new-stream.css @@ -45,3 +45,12 @@ color: inherit; background: #353535; } + +.new-stream .tos-link { + cursor: pointer; + color: var(--text-link); +} + +.new-stream .tos-link:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/src/element/nostr-provider-dialog.tsx b/src/element/nostr-provider-dialog.tsx index 8bf5985..9364462 100644 --- a/src/element/nostr-provider-dialog.tsx +++ b/src/element/nostr-provider-dialog.tsx @@ -8,17 +8,7 @@ import { useEffect, useState } from "react"; import { SendZaps } from "./send-zap"; import { StreamEditor, StreamEditorProps } from "./stream-editor"; import Spinner from "./spinner"; -import { LIVE_STREAM } from "const"; - -const DummyEvent = { - content: "", - id: "", - pubkey: "", - sig: "", - kind: LIVE_STREAM, - created_at: 0, - tags: [], -} as NostrEvent; +import AsyncButton from "./async-button"; export function NostrProviderDialog({ provider, @@ -27,6 +17,7 @@ export function NostrProviderDialog({ const [topup, setTopup] = useState(false); const [info, setInfo] = useState(); const [ep, setEndpoint] = useState(); + const [tos, setTos] = useState(false); function sortEndpoints(arr: Array) { return arr.sort((a, b) => ((a.rate ?? 0) > (b.rate ?? 0) ? -1 : 1)); @@ -35,6 +26,7 @@ export function NostrProviderDialog({ useEffect(() => { provider.info().then((v) => { setInfo(v); + setTos(v.tosAccepted ?? true); setEndpoint(sortEndpoints(v.endpoints)[0]); }); }, [provider]); @@ -87,7 +79,37 @@ export function NostrProviderDialog({ return cap; } - const streamEvent = others.ev ?? info.publishedEvent ?? DummyEvent; + async function acceptTos() { + await provider.acceptTos(); + const i = await provider.info(); + setInfo(i); + } + + function tosInput() { + if (!info) return; + + return <> +
+
+ setTos(e.target.checked)} /> +

+ I have read and agree with {info.name}'s window.open(info.tosLink, "popup", "width=400,height=800")}>terms and conditions. +

+
+
+
+ + Continue + +
+ + } + return ( <> {info.endpoints.length > 1 && ( @@ -145,19 +167,26 @@ export function NostrProviderDialog({ ))} - {streamEvent && ( + {info.tosAccepted === false ? tosInput() : { provider.updateStreamInfo(ex); others.onFinish?.(ex); }} - ev={streamEvent} + ev={{ + tags: [ + ["title", info.streamInfo?.title ?? ""], + ["summary", info.streamInfo?.summary ?? ""], + ["image", info.streamInfo?.image ?? ""], + ...(info.streamInfo?.content_warning ? [["content-warning", info.streamInfo?.content_warning]] : []), + ...(info.streamInfo?.tags?.map(a => ["t", a]) ?? []) + ] + } as NostrEvent} options={{ canSetStream: false, canSetStatus: false, }} - /> - )} + />} ); } diff --git a/src/pages/providers/nostr.tsx b/src/pages/providers/nostr.tsx index 355eb97..0867140 100644 --- a/src/pages/providers/nostr.tsx +++ b/src/pages/providers/nostr.tsx @@ -5,7 +5,7 @@ import AsyncButton from "element/async-button"; import { StatePill } from "element/state-pill"; import { StreamState } from "index"; import { StreamProviderInfo, StreamProviderStore } from "providers"; -import { Nip103StreamProvider } from "providers/nip103"; +import { Nip103StreamProvider } from "providers/zsz"; export function ConfigureNostrType() { const [url, setUrl] = useState(""); diff --git a/src/providers/index.ts b/src/providers/index.ts index 040b67a..5fc85ad 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,7 +1,7 @@ import { StreamState } from "index"; import { NostrEvent } from "@snort/system"; import { ExternalStore } from "@snort/shared"; -import { Nip103StreamProvider } from "./nip103"; +import { Nip103StreamProvider } from "./zsz"; import { ManualProvider } from "./manual"; import { OwncastProvider } from "./owncast"; @@ -28,6 +28,11 @@ export interface StreamProvider { * Top-up balance with provider */ topup(amount: number): Promise; + + /** + * Accept TOS of the streaming provider + */ + acceptTos(): Promise; } export enum StreamProviders { @@ -44,8 +49,11 @@ export interface StreamProviderInfo { state: StreamState; viewers?: number; publishedEvent?: NostrEvent; + streamInfo?: StreamProviderStreamInfo; balance?: number; endpoints: Array; + tosAccepted?: boolean; + tosLink?: string } export interface StreamProviderEndpoint { @@ -57,6 +65,14 @@ export interface StreamProviderEndpoint { capabilities?: Array; } +export interface StreamProviderStreamInfo { + title: string + summary: string + image: string + tags: Array + content_warning: string +} + export class ProviderStore extends ExternalStore> { #providers: Array = []; diff --git a/src/providers/manual.ts b/src/providers/manual.ts index a575f01..2459718 100644 --- a/src/providers/manual.ts +++ b/src/providers/manual.ts @@ -31,4 +31,8 @@ export class ManualProvider implements StreamProvider { topup(): Promise { throw new Error("Method not implemented."); } + + acceptTos(): Promise { + throw new Error("Method not implemented."); + } } diff --git a/src/providers/owncast.ts b/src/providers/owncast.ts index 3a0cde1..f2da89d 100644 --- a/src/providers/owncast.ts +++ b/src/providers/owncast.ts @@ -48,6 +48,10 @@ export class OwncastProvider implements StreamProvider { throw new Error("Method not implemented."); } + acceptTos(): Promise { + throw new Error("Method not implemented."); + } + async #getJson( method: "GET" | "POST", path: string, diff --git a/src/providers/nip103.ts b/src/providers/zsz.ts similarity index 86% rename from src/providers/nip103.ts rename to src/providers/zsz.ts index fd6e835..a84bcdc 100644 --- a/src/providers/nip103.ts +++ b/src/providers/zsz.ts @@ -2,10 +2,11 @@ import { StreamProvider, StreamProviderEndpoint, StreamProviderInfo, + StreamProviderStreamInfo, StreamProviders, } from "."; import { EventKind, NostrEvent } from "@snort/system"; -import { Login } from "index"; +import { Login, StreamState } from "index"; import { getPublisher } from "login"; import { findTag } from "utils"; @@ -26,15 +27,15 @@ export class Nip103StreamProvider implements StreamProvider { async info() { const rsp = await this.#getJson("GET", "account"); - const title = findTag(rsp.event, "title"); - const state = findTag(rsp.event, "status"); return { type: StreamProviders.NostrType, - name: title ?? "", - state: state, + name: this.name, + state: StreamState.Planned, viewers: 0, - publishedEvent: rsp.event, + streamInfo: rsp.event, balance: rsp.balance, + tosAccepted: rsp.tos?.accepted, + tosLink: rsp.tos?.link, endpoints: rsp.endpoints.map((a) => { return { name: a.name, @@ -78,6 +79,12 @@ export class Nip103StreamProvider implements StreamProvider { return rsp.pr; } + async acceptTos(): Promise { + await this.#getJson("PATCH", "account", { + accept_tos: true, + }); + } + async #getJson( method: "GET" | "POST" | "PATCH", path: string, @@ -113,8 +120,12 @@ export class Nip103StreamProvider implements StreamProvider { interface AccountResponse { balance: number; - event?: NostrEvent; + event?: StreamProviderStreamInfo; endpoints: Array; + tos?: { + accepted: boolean; + link: string; + }; } interface IngestEndpoint {