import axios from 'axios'; import React, { FunctionComponent, useCallback, useEffect, useState } from "react"; import { Button } from '@revoltchat/ui/lib/components/atoms/inputs/Button'; import { InputBox } from '@revoltchat/ui/lib/components/atoms/inputs/InputBox'; import { Checkbox } from '@revoltchat/ui/lib/components/atoms/inputs/Checkbox'; import { ComboBox } from '@revoltchat/ui/lib/components/atoms/inputs/ComboBox'; import { LineDivider } from '@revoltchat/ui/lib/components/atoms/layout/LineDivider'; import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1'; import { H3 } from '@revoltchat/ui/lib/components/atoms/heading/H3'; import { H4 } from '@revoltchat/ui/lib/components/atoms/heading/H4'; import { Icon } from '@mdi/react'; import { mdiCloseBox } from '@mdi/js'; import { API_URL } from "../App"; import { getAuthHeaders } from "../utils"; import { useParams } from "react-router-dom"; import defaultChannelIcon from '../assets/channel-default-icon.svg'; type User = { id: string, username?: string, avatarURL?: string } type Channel = { id: string, name: string, icon?: string, type: 'VOICE'|'TEXT', nsfw: boolean } type Server = { id?: string, perms?: 0|1|2|3, name?: string, description?: string, iconURL?: string, bannerURL?: string, serverConfig?: { [key: string]: any }, users: User[], channels: Channel[], } type AntispamRule = { id: string; max_msg: number; timeframe: number; action: 0|1|2|3|4; channels: string[] | null; message: string | null; } const ServerDashboard: FunctionComponent = () => { const [serverInfo, setServerInfo] = useState({} as Server); const [status, setStatus] = useState(''); const [changed, setChanged] = useState({} as { prefix?: boolean, prefixAllowSpace?: boolean }); const [prefix, setPrefix] = useState('' as string|undefined); const [prefixAllowSpace, setPrefixAllowSpace] = useState(false); const [botManagers, setBotManagers] = useState([] as string[]); const [moderators, setModerators] = useState([] as string[]); const [automodSettings, setAutomodSettings] = useState(null as { antispam: AntispamRule[] }|null); const { serverid } = useParams(); const saveConfig = useCallback(async () => { if (Object.values(changed).filter(i => i).length == 0) return; const payload = { ...(changed.prefix ? { prefix } : undefined), ...(changed.prefixAllowSpace ? { spaceAfterPrefix: prefixAllowSpace } : undefined), } const res = await axios.put( API_URL + `/dash/server/${serverid}/config`, payload, { headers: await getAuthHeaders() } ); if (res.data.success) { setChanged({}); } }, [ prefix, prefixAllowSpace, changed ]); const loadInfo = useCallback(async () => { try { const res = await axios.get(`${API_URL}/dash/server/${serverid}`, { headers: await getAuthHeaders() }); console.log(res.data); const server: Server = res.data.server; setServerInfo(server); setPrefix(server.serverConfig?.prefix || ''); setPrefixAllowSpace(!!server.serverConfig?.spaceAfterPrefix); setBotManagers(server.serverConfig?.botManagers ?? []); setModerators(server.serverConfig?.moderators ?? []); loadAutomodInfo(server); } catch(e: any) { console.error(e); setStatus(`${e?.message ?? e}`); } }, [serverInfo]); const loadAutomodInfo = useCallback(async (server: Server) => { if ((server.perms ?? 0) > 0) { const res = await axios.get(API_URL + `/dash/server/${serverid}/automod`, { headers: await getAuthHeaders() }); setAutomodSettings(res.data); console.log(res.data); } }, []); useEffect(() => { loadInfo(); }, []); return ( <>

{serverInfo?.name ?? 'Loading...'}

{status.length ? {status} :
} ); function RemoveButton(props: { onClick: () => void }) { return (
) } function UserListEntry(props: { user: User, type: 'MANAGER'|'MOD' }) { return (
{props.user.username ?? 'Unknown'} { const res = await axios.delete( `${API_URL}/dash/server/${serverid}/${props.type == 'MANAGER' ? 'managers' : 'mods'}/${props.user.id}`, { headers: await getAuthHeaders() } ); if (props.type == 'MANAGER') { setBotManagers(res.data.managers); } else if (props.type == 'MOD') { setModerators(res.data.mods); } }} />
); } function UserListContainer(props: { disabled: boolean, children: any }) { return (
{props.children}
); } function UserListTypeContainer(props: any) { return (
{props.children}
); } function UserListAddField(props: { type: 'MANAGER'|'MOD' }) { const [content, setContent] = useState(''); const onConfirm = useCallback(async () => {0 if (content.length) { const res = await axios.put( `${API_URL}/dash/server/${serverid}/${props.type == 'MANAGER' ? 'managers' : 'mods'}`, { item: content }, { headers: await getAuthHeaders() } ); if (res.data.users?.length) { res.data.users.forEach((user: User) => { if (!serverInfo.users.find(u => u.id == user.id)) serverInfo.users.push(user); }); } if (props.type == 'MANAGER') { setBotManagers(res.data.managers); } else if (props.type == 'MOD') { setModerators(res.data.mods); } } }, [content]); return (
setContent(e.currentTarget.value)} style={{ float: 'left', width: '180px', height: '38px', margin: '4px 8px', }} onKeyDown={e => e.key == 'Enter' && onConfirm()} />
); } function ChannelListAddField(props: { onInput: (channel: Channel) => void }) { const [content, setContent] = useState(''); const onConfirm = useCallback(async () => { if (content.length) { const channel = serverInfo.channels .find(c => c.id == content.toUpperCase()) || serverInfo.channels .find(c => c.name == content) || serverInfo.channels // Prefer channel with same capitalization, .find(c => c.name.toLowerCase() == content.toLowerCase()); // otherwise search case insensitive if (channel && channel.type == 'TEXT') { props.onInput(channel); setContent(''); } } }, [content]); return (
setContent(e.currentTarget.value)} style={{ float: 'left', width: '180px', height: '38px', margin: '4px 8px', }} onKeyDown={e => e.key == 'Enter' && onConfirm()} />
); } function AntispamRule(props: { rule: AntispamRule }) { const [maxMsg, setMaxMsg] = useState(props.rule.max_msg); const [timeframe, setTimeframe] = useState(props.rule.timeframe); const [action, setAction] = useState(props.rule.action); const [message, setMessage] = useState(props.rule.message || ''); const [channels, setChannels] = useState(props.rule.channels ?? []); const [channelsChanged, setChannelsChanged] = useState(false); const save = useCallback(async () => { await axios.patch( `${API_URL}/dash/server/${serverid}/automod/${props.rule.id}`, { action: action != props.rule.action ? action : undefined, channels: channelsChanged ? channels : undefined, max_msg: maxMsg != props.rule.max_msg ? maxMsg : undefined, message: message != props.rule.message ? message : undefined, timeframe: timeframe != props.rule.timeframe ? timeframe : undefined, } as AntispamRule, { headers: await getAuthHeaders() } ); await loadAutomodInfo(serverInfo); }, [maxMsg, timeframe, action, message, channels, channelsChanged]); const reset = useCallback(() => { setMaxMsg(props.rule.max_msg); setTimeframe(props.rule.timeframe); setAction(props.rule.action); setMessage(props.rule.message || ''); setChannels(props.rule.channels ?? []); setChannelsChanged(false); }, []); const remove = useCallback(async () => { if (confirm(`Do you want to irreversably delete rule ${props.rule.id}?`)) { await axios.delete(`${API_URL}/dash/server/${serverid}/automod/${props.rule.id}`, { headers: await getAuthHeaders() }); setAutomodSettings({ antispam: automodSettings!.antispam.filter(r => r.id != props.rule.id) }); } }, []); const inputStyle: React.CSSProperties = { maxWidth: '100px', margin: '8px 8px 0px 8px', } const messagePlaceholders = { 0: '', 1: 'Message content...', 2: '(Optional) Warn reason...', 3: '', 4: '', } return (
If user sends more than { const val = e.currentTarget.value; if (!isNaN(Number(val)) && val.length <= 4 && Number(val) >= 0) setMaxMsg(Number(val)); }} /> messages in { const val = e.currentTarget.value; if (!isNaN(Number(val)) && val.length <= 4 && Number(val) >= 0) setTimeframe(Number(val)); }} /> seconds, setAction(ev.currentTarget.value as any)} > = 3 || action == 0 ? 'none' : 'unset' }} value={message} placeholder={messagePlaceholders[action] || ''} onChange={ev => setMessage(ev.currentTarget.value)} /> = 3 ? 'unset' : 'none'}}>
"Kick" and "Ban" actions are currently placeholders, they do not have any functionality yet.

You can specify channels here that this rule will run in. If left empty, it will run in all channels.

{ channels.map(cid => { const channel: Channel = serverInfo.channels.find(c => c.id == cid && c.type == 'TEXT') || { id: cid, name: 'Unknown channel', nsfw: false, type: 'TEXT' }; return (
{channel.name} { setChannels(channels.filter(c => c != cid)); setChannelsChanged(true); }} />
) }) } { if (!channels.includes(channel.id)) { setChannels([ ...channels, channel.id ]); setChannelsChanged(true); } }} />
) } } export default ServerDashboard;