-
+
-
+
),
}}
@@ -219,7 +218,7 @@ export function LoginSignup({ close }: { close: () => void }) {
type="text"
value={key}
onChange={e => setNewKey(e.target.value)}
- placeholder={formatMessage({ defaultMessage: "eg. nsec1xyz", id: "yzKwBQ" })}
+ placeholder={formatMessage({ defaultMessage: "eg. nsec1xyz" })}
/>
@@ -244,10 +243,10 @@ export function LoginSignup({ close }: { close: () => void }) {
case Stage.Details: {
return (
<>
-
![]({LoginProfile)
+
-
+
{avatar &&
![]({avatar})
}
@@ -261,16 +260,15 @@ export function LoginSignup({ close }: { close: () => void }) {
type="text"
placeholder={formatMessage({
defaultMessage: "Username",
- id: "JCIgkj",
})}
value={username}
onChange={e => setUsername(e.target.value)}
/>
-
+
-
+
{error &&
{error}}
@@ -280,22 +278,18 @@ export function LoginSignup({ close }: { close: () => void }) {
case Stage.LnAddress: {
return (
<>
-
![]({LoginWallet)
+
-
+
-
+
{providerInfo?.balance && (
,
}}
@@ -304,16 +298,16 @@ export function LoginSignup({ close }: { close: () => void }) {
)}
setLnAddress(e.target.value)}
/>
-
+
{error && {error}}
-
+
>
@@ -322,7 +316,7 @@ export function LoginSignup({ close }: { close: () => void }) {
case Stage.SaveKey: {
return (
<>
-
![]({LoginKey)
+
diff --git a/src/element/modal.tsx b/src/element/modal.tsx
index 9ae54b5..ff164a4 100644
--- a/src/element/modal.tsx
+++ b/src/element/modal.tsx
@@ -10,6 +10,7 @@ export interface ModalProps {
onClose?: (e: React.MouseEvent | KeyboardEvent) => void;
onClick?: (e: React.MouseEvent) => void;
children: ReactNode;
+ showClose?: boolean;
}
let scrollbarWidth: number | null = null;
@@ -81,17 +82,19 @@ export default function Modal(props: ModalProps) {
e.stopPropagation();
props.onClick?.(e);
}}>
-
- {
- e.stopPropagation();
- props.onClose?.(e);
- }}
- className="rounded-full aspect-square bg-layer-2 p-3"
- iconSize={10}
- />
-
+ {(props.showClose ?? true) && (
+
+ {
+ e.stopPropagation();
+ props.onClose?.(e);
+ }}
+ className="rounded-full aspect-square bg-layer-2 p-3"
+ iconSize={10}
+ />
+
+ )}
{props.children}
,
diff --git a/src/element/provider/nostr/fowards.tsx b/src/element/provider/nostr/fowards.tsx
new file mode 100644
index 0000000..46905df
--- /dev/null
+++ b/src/element/provider/nostr/fowards.tsx
@@ -0,0 +1,169 @@
+import { DefaultButton } from "@/element/buttons";
+import { NostrStreamProvider } from "@/providers";
+import { unwrap } from "@snort/shared";
+import { useState } from "react";
+import { useIntl, FormattedMessage } from "react-intl";
+
+enum ForwardService {
+ Custom = "custom",
+ Twitch = "twitch",
+ Youtube = "youtube",
+ Facebook = "facebook",
+ Kick = "kick",
+ Trovo = "trovo",
+}
+
+export function AddForwardInputs({
+ provider,
+ onAdd,
+}: {
+ provider: NostrStreamProvider;
+ onAdd: (name: string, target: string) => void;
+}) {
+ const [name, setName] = useState("");
+ const [target, setTarget] = useState("");
+ const [svc, setService] = useState(ForwardService.Twitch);
+ const [error, setError] = useState("");
+ const { formatMessage } = useIntl();
+
+ async function getTargetFull() {
+ if (svc === ForwardService.Custom) {
+ return target;
+ }
+
+ if (svc === ForwardService.Twitch) {
+ const urls = (await (await fetch("https://ingest.twitch.tv/ingests")).json()) as {
+ ingests: Array<{
+ availability: number;
+ name: string;
+ url_template: string;
+ }>;
+ };
+
+ const ingestsEurope = urls.ingests.filter(
+ a => a.name.toLowerCase().startsWith("europe:") && a.availability === 1
+ );
+ const random = ingestsEurope.at(ingestsEurope.length * Math.random());
+ return unwrap(random).url_template.replace("{stream_key}", target);
+ }
+
+ if (svc === ForwardService.Youtube) {
+ return `rtmp://a.rtmp.youtube.com:1935/live2/${target}`;
+ }
+
+ if (svc === ForwardService.Facebook) {
+ return `rtmps://live-api-s.facebook.com:443/rtmp/${target}`;
+ }
+
+ if (svc === ForwardService.Trovo) {
+ return `rtmp://livepush.trovo.live:1935/live/${target}`;
+ }
+
+ if (svc === ForwardService.Kick) {
+ return `rtmps://fa723fc1b171.global-contribute.live-video.net:443/app/${target}`;
+ }
+ }
+
+ async function doAdd() {
+ if (svc === ForwardService.Custom) {
+ if (!target.startsWith("rtmp://")) {
+ setError(
+ formatMessage({
+ defaultMessage: "Stream url must start with rtmp://",
+ id: "7+bCC1",
+ })
+ );
+ return;
+ }
+ try {
+ // stupid URL parser doesnt work for non-http protocols
+ const u = new URL(target.replace("rtmp://", "http://"));
+ console.debug(u);
+ if (u.host.length < 1) {
+ throw new Error();
+ }
+ if (u.pathname === "/") {
+ throw new Error();
+ }
+ } catch {
+ setError(
+ formatMessage({
+ defaultMessage: "Not a valid URL",
+ id: "1q4BO/",
+ })
+ );
+ return;
+ }
+ } else {
+ if (target.length < 2) {
+ setError(
+ formatMessage({
+ defaultMessage: "Stream Key is required",
+ id: "50+/JW",
+ })
+ );
+ return;
+ }
+ }
+ if (name.length < 2) {
+ setError(
+ formatMessage({
+ defaultMessage: "Name is required",
+ id: "Gvxoji",
+ })
+ );
+ return;
+ }
+
+ try {
+ const t = await getTargetFull();
+ if (!t)
+ throw new Error(
+ formatMessage({
+ defaultMessage: "Could not create stream URL",
+ id: "E9APoR",
+ })
+ );
+ await provider.addForward(name, t);
+ } catch (e) {
+ setError((e as Error).message);
+ }
+ setName("");
+ setTarget("");
+ onAdd(name, target);
+ }
+
+ return (
+
+
+
+ setName(e.target.value)}
+ />
+
+
setTarget(e.target.value)}
+ />
+
+
+
+ {error &&
{error}}
+
+ );
+}
diff --git a/src/element/provider/nostr/index.tsx b/src/element/provider/nostr/index.tsx
index b1bfe2a..b00b60a 100644
--- a/src/element/provider/nostr/index.tsx
+++ b/src/element/provider/nostr/index.tsx
@@ -1,16 +1,18 @@
import { NostrEvent } from "@snort/system";
import { useContext, useEffect, useState } from "react";
-import { FormattedMessage, useIntl } from "react-intl";
+import { FormattedMessage } from "react-intl";
import { SnortContext } from "@snort/system-react";
import { NostrStreamProvider, StreamProviderEndpoint, StreamProviderInfo } from "@/providers";
import { SendZaps } from "@/element/send-zap";
import { StreamEditor, StreamEditorProps } from "@/element/stream-editor";
import Spinner from "@/element/spinner";
-import { unwrap } from "@snort/shared";
import { useRates } from "@/hooks/rates";
import { DefaultButton } from "@/element/buttons";
import Pill from "@/element/pill";
+import { AddForwardInputs } from "./fowards";
+import StreamKey from "./stream-key";
+import AccountTopup from "./topup";
export default function NostrProviderDialog({
provider,
@@ -164,23 +166,7 @@ export default function NostrProviderDialog({
)}
-