hermes-web/components/elements/connection.tsx

223 lines
8.1 KiB
TypeScript
Raw Normal View History

"use client";
import axios from "axios";
import { useState } from "react";
import { Button } from "../ui/button";
import { useRouter } from "next/navigation";
import { v4 } from "uuid";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Input } from "../ui/input";
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
import { env } from "process";
export interface Connection {
name: string
type: string
clientId: string
scope: string
expiresAt: Date
remover: (name: string) => void
}
const AUTHORIZATION_DATA: { [service: string]: { type: string, endpoint: string, grantType: string, scopes: string[], redirect: string } } = {
'nightbot': {
type: 'nightbot',
endpoint: 'https://api.nightbot.tv/oauth2/authorize',
grantType: 'token',
scopes: ['song_requests', 'song_requests_queue', 'song_requests_playlist'],
redirect: 'https://tomtospeech.com/connection/authorize'
},
'twitch': {
type: 'twitch',
endpoint: 'https://id.twitch.tv/oauth2/authorize',
grantType: 'token',
scopes: [
'chat:read',
'bits:read',
'channel:read:polls',
'channel:read:predictions',
'channel:read:subscriptions',
'channel:read:vips',
'moderator:read:blocked_terms',
'chat:read',
'channel:moderate',
'channel:read:redemptions',
'channel:manage:redemptions',
'channel:manage:predictions',
'user:read:chat',
'channel:bot',
'moderator:read:followers',
'channel:read:ads',
'moderator:read:chatters',
],
redirect: 'https://tomtospeech.com/connection/authorize'
},
// 'twitch tts bot': {
// type: 'twitch',
// endpoint: 'https://id.twitch.tv/oauth2/authorize',
// grantType: 'token',
// scopes: [
// 'chat:read',
// 'bits:read',
// 'channel:read:polls',
// 'channel:read:predictions',
// 'channel:read:subscriptions',
// 'channel:read:vips',
// 'moderator:read:blocked_terms',
// 'chat:read',
// 'channel:moderate',
// 'channel:read:redemptions',
// 'channel:manage:redemptions',
// 'channel:manage:predictions',
// 'user:read:chat',
// 'channel:bot',
// 'moderator:read:followers',
// 'channel:read:ads',
// 'moderator:read:chatters',
// ],
// redirect: 'https://tomtospeech.com/connection/authorize'
// }
}
function AddOrRenew(name: string, type: string | undefined, clientId: string, router: AppRouterInstance) {
if (type === undefined)
return
if (!(type in AUTHORIZATION_DATA))
return
console.log(type)
const data = AUTHORIZATION_DATA[type]
const state = v4()
const clientIdUpdated = type == 'twitch tts bot' ? process.env.NEXT_PUBLIC_TWITCH_TTS_CLIENT_ID : clientId
axios.post("/api/connection/prepare", {
name: name,
type: data.type,
clientId: clientIdUpdated,
grantType: data.grantType,
state: state
}).then(_ => {
const url = data.endpoint + '?client_id=' + clientIdUpdated + '&redirect_uri=' + data.redirect + '&response_type=' + data.grantType
+ '&scope=' + data.scopes.join('%20') + '&state=' + state + '&force_verify=true'
router.push(url)
})
}
export const ConnectionElement = ({
name,
type,
clientId,
expiresAt,
remover,
}: Connection) => {
const router = useRouter()
const expirationHours = (new Date(expiresAt).getTime() - new Date().getTime()) / 1000 / 60 / 60
const expirationDays = expirationHours / 24
function Delete() {
axios.delete("/api/connection?name=" + name)
.then(d => {
remover(d.data.data.name)
})
}
return (
<div
className="bg-green-300 p-3 border-2 border-green-400 rounded-lg flex text-black m-1">
<div
className="justify-between flex-1 font-bold text-xl">
{name}
<div className="text-base font-normal">
{expirationDays > 1 && Math.floor(expirationDays) + " days - " + type}
{expirationDays <= 1 && Math.floor(expirationHours) + " hours - " + type}
</div>
</div>
<div
className="float-right align-middle flex flex-row items-center">
<Button
className="bg-blue-500 mr-3"
onClick={() => AddOrRenew(name, type, clientId, router)}>
Renew
</Button>
<Button
className="bg-red-500"
onClick={Delete}>
Delete
</Button>
</div>
</div>
);
}
export const ConnectionAdderElement = () => {
const router = useRouter()
const [name, setName] = useState<string>('')
const [type, setType] = useState<string | undefined>(undefined)
const [clientId, setClientId] = useState('')
const [open, setOpen] = useState(false)
return (
<div
className="bg-green-300 p-3 border-2 border-green-300 rounded-lg flex m-1">
<div
className="justify-between flex-1">
<Popover
open={open}
onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[120px] justify-between"
>{!type ? "Select service..." : type}</Button>
</PopoverTrigger>
<PopoverContent>
<Command>
<CommandInput
placeholder="Filter services..."
autoFocus={true} />
<CommandList>
<CommandEmpty>No action found.</CommandEmpty>
<CommandGroup>
{Object.keys(AUTHORIZATION_DATA).map((authType: string) => (
<CommandItem
value={authType}
key={authType}
onSelect={(value) => {
setType(authType)
setOpen(false)
}}>
{authType}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<Input
className='w-[200px] inline m-1'
placeholder="Name"
value={name}
onChange={e => setName(e.target.value.toLowerCase())} />
{!!type && type != 'twitch tts bot' &&
<Input
className='w-[250px] m-1'
placeholder="Client Id"
value={clientId}
onChange={e => setClientId(e.target.value)} />
}
</div>
<div
className="float-right flex flex-row items-center">
<Button
className="bg-green-500"
onClick={() => AddOrRenew(name, type, clientId, router)}>
Add
</Button>
</div>
</div>
);
}