server list
This commit is contained in:
parent
f01c616a44
commit
46802f995d
10 changed files with 221 additions and 8 deletions
|
@ -25,6 +25,7 @@ export { logger, app, db, PORT, SESSION_LIFETIME }
|
||||||
import('./routes/internal/ws'),
|
import('./routes/internal/ws'),
|
||||||
import('./routes/root'),
|
import('./routes/root'),
|
||||||
import('./routes/login'),
|
import('./routes/login'),
|
||||||
|
import('./routes/dash/servers'),
|
||||||
]);
|
]);
|
||||||
logger.done('All routes and middlewares loaded');
|
logger.done('All routes and middlewares loaded');
|
||||||
})();
|
})();
|
||||||
|
|
21
api/src/routes/dash/servers.ts
Normal file
21
api/src/routes/dash/servers.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { app } from '../..';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { isAuthenticated } from '../../utils';
|
||||||
|
import { botReq } from '../internal/ws';
|
||||||
|
|
||||||
|
type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||||
|
|
||||||
|
app.get('/dash/servers', async (req: Request, res: Response) => {
|
||||||
|
const user = await isAuthenticated(req, res, true);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
const response = await botReq('getUserServers', { user });
|
||||||
|
if (!response.success) {
|
||||||
|
return res.status(response.statusCode ?? 500).send({ error: response.error });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.servers) return res.status(404).send({ error: 'Not found' });
|
||||||
|
|
||||||
|
const servers: Server[] = response.servers;
|
||||||
|
res.send({ servers });
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import { Request } from "express";
|
import { Request, Response } from "express";
|
||||||
import { FindOneResult } from "monk";
|
import { FindOneResult } from "monk";
|
||||||
import { db } from ".";
|
import { db } from ".";
|
||||||
|
|
||||||
|
@ -15,13 +15,16 @@ class Session {
|
||||||
* @param req
|
* @param req
|
||||||
* @returns false if not authenticated, otherwise the (Revolt) user ID
|
* @returns false if not authenticated, otherwise the (Revolt) user ID
|
||||||
*/
|
*/
|
||||||
async function isAuthenticated(req: Request): Promise<string|false> {
|
async function isAuthenticated(req: Request, res?: Response, send401?: boolean): Promise<string|false> {
|
||||||
const user = req.header('x-auth-user');
|
const user = req.header('x-auth-user');
|
||||||
const token = req.header('x-auth-token');
|
const token = req.header('x-auth-token');
|
||||||
|
|
||||||
if (!user || !token) return false;
|
if (!user || !token) return false;
|
||||||
|
|
||||||
const info = await getSessionInfo(user, token);
|
const info = await getSessionInfo(user, token);
|
||||||
|
if (res && send401 && !info.valid) {
|
||||||
|
res.status(401).send({ error: 'Unauthorized' });
|
||||||
|
}
|
||||||
return info.valid ? user : false;
|
return info.valid ? user : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
bot/src/bot/modules/api/servers.ts
Normal file
54
bot/src/bot/modules/api/servers.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { User } from 'revolt.js/dist/maps/Users';
|
||||||
|
import { client } from '../../..';
|
||||||
|
import { getPermissionLevel, isBotManager } from '../../util';
|
||||||
|
import { wsEvents, WSResponse } from '../api_communication';
|
||||||
|
|
||||||
|
type ReqData = { user: string }
|
||||||
|
|
||||||
|
wsEvents.on('req:getUserServers', async (data: ReqData, cb: (data: WSResponse) => void) => {
|
||||||
|
try {
|
||||||
|
let user: User;
|
||||||
|
try {
|
||||||
|
user = client.users.get(data.user) || await client.users.fetch(data.user);
|
||||||
|
} catch(e) {
|
||||||
|
cb({ success: false, error: 'The requested user could not be found', statusCode: 404 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutuals = await user.fetchMutual();
|
||||||
|
|
||||||
|
type ServerResponse = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||||
|
|
||||||
|
const promises: Promise<ServerResponse>[] = [];
|
||||||
|
|
||||||
|
for (const sid of mutuals.servers) {
|
||||||
|
promises.push(new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const server = client.servers.get(sid);
|
||||||
|
if (!server) return reject('Server not found');
|
||||||
|
const perms = await getPermissionLevel(user, server);
|
||||||
|
resolve({
|
||||||
|
id: sid,
|
||||||
|
perms,
|
||||||
|
name: server.name,
|
||||||
|
bannerURL: server.generateBannerURL(),
|
||||||
|
iconURL: server.generateIconURL({}),
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
reject(`${e}`);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
cb({
|
||||||
|
success: true,
|
||||||
|
servers: (await Promise.allSettled(promises)).map(
|
||||||
|
p => p.status == 'fulfilled' ? p.value : undefined
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
cb({ success: false, error: `${e}` });
|
||||||
|
}
|
||||||
|
});
|
|
@ -9,7 +9,6 @@ import { client as bot } from '../..';
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { parseUser } from "../util";
|
import { parseUser } from "../util";
|
||||||
import PendingLogin from "../../struct/PendingLogin";
|
import PendingLogin from "../../struct/PendingLogin";
|
||||||
import { LogLevel } from "log75";
|
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
const wsEvents = new EventEmitter();
|
const wsEvents = new EventEmitter();
|
||||||
|
@ -17,6 +16,8 @@ const { API_WS_URL, API_WS_TOKEN } = process.env;
|
||||||
const wsQueue: { [key: string]: string }[] = [];
|
const wsQueue: { [key: string]: string }[] = [];
|
||||||
let client: ws|undefined = undefined;
|
let client: ws|undefined = undefined;
|
||||||
|
|
||||||
|
type WSResponse = { success: false, error: string, statusCode?: number } | { success: true, [key: string]: any }
|
||||||
|
|
||||||
if (!API_WS_URL || !API_WS_TOKEN)
|
if (!API_WS_URL || !API_WS_TOKEN)
|
||||||
logger.info("$API_WS_URL or $API_WS_TOKEN not found.");
|
logger.info("$API_WS_URL or $API_WS_TOKEN not found.");
|
||||||
else {
|
else {
|
||||||
|
@ -92,7 +93,7 @@ wsEvents.on('req:test', (data: any, res: (data: any) => void) => {
|
||||||
res({ received: data });
|
res({ received: data });
|
||||||
});
|
});
|
||||||
|
|
||||||
wsEvents.on('req:requestLogin', async (data: any, cb: (data: any) => void) => {
|
wsEvents.on('req:requestLogin', async (data: any, cb: (data: WSResponse) => void) => {
|
||||||
try {
|
try {
|
||||||
const user = await parseUser(data.user);
|
const user = await parseUser(data.user);
|
||||||
if (!user)
|
if (!user)
|
||||||
|
@ -126,8 +127,10 @@ wsEvents.on('req:requestLogin', async (data: any, cb: (data: any) => void) => {
|
||||||
cb({ success: true, uid: user._id, nonce, code });
|
cb({ success: true, uid: user._id, nonce, code });
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
cb({ success: false, error: e });
|
cb({ success: false, error: `${e}` });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export { wsEvents, wsSend }
|
export { wsEvents, wsSend, WSResponse }
|
||||||
|
|
||||||
|
import('./api/servers');
|
||||||
|
|
|
@ -99,6 +99,22 @@ async function checkSudoPermission(message: Message): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function getPermissionLevel(user: User|Member, server: Server): Promise<0|1|2> {
|
||||||
|
if (isSudo(user instanceof User ? user : (user.user || await client.users.fetch(user._id.user)))) return 2;
|
||||||
|
|
||||||
|
const member = user instanceof User ? await server.fetchMember(user) : user;
|
||||||
|
if (user instanceof Member) user = user.user!;
|
||||||
|
|
||||||
|
if (hasPerm(member, 'ManageServer')) return 2;
|
||||||
|
if (hasPerm(member, 'KickMembers')) return 1;
|
||||||
|
|
||||||
|
const config = (await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig;
|
||||||
|
|
||||||
|
if (config.botManagers?.includes(user._id)) return 2;
|
||||||
|
if (config.moderators?.includes(user._id)) return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean {
|
function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean {
|
||||||
let p = ServerPermission[perm];
|
let p = ServerPermission[perm];
|
||||||
|
@ -292,6 +308,7 @@ export {
|
||||||
getOwnMemberInServer,
|
getOwnMemberInServer,
|
||||||
isModerator,
|
isModerator,
|
||||||
isBotManager,
|
isBotManager,
|
||||||
|
getPermissionLevel,
|
||||||
parseUser,
|
parseUser,
|
||||||
parseUserOrId,
|
parseUserOrId,
|
||||||
storeInfraction,
|
storeInfraction,
|
||||||
|
|
|
@ -4,6 +4,8 @@ import './App.css';
|
||||||
import '@revoltchat/ui/src/styles/dark.css';
|
import '@revoltchat/ui/src/styles/dark.css';
|
||||||
import '@revoltchat/ui/src/styles/common.css';
|
import '@revoltchat/ui/src/styles/common.css';
|
||||||
import RequireAuth from './components/RequireAuth';
|
import RequireAuth from './components/RequireAuth';
|
||||||
|
import DashboardHome from './pages/DashboardHome';
|
||||||
|
import ServerDashboard from './pages/ServerDashboard';
|
||||||
|
|
||||||
const API_URL = 'http://localhost:9000';
|
const API_URL = 'http://localhost:9000';
|
||||||
|
|
||||||
|
@ -12,7 +14,8 @@ function App() {
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={<Home />} />
|
<Route path='/' element={<Home />} />
|
||||||
<Route path='/dashboard' element={<RequireAuth><a>among us</a></RequireAuth>} />
|
<Route path='/dashboard' element={<RequireAuth><DashboardHome /></RequireAuth>} />
|
||||||
|
<Route path='/dashboard/:serverid' element={<RequireAuth><ServerDashboard /></RequireAuth>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
81
web/src/pages/DashboardHome.tsx
Normal file
81
web/src/pages/DashboardHome.tsx
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { FunctionComponent, useCallback, useEffect, useState } from "react";
|
||||||
|
import { Button } from '@revoltchat/ui/lib/components/atoms/inputs/Button';
|
||||||
|
import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1';
|
||||||
|
import { H2 } from '@revoltchat/ui/lib/components/atoms/heading/H2';
|
||||||
|
import { API_URL } from "../App";
|
||||||
|
import { getAuthHeaders } from "../utils";
|
||||||
|
|
||||||
|
type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||||
|
|
||||||
|
function permissionName(p: number) {
|
||||||
|
switch(p) {
|
||||||
|
case 0: return 'User';
|
||||||
|
case 1: return 'Moderator';
|
||||||
|
case 2: return 'Manager';
|
||||||
|
default: return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dashboard: FunctionComponent = () => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [servers, setServers] = useState([] as Server[]);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const loadServers = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(API_URL + '/dash/servers', { headers: await getAuthHeaders() });
|
||||||
|
setServers(res.data.servers);
|
||||||
|
setLoading(false);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => { loadServers() }, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<H1>dashbord</H1>
|
||||||
|
<br/>
|
||||||
|
<p hidden={!loading}>loading</p>
|
||||||
|
{
|
||||||
|
servers.map(server => <div className="server-card" style={{ paddingTop: '10px' }} key={server.id}>
|
||||||
|
<img
|
||||||
|
src={server.iconURL ?? 'https://dl.insrt.uk/projects/revolt/emotes/trol.png'}
|
||||||
|
width={48}
|
||||||
|
height={48}
|
||||||
|
style={{
|
||||||
|
float: 'left',
|
||||||
|
marginLeft: '8px',
|
||||||
|
marginRight: '12px',
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div style={{
|
||||||
|
float: 'left',
|
||||||
|
maxWidth: '240px',
|
||||||
|
textOverflow: 'clip',
|
||||||
|
overflow: 'clip',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}>
|
||||||
|
<H2>{server.name} ({permissionName(server.perms)})</H2>
|
||||||
|
<code style={{ color: 'var(--foreground)' }}>{server.id}</code>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
style={{ position: 'relative', top: '8px', left: '12px' }}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/dashboard/${server.id}`);
|
||||||
|
}}
|
||||||
|
>Open</Button>
|
||||||
|
</div>
|
||||||
|
<div style={{ clear: 'both' }} />
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard;
|
22
web/src/pages/ServerDashboard.tsx
Normal file
22
web/src/pages/ServerDashboard.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import localforage from "localforage";
|
||||||
|
import axios from 'axios';
|
||||||
|
import { 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 { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1';
|
||||||
|
import { H2 } from '@revoltchat/ui/lib/components/atoms/heading/H2';
|
||||||
|
import { API_URL } from "../App";
|
||||||
|
import { getAuthHeaders } from "../utils";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
type Server = { id: string, perms: 0|1|2, name: string, iconURL?: string, bannerURL?: string }
|
||||||
|
|
||||||
|
const ServerDashboard: FunctionComponent = () => {
|
||||||
|
const { serverid } = useParams();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a>sus</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServerDashboard;
|
|
@ -2,6 +2,14 @@ import axios from "axios";
|
||||||
import localforage from "localforage";
|
import localforage from "localforage";
|
||||||
import { API_URL } from "./App";
|
import { API_URL } from "./App";
|
||||||
|
|
||||||
|
async function getAuthHeaders() {
|
||||||
|
const auth: any = await localforage.getItem('auth');
|
||||||
|
return {
|
||||||
|
'x-auth-user': auth.user,
|
||||||
|
'x-auth-token': auth.token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getAuth(): Promise<false|{ user: string, token: string }> {
|
async function getAuth(): Promise<false|{ user: string, token: string }> {
|
||||||
const auth: any = await localforage.getItem('auth');
|
const auth: any = await localforage.getItem('auth');
|
||||||
if (!auth) return false;
|
if (!auth) return false;
|
||||||
|
@ -19,4 +27,4 @@ async function getAuth(): Promise<false|{ user: string, token: string }> {
|
||||||
} catch(e) { return false } // todo: dont assume we're logged out if death
|
} catch(e) { return false } // todo: dont assume we're logged out if death
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getAuth }
|
export { getAuth, getAuthHeaders }
|
Loading…
Reference in a new issue