working on discord
This commit is contained in:
parent
e9e5e8f7ec
commit
8504775ff3
17 changed files with 195 additions and 54 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
7
client/src/api/discord.ts
Normal file
7
client/src/api/discord.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const getDiscordInstallURL = (callback: CallableFunction) => {
|
||||||
|
fetch('api/discord/install')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
callback(res.data.url)
|
||||||
|
})
|
||||||
|
}
|
|
@ -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))
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -135,6 +135,7 @@ export default function ManagePanel(){
|
||||||
loggedIn ?
|
loggedIn ?
|
||||||
<GroupPanel
|
<GroupPanel
|
||||||
groups={groups}
|
groups={groups}
|
||||||
|
fetchGroups={fetchGroups}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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' ? [
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in a new issue