"use client"; import axios from "axios"; import { useState } from "react"; import { Button } from "@/components/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 "@/components/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> ); }