working on discord

This commit is contained in:
ansible user/allowed to read system logs 2023-08-01 20:13:11 -07:00
parent e9e5e8f7ec
commit 8504775ff3
17 changed files with 195 additions and 54 deletions

View file

@ -14,6 +14,7 @@ Very unfinished!! Mostly a programming exercise for me for now to practice fulls
- [ ] Sanitize user inputs - [ ] Sanitize user inputs
- [ ] Check status of matterbridge processes - [ ] Check status of matterbridge processes
- [ ] Complete slack login workflow - [ ] Complete slack login workflow
- [ ] Kill group processes & delete config when group is deleted
## Supported Clients ## Supported Clients

View file

@ -0,0 +1,7 @@
export const getDiscordInstallURL = (callback: CallableFunction) => {
fetch('api/discord/install')
.then(res => res.json())
.then(res => {
callback(res.data.url)
})
}

View file

@ -10,4 +10,18 @@ export const groupInvite = (token: string, callback: CallableFunction) => {
}) })
.then(result => result.json()) .then(result => result.json())
.then(result => callback(result)) .then(result => callback(result))
}
export const deleteGroup = (id: string, callback: CallableFunction) => {
fetch('api/groups', {
method: "DELETE",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
id: id
})
})
.then(result => result.json())
.then(result => callback(result))
} }

View file

@ -9,10 +9,12 @@ import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead'; import TableHead from '@mui/material/TableHead';
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import GroupRow from "./groupRow"; import GroupRow from "./groupRow";
export default function GroupPanel({ export default function GroupPanel({
groups groups,
fetchGroups
}){ }){
return( return(
@ -24,6 +26,7 @@ export default function GroupPanel({
<TableCell>Group</TableCell> <TableCell>Group</TableCell>
<TableCell align="right">Created At</TableCell> <TableCell align="right">Created At</TableCell>
<TableCell align="right">Invite Token</TableCell> <TableCell align="right">Invite Token</TableCell>
<TableCell align="right">Delete</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -34,6 +37,7 @@ export default function GroupPanel({
id={group.id} id={group.id}
invite_token={group.invite_token} invite_token={group.invite_token}
created_at={group.created_at} created_at={group.created_at}
fetchGroups={fetchGroups}
></GroupRow> ></GroupRow>
)) : undefined} )) : undefined}
</TableBody> </TableBody>

View file

@ -2,17 +2,30 @@ import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer'; import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead'; import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow'; import TableRow from '@mui/material/TableRow';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import IconButton from "@mui/material/IconButton"
import {deleteGroup} from "../../api/groups";
export interface GroupRowProps { export interface GroupRowProps {
name: string; name: string;
id: string; id: string;
invite_token: string; invite_token: string;
created_at: string; created_at: string;
fetchGroups: CallableFunction;
} }
export default function GroupRow( export default function GroupRow(
{name, id, invite_token, created_at}: GroupRowProps {name, id, invite_token, created_at, fetchGroups}: GroupRowProps
){ ){
const handleDeleteGroup = () => {
deleteGroup(id, deleteGroupCallback)
}
const deleteGroupCallback = () => {
fetchGroups()
}
return( return(
<TableRow <TableRow
key={id} key={id}
@ -23,6 +36,13 @@ export default function GroupRow(
</TableCell> </TableCell>
<TableCell align="right">{created_at}</TableCell> <TableCell align="right">{created_at}</TableCell>
<TableCell align="right">{invite_token}</TableCell> <TableCell align="right">{invite_token}</TableCell>
<TableCell align="right">
<IconButton
onClick={handleDeleteGroup}
>
<DeleteForeverIcon/>
</IconButton>
</TableCell>
</TableRow> </TableRow>
) )

View file

@ -9,9 +9,11 @@ import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select'; import Select from '@mui/material/Select';
import {SlackLogin} from "../platforms/slackLogin"; import {SlackLogin} from "../platforms/slackLogin";
import {DiscordLogin} from "../platforms/discordLogin";
enum PLATFORMS { enum PLATFORMS {
Slack = 'Slack' Slack = 'Slack',
Discord = 'Discord'
} }
export const JoinLogin = ({ export const JoinLogin = ({
@ -22,7 +24,6 @@ export const JoinLogin = ({
stepComplete, stepComplete,
setStepComplete setStepComplete
}) => { }) => {
const [loginComponent, setLoginComponent] = useState(<></>);
const handleSelect = (event) => { const handleSelect = (event) => {
@ -30,19 +31,6 @@ export const JoinLogin = ({
setPlatform(event.target.value); setPlatform(event.target.value);
} }
useEffect(() => {
switch(platform){
case "Slack":
setLoginComponent(<SlackLogin
bridge={bridge}
setBridge={setBridge}
/>)
default:
setLoginComponent(<></>)
}
}, [platform])
useEffect(() => { useEffect(() => {
if (bridge !== undefined) { if (bridge !== undefined) {
setStepComplete({...stepComplete, login:true}) setStepComplete({...stepComplete, login:true})
@ -60,11 +48,20 @@ export const JoinLogin = ({
label={"Select Platform"} label={"Select Platform"}
> >
<MenuItem value={'Slack'}>Slack</MenuItem> <MenuItem value={'Slack'}>Slack</MenuItem>
<MenuItem value={'Discord'}>Discord</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
{ {
platform ? loginComponent : <div style={{width: "50%"}}></div> platform === "Slack" ? <SlackLogin
bridge={bridge}
setBridge={setBridge}
/>
: platform === "Discord" ? <DiscordLogin
bridge={bridge}
setBridge={setBridge}
/>
: <div style={{width: "50%"}}></div>
} }
</div> </div>

View file

@ -135,6 +135,7 @@ export default function ManagePanel(){
loggedIn ? loggedIn ?
<GroupPanel <GroupPanel
groups={groups} groups={groups}
fetchGroups={fetchGroups}
/> />
: :
null null

View file

@ -0,0 +1,56 @@
import {useEffect, useRef, useState} from "react";
import {getDiscordInstallURL} from "../../api/discord";
import {getBridgeByStateToken} from "../../api/bridge";
import Button from "@mui/material/Button";
export const DiscordLogin = ({
bridge,
setBridge
}) => {
const [installLink, setInstallLink] = useState();
const [installStarted, setInstallStarted] = useState(false)
const pingTimeout = useRef(null);
const openInstallTab = () => {
window.open(installLink, '_blank').focus();
setInstallStarted(true)
}
useEffect(() => {
getDiscordInstallURL(handleInstallLink)
}, [])
const handleInstallLink = (url) => {
setInstallLink(url);
}
useEffect(() => {
const pingForBridge = () => {
if (bridge === undefined) {
getBridgeByStateToken(setBridge);
pingTimeout.current = setTimeout(pingForBridge, 1000);
}
}
if (installStarted) {
if (bridge === undefined){
pingForBridge()
}
}
return () => {clearInterval(pingTimeout.current)}
}, [installStarted, bridge])
return(
<Button
variant={"outlined"}
onClick={openInstallTab}
color={bridge !== undefined ? 'success': undefined}
disabled={installLink === undefined}
>
{installLink === undefined ? 'Waiting for Install Link...' : 'Add to Slack'}
</Button>
)
}

View file

@ -6,7 +6,7 @@ import {getBridgeByStateToken} from "../../api/bridge";
export const SlackLogin = ({ export const SlackLogin = ({
bridge, bridge,
setBridge setBridge
}) => { }) => {
const [installLink, setInstallLink] = useState(); const [installLink, setInstallLink] = useState();
const [installStarted, setInstallStarted] = useState(false) const [installStarted, setInstallStarted] = useState(false)
const pingTimeout = useRef(null); const pingTimeout = useRef(null);

View file

@ -21,7 +21,6 @@ export const checkAdminToken = async(
}); });
} else { } else {
req.session.logged_in = false; req.session.logged_in = false;
logger.warning("Login with admin token failed") logger.warning("Login with admin token failed")
return res.status(403).json({ return res.status(403).json({
status:'fail', status:'fail',

View file

@ -20,6 +20,13 @@ export const DiscordInstallLinkHandler = async(
const url = `https://discordapp.com/oauth2/authorize?&client_id=${discordConfig.client_id}&scope=bot&permissions=536870912&state=${state_token}` const url = `https://discordapp.com/oauth2/authorize?&client_id=${discordConfig.client_id}&scope=bot&permissions=536870912&state=${state_token}`
res.status(200).json({
status: 'success',
data: {
url
}
})
} }

View file

@ -86,6 +86,26 @@ export const getGroupWithInviteHandler = async(
} }
export const deleteGroupHandler = async(
req: Request,
res:Response,
) => {
let group = await groupRepository.findOneBy({id: req.body.id})
if (group){
await groupRepository.remove(group)
return res.status(200).json({
status: 'success',
message: 'Group successfully deleted!'
})
} else {
return res.status(404).json({
status: 'failed',
message: 'no group with matching ID found'
})
}
}
export const getGroupsHandler = async( export const getGroupsHandler = async(
req: Request, req: Request,
res: Response res: Response

View file

@ -1,20 +1,28 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
import config from "config"; import config from "config";
import { Logger, format, createLogger, transports } from 'winston'; // import { Logger, format, createLogger, transports } from 'winston';
const winston = require('winston');
let Logger = winston.Logger
let format = winston.format
let createLogger = winston.createLogger
let transports = winston.transports
const logDir = config.get<string>('logDir') const logDir = config.get<string>('logDir')
const makeLogdir = () => { const makeLogdir = () => {
fs.mkdir(logDir, (err:Error|undefined) => { if (!fs.existsSync(logDir)){
if (err) throw err; fs.mkdir(logDir, (err:Error|undefined) => {
}) if (err) throw err;
})
}
} }
const makeLogger = (): Logger => { const makeLogger = (): typeof Logger => {
makeLogdir() makeLogdir()
const logger = createLogger({ const logger = createLogger({
levels: winston.config.syslog.levels,
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info', level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
format: format.combine( format: format.combine(
format.timestamp({ format.timestamp({
@ -26,11 +34,11 @@ const makeLogger = (): Logger => {
), ),
transports: [ transports: [
new transports.File({ new transports.File({
filename: path.join([logDir, 'chatbridge.error.log']), filename: path.join(logDir, 'chatbridge.error.log'),
level: 'error' level: 'error'
}), }),
new transports.File({ new transports.File({
filename: path.join([logDir, 'chatbridge.debug.log']), filename: path.join(logDir, 'chatbridge.debug.log'),
level: 'debug' level: 'debug'
}), }),
...(process.env.NODE_ENV === 'development' ? [ ...(process.env.NODE_ENV === 'development' ? [

View file

@ -175,7 +175,7 @@ export const writeTOML = (gateway_toml: object, out_file: string) => {
} }
) )
logger.debug('toml string', toml_string) // logger.debug('toml string', toml_string)
fs.writeFileSync(out_file, toml_string) fs.writeFileSync(out_file, toml_string)

View file

@ -58,30 +58,35 @@ class MatterbridgeManager {
let group_name_slug = slugify(group_name) let group_name_slug = slugify(group_name)
let group_filename = `${this.matterbridge_config_dir}/matterbridge-${group_name_slug}.toml` let group_filename = `${this.matterbridge_config_dir}/matterbridge-${group_name_slug}.toml`
await writeGroupConfig(group_name, group_filename); await writeGroupConfig(group_name, group_filename);
if (!this.process_list.includes(group_name_slug)){
logger.info('Spawning new matterbridge process: %s', group_name_slug) pm2.connect(async(err:any) => {
await pm2.start( if (!this.process_list.includes(group_name_slug)) {
{ logger.info('Spawning new matterbridge process: %s', group_name_slug)
name: group_name_slug, await pm2.start(
script: this.matterbridge_bin, {
args: `-conf ${group_filename}`, name: group_name_slug,
interpreter: 'none' script: this.matterbridge_bin,
}, args: `-conf ${group_filename}`,
(err:any, apps:object) => { interpreter: 'none'
if (err) { },
logger.error('error starting matterbridge process', err, apps) (err: any, apps: object) => {
} if (err) {
} logger.error('error starting matterbridge process', err, apps)
) }
this.process_list.push(group_name_slug) }
} else { )
logger.info('Restarting existing matterbridge process: %s', group_name_slug) this.process_list.push(group_name_slug)
await pm2.restart(group_name_slug, (err:any, proc:any) => { } else {
if (err) { logger.info('Restarting existing matterbridge process: %s', group_name_slug)
logger.error('error restarting matterbridge process', err) await pm2.restart(group_name_slug, (err: any, proc: any) => {
}} if (err) {
) logger.error('error restarting matterbridge process', err)
} }
}
)
}
// await pm2.disconnect()
})
} }
async refreshConfig(group_name: string) { async refreshConfig(group_name: string) {
@ -111,7 +116,7 @@ class MatterbridgeManager {
status: proc.pm2_env.status, status: proc.pm2_env.status,
monit: proc.monit, monit: proc.monit,
pm2_env: { pm2_env: {
created_at: proc.pm2_env.proc.pm2_env.created_at, created_at: proc.pm2_env.created_at,
exec_interpreter: proc.pm2_env.exec_interpreter, exec_interpreter: proc.pm2_env.exec_interpreter,
exec_mode: proc.pm2_env.exec_mode, exec_mode: proc.pm2_env.exec_mode,
instances: proc.pm2_env.instances, instances: proc.pm2_env.instances,

View file

@ -3,7 +3,8 @@ import express from 'express';
import { import {
createGroupHandler, createGroupHandler,
getGroupHandler, getGroupHandler,
getGroupWithInviteHandler getGroupWithInviteHandler,
deleteGroupHandler
} from "../controllers/group.controller"; } from "../controllers/group.controller";
import { import {
@ -19,6 +20,7 @@ router
.route('/') .route('/')
.post(validate(createGroupSchema), requireAdmin, createGroupHandler) .post(validate(createGroupSchema), requireAdmin, createGroupHandler)
.get(requireAdmin, getGroupHandler) .get(requireAdmin, getGroupHandler)
.delete(requireAdmin, deleteGroupHandler)
router router
.route('/invite') .route('/invite')