add ability to create and delete antispam rules

This commit is contained in:
JandereDev 2022-03-14 10:00:10 +01:00
parent 50bd8e1c06
commit 30185ce004
No known key found for this signature in database
GPG key ID: 5D5E18ACB990F57A
6 changed files with 171 additions and 5 deletions

View file

@ -20,6 +20,7 @@
"express": "^4.17.2", "express": "^4.17.2",
"log75": "^2.2.0", "log75": "^2.2.0",
"monk": "^7.3.4", "monk": "^7.3.4",
"ulid": "^2.3.0",
"ws": "^8.4.2" "ws": "^8.4.2"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,8 +1,9 @@
import { app, db } from '../..'; import { app, db } from '../..';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { badRequest, isAuthenticated, requireAuth, unauthorized } from '../../utils'; import { badRequest, ensureObjectStructure, isAuthenticated, requireAuth, unauthorized } from '../../utils';
import { botReq } from '../internal/ws'; import { botReq } from '../internal/ws';
import { FindOneResult } from 'monk'; import { FindOneResult } from 'monk';
import { ulid } from 'ulid';
type AntispamRule = { type AntispamRule = {
id: string; id: string;
@ -80,3 +81,82 @@ app.patch('/dash/server/:server/automod/:ruleid', requireAuth({ permission: 2 })
return res.send({ success: true }); return res.send({ success: true });
}); });
app.post('/dash/server/:server/automod', requireAuth({ permission: 2 }), async (req, res) => {
const user = await isAuthenticated(req, res, true);
if (!user) return;
const { server } = req.params;
if (!server || typeof server != 'string') return badRequest(res);
const response = await botReq('getUserServerDetails', { user, server });
if (!response.success) {
return res.status(response.statusCode ?? 500).send({ error: response.error });
}
if (!response.server) return res.status(404).send({ error: 'Server not found' });
let rule: any;
try {
rule = ensureObjectStructure(req.body, {
max_msg: 'number',
timeframe: 'number',
action: 'number',
message: 'string',
}, true);
} catch(e) { return res.status(400).send(e) }
if (rule.action != null && rule.action < 0 || rule.action > 4) return res.status(400).send('Invalid action');
const id = ulid();
await db.get('servers').update({
id: server,
}, {
$push: {
"automodSettings.spam": {
id: id,
max_msg: rule.max_msg ?? 5,
timeframe: rule.timeframe ?? 3,
action: rule.action ?? 0,
message: rule.message ?? null,
}
}
});
res.status(200).send({ success: true, id: id });
});
app.delete('/dash/server/:server/automod/:ruleid', requireAuth({ permission: 2 }), async (req, res) => {
const user = await isAuthenticated(req, res, true);
if (!user) return;
const { server, ruleid } = req.params;
if (!server || typeof server != 'string' || !ruleid || typeof ruleid != 'string') return badRequest(res);
const response = await botReq('getUserServerDetails', { user, server });
if (!response.success) {
return res.status(response.statusCode ?? 500).send({ error: response.error });
}
if (!response.server) return res.status(404).send({ error: 'Server not found' });
// todo: fix this shit idk if it works
let queryRes;
try {
queryRes = await db.get('servers').update({
id: server
}, {
$pull: {
"automodSettings.spam": { id: ruleid }
}
});
} catch(e) {
console.error(e);
res.status(500).send({ error: e });
return;
}
if (queryRes.nModified > 0) res.status(200).send({ success: true });
else res.status(404).send({ success: false, error: 'Rule not found' });
});

View file

@ -144,7 +144,7 @@ app.put('/dash/server/:server/:option', async (req: Request, res: Response) => {
} }
}); });
app.delete('/dash/server/:server/:option/:target', async (req: Request, res: Response) => { app.delete('/dash/server/:server/:option/:target', async (req: Request, res: Response, next) => {
const user = await isAuthenticated(req, res, true); const user = await isAuthenticated(req, res, true);
if (!user) return unauthorized(res); if (!user) return unauthorized(res);
@ -190,6 +190,6 @@ app.delete('/dash/server/:server/:option/:target', async (req: Request, res: Res
}); });
return; return;
} }
default: return badRequest(res); default: next();
} }
}); });

View file

@ -69,4 +69,49 @@ function requireAuth(config: RequireAuthConfig): (req: Request, res: Response, n
} }
} }
export { isAuthenticated, getSessionInfo, badRequest, unauthorized, getPermissionLevel, requireAuth } /**
* Strips the input object of unwanted fields and
* throws if a value has the wrong type
* @param obj
* @param structure
*/
function ensureObjectStructure(obj: any, structure: { [key: string]: 'string'|'number'|'float'|'strarray' }, allowEmpty?: boolean): any {
const returnObj: any = {}
for (const key of Object.keys(obj)) {
const type = obj[key] == null ? 'null' : typeof obj[key];
if (allowEmpty && (type == 'undefined' || type == 'null')) continue;
switch(structure[key]) {
case 'string':
case 'number':
case 'float':
if (type != structure[key]) throw `Property '${key}' was expected to be of type '${structure[key]}', got '${type}' instead`;
if (structure[key] == 'number' && `${Math.round(obj[key])}` != `${obj[key]}`)
throw `Property '${key}' was expected to be of type '${structure[key]}', got 'float' instead`;
returnObj[key] = obj[key];
break;
case 'strarray':
if (!(obj[key] instanceof Array)) {
throw `Property '${key}' was expected to be of type 'string[]', got '${type}' instead`;
}
for (const i in obj[key]) {
const item = obj[key][i];
if (typeof item != 'string') throw `Property '${key}' was expected to be of type 'string[]', `
+ `found '${typeof item}' at index ${i}`;
}
returnObj[key] = obj[key];
break;
default: continue;
}
}
return returnObj;
}
export { isAuthenticated, getSessionInfo, badRequest, unauthorized, getPermissionLevel, requireAuth, ensureObjectStructure }

View file

@ -639,6 +639,11 @@ typescript@^4.5.5:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
ulid@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/ulid/-/ulid-2.3.0.tgz#93063522771a9774121a84d126ecd3eb9804071f"
integrity sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==
unpipe@1.0.0, unpipe@~1.0.0: unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"

View file

@ -8,7 +8,6 @@ import { LineDivider } from '@revoltchat/ui/lib/components/atoms/layout/LineDivi
import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1'; import { H1 } from '@revoltchat/ui/lib/components/atoms/heading/H1';
import { H3 } from '@revoltchat/ui/lib/components/atoms/heading/H3'; import { H3 } from '@revoltchat/ui/lib/components/atoms/heading/H3';
import { H4 } from '@revoltchat/ui/lib/components/atoms/heading/H4'; import { H4 } from '@revoltchat/ui/lib/components/atoms/heading/H4';
import { H5 } from '@revoltchat/ui/lib/components/atoms/heading/H5';
import { Icon } from '@mdi/react'; import { Icon } from '@mdi/react';
import { mdiCloseBox } from '@mdi/js'; import { mdiCloseBox } from '@mdi/js';
import { API_URL } from "../App"; import { API_URL } from "../App";
@ -187,6 +186,34 @@ const ServerDashboard: FunctionComponent = () => {
? ( ? (
<> <>
{automodSettings.antispam.map(r => <AntispamRule rule={r} key={r.id} />)} {automodSettings.antispam.map(r => <AntispamRule rule={r} key={r.id} />)}
<Button style={{
marginTop: '12px',
}} onClick={async () => {
const newRule: AntispamRule = {
action: 0,
max_msg: 5,
timeframe: 3,
message: null,
id: '',
channels: [],
}
const res = await axios.post(
`${API_URL}/dash/server/${serverid}/automod`,
{
action: newRule.action,
max_msg: newRule.max_msg,
timeframe: newRule.timeframe,
},
{ headers: await getAuthHeaders() }
);
newRule.id = res.data.id;
setAutomodSettings({ antispam: [ ...(automodSettings.antispam), newRule ] });
}}>
Create Rule
</Button>
</> </>
) )
: ( : (
@ -441,6 +468,13 @@ const ServerDashboard: FunctionComponent = () => {
setChannelsChanged(false); 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 = { const inputStyle: React.CSSProperties = {
maxWidth: '100px', maxWidth: '100px',
margin: '8px 8px 0px 8px', margin: '8px 8px 0px 8px',
@ -560,6 +594,7 @@ const ServerDashboard: FunctionComponent = () => {
> >
<Button style={{ float: 'left' }} onClick={save}>Save</Button> <Button style={{ float: 'left' }} onClick={save}>Save</Button>
<Button style={{ float: 'left', marginLeft: '8px' }} onClick={reset}>Reset</Button> <Button style={{ float: 'left', marginLeft: '8px' }} onClick={reset}>Reset</Button>
<Button style={{ float: 'left', marginLeft: '8px' }} onClick={remove}>Delete</Button>
<div style={{ clear: 'both' }} /> <div style={{ clear: 'both' }} />
</div> </div>
</div> </div>