forked from Kieran/zap.stream
TOS for stream providers
This commit is contained in:
parent
786631e45e
commit
508abaad1f
@ -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;
|
||||||
|
}
|
@ -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,
|
||||||
}}
|
}}
|
||||||
/>
|
/>}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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("");
|
||||||
|
@ -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> = [];
|
||||||
|
|
||||||
|
@ -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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
Loading…
Reference in New Issue
Block a user