TOS for stream providers

This commit is contained in:
Kieran 2023-08-01 15:51:07 +01:00
parent 786631e45e
commit 508abaad1f
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
7 changed files with 98 additions and 25 deletions

View File

@ -45,3 +45,12 @@
color: inherit; color: inherit;
background: #353535; background: #353535;
} }
.new-stream .tos-link {
cursor: pointer;
color: var(--text-link);
}
.new-stream .tos-link:hover {
text-decoration: underline;
}

View File

@ -8,17 +8,7 @@ import { useEffect, useState } from "react";
import { SendZaps } from "./send-zap"; import { SendZaps } from "./send-zap";
import { StreamEditor, StreamEditorProps } from "./stream-editor"; import { StreamEditor, StreamEditorProps } from "./stream-editor";
import Spinner from "./spinner"; import Spinner from "./spinner";
import { LIVE_STREAM } from "const"; import AsyncButton from "./async-button";
const DummyEvent = {
content: "",
id: "",
pubkey: "",
sig: "",
kind: LIVE_STREAM,
created_at: 0,
tags: [],
} as NostrEvent;
export function NostrProviderDialog({ export function NostrProviderDialog({
provider, provider,
@ -27,6 +17,7 @@ export function NostrProviderDialog({
const [topup, setTopup] = useState(false); const [topup, setTopup] = useState(false);
const [info, setInfo] = useState<StreamProviderInfo>(); const [info, setInfo] = useState<StreamProviderInfo>();
const [ep, setEndpoint] = useState<StreamProviderEndpoint>(); const [ep, setEndpoint] = useState<StreamProviderEndpoint>();
const [tos, setTos] = useState(false);
function sortEndpoints(arr: Array<StreamProviderEndpoint>) { function sortEndpoints(arr: Array<StreamProviderEndpoint>) {
return arr.sort((a, b) => ((a.rate ?? 0) > (b.rate ?? 0) ? -1 : 1)); return arr.sort((a, b) => ((a.rate ?? 0) > (b.rate ?? 0) ? -1 : 1));
@ -35,6 +26,7 @@ export function NostrProviderDialog({
useEffect(() => { useEffect(() => {
provider.info().then((v) => { provider.info().then((v) => {
setInfo(v); setInfo(v);
setTos(v.tosAccepted ?? true);
setEndpoint(sortEndpoints(v.endpoints)[0]); setEndpoint(sortEndpoints(v.endpoints)[0]);
}); });
}, [provider]); }, [provider]);
@ -87,7 +79,37 @@ export function NostrProviderDialog({
return cap; 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 <>
<div>
<div className="flex g12">
<input type="checkbox" checked={tos} onChange={e => setTos(e.target.checked)} />
<p>
I have read and agree with {info.name}'s <span className="tos-link" onClick={() => window.open(info.tosLink, "popup", "width=400,height=800")}>terms and conditions</span>.
</p>
</div>
</div>
<div>
<AsyncButton
type="button"
className="btn btn-primary wide"
disabled={!tos}
onClick={acceptTos}
>
Continue
</AsyncButton>
</div>
</>
}
return ( return (
<> <>
{info.endpoints.length > 1 && ( {info.endpoints.length > 1 && (
@ -145,19 +167,26 @@ export function NostrProviderDialog({
))} ))}
</div> </div>
</div> </div>
{streamEvent && ( {info.tosAccepted === false ? tosInput() :
<StreamEditor <StreamEditor
onFinish={(ex) => { onFinish={(ex) => {
provider.updateStreamInfo(ex); provider.updateStreamInfo(ex);
others.onFinish?.(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={{ options={{
canSetStream: false, canSetStream: false,
canSetStatus: false, canSetStatus: false,
}} }}
/> />}
)}
</> </>
); );
} }

View File

@ -5,7 +5,7 @@ import AsyncButton from "element/async-button";
import { StatePill } from "element/state-pill"; import { StatePill } from "element/state-pill";
import { StreamState } from "index"; import { StreamState } from "index";
import { StreamProviderInfo, StreamProviderStore } from "providers"; import { StreamProviderInfo, StreamProviderStore } from "providers";
import { Nip103StreamProvider } from "providers/nip103"; import { Nip103StreamProvider } from "providers/zsz";
export function ConfigureNostrType() { export function ConfigureNostrType() {
const [url, setUrl] = useState(""); const [url, setUrl] = useState("");

View File

@ -1,7 +1,7 @@
import { StreamState } from "index"; import { StreamState } from "index";
import { NostrEvent } from "@snort/system"; import { NostrEvent } from "@snort/system";
import { ExternalStore } from "@snort/shared"; import { ExternalStore } from "@snort/shared";
import { Nip103StreamProvider } from "./nip103"; import { Nip103StreamProvider } from "./zsz";
import { ManualProvider } from "./manual"; import { ManualProvider } from "./manual";
import { OwncastProvider } from "./owncast"; import { OwncastProvider } from "./owncast";
@ -28,6 +28,11 @@ export interface StreamProvider {
* Top-up balance with provider * Top-up balance with provider
*/ */
topup(amount: number): Promise<string>; topup(amount: number): Promise<string>;
/**
* Accept TOS of the streaming provider
*/
acceptTos(): Promise<void>;
} }
export enum StreamProviders { export enum StreamProviders {
@ -44,8 +49,11 @@ export interface StreamProviderInfo {
state: StreamState; state: StreamState;
viewers?: number; viewers?: number;
publishedEvent?: NostrEvent; publishedEvent?: NostrEvent;
streamInfo?: StreamProviderStreamInfo;
balance?: number; balance?: number;
endpoints: Array<StreamProviderEndpoint>; endpoints: Array<StreamProviderEndpoint>;
tosAccepted?: boolean;
tosLink?: string
} }
export interface StreamProviderEndpoint { export interface StreamProviderEndpoint {
@ -57,6 +65,14 @@ export interface StreamProviderEndpoint {
capabilities?: Array<string>; capabilities?: Array<string>;
} }
export interface StreamProviderStreamInfo {
title: string
summary: string
image: string
tags: Array<string>
content_warning: string
}
export class ProviderStore extends ExternalStore<Array<StreamProvider>> { export class ProviderStore extends ExternalStore<Array<StreamProvider>> {
#providers: Array<StreamProvider> = []; #providers: Array<StreamProvider> = [];

View File

@ -31,4 +31,8 @@ export class ManualProvider implements StreamProvider {
topup(): Promise<string> { topup(): Promise<string> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
acceptTos(): Promise<void> {
throw new Error("Method not implemented.");
}
} }

View File

@ -48,6 +48,10 @@ export class OwncastProvider implements StreamProvider {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
acceptTos(): Promise<void> {
throw new Error("Method not implemented.");
}
async #getJson<T>( async #getJson<T>(
method: "GET" | "POST", method: "GET" | "POST",
path: string, path: string,

View File

@ -2,10 +2,11 @@ import {
StreamProvider, StreamProvider,
StreamProviderEndpoint, StreamProviderEndpoint,
StreamProviderInfo, StreamProviderInfo,
StreamProviderStreamInfo,
StreamProviders, StreamProviders,
} from "."; } from ".";
import { EventKind, NostrEvent } from "@snort/system"; import { EventKind, NostrEvent } from "@snort/system";
import { Login } from "index"; import { Login, StreamState } from "index";
import { getPublisher } from "login"; import { getPublisher } from "login";
import { findTag } from "utils"; import { findTag } from "utils";
@ -26,15 +27,15 @@ export class Nip103StreamProvider implements StreamProvider {
async info() { async info() {
const rsp = await this.#getJson<AccountResponse>("GET", "account"); const rsp = await this.#getJson<AccountResponse>("GET", "account");
const title = findTag(rsp.event, "title");
const state = findTag(rsp.event, "status");
return { return {
type: StreamProviders.NostrType, type: StreamProviders.NostrType,
name: title ?? "", name: this.name,
state: state, state: StreamState.Planned,
viewers: 0, viewers: 0,
publishedEvent: rsp.event, streamInfo: rsp.event,
balance: rsp.balance, balance: rsp.balance,
tosAccepted: rsp.tos?.accepted,
tosLink: rsp.tos?.link,
endpoints: rsp.endpoints.map((a) => { endpoints: rsp.endpoints.map((a) => {
return { return {
name: a.name, name: a.name,
@ -78,6 +79,12 @@ export class Nip103StreamProvider implements StreamProvider {
return rsp.pr; return rsp.pr;
} }
async acceptTos(): Promise<void> {
await this.#getJson("PATCH", "account", {
accept_tos: true,
});
}
async #getJson<T>( async #getJson<T>(
method: "GET" | "POST" | "PATCH", method: "GET" | "POST" | "PATCH",
path: string, path: string,
@ -113,8 +120,12 @@ export class Nip103StreamProvider implements StreamProvider {
interface AccountResponse { interface AccountResponse {
balance: number; balance: number;
event?: NostrEvent; event?: StreamProviderStreamInfo;
endpoints: Array<IngestEndpoint>; endpoints: Array<IngestEndpoint>;
tos?: {
accepted: boolean;
link: string;
};
} }
interface IngestEndpoint { interface IngestEndpoint {