mirror of
https://github.com/janderedev/automod.git
synced 2024-12-22 10:45:27 +00:00
add ability to create and delete antispam rules
This commit is contained in:
parent
50bd8e1c06
commit
30185ce004
|
@ -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": {
|
||||||
|
|
|
@ -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' });
|
||||||
|
});
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue