Compare commits

..

No commits in common. "3671cbd7aef7e25ee4ecc8f152bc195360a76a2e" and "7725860a96c8e79f3f1b95d32776889f117c65c3" have entirely different histories.

26 changed files with 221 additions and 688 deletions

View file

@ -2,10 +2,6 @@ const webpack = require('webpack')
const { merge } = require('webpack-merge') const { merge } = require('webpack-merge')
const path = require( 'path' ); const path = require( 'path' );
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const ReactRefreshTypeScript = require('react-refresh-typescript');
const common = require('./webpack.common.js') const common = require('./webpack.common.js')
@ -24,10 +20,6 @@ module.exports = merge(common, {
client: { client: {
webSocketURL: 'auto://0.0.0.0:0/ws' webSocketURL: 'auto://0.0.0.0:0/ws'
}, },
allowedHosts: [
'seed.aharoni-lab.com']
}, },
// Control how source maps are generated // Control how source maps are generated
@ -50,13 +42,12 @@ module.exports = merge(common, {
'style-loader', 'style-loader',
'css-loader' 'css-loader'
] ]
}, }
], ],
}, },
plugins: [ plugins: [
// Only update what has changed on hot reload // Only update what has changed on hot reload
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin()
], ],
}) })

View file

@ -32,11 +32,8 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"css-loader": "^6.8.1", "css-loader": "^6.8.1",
"html-webpack-plugin": "^5.5.3", "html-webpack-plugin": "^5.5.3",
"react-refresh": "^0.14.0",
"react-refresh-typescript": "^2.0.9",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
"style-loader": "^3.3.3", "style-loader": "^3.3.3",
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",

View file

@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>ChatBridge2</title> <title>React App</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View file

@ -22,9 +22,8 @@ export const setBridgeLabel = (label:string, callback: CallableFunction) => {
}) })
}).then(res => res.json()) }).then(res => res.json())
.then((res) => { .then((res) => {
callback(res) if (res.status === "success") {
// if (res.status === "success") { callback()
// callback() }
// }
}) })
} }

View file

@ -1,20 +0,0 @@
export const createChannel = (
channel_name: string,
bridge_id: string,
invite_token: string,
callback: CallableFunction
) => {
fetch('api/channel/create',{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
channel_name, bridge_id, invite_token
})
}).then(result => result.json())
.then(result => {
console.log(result);
callback(result)
})
}

View file

@ -1,13 +0,0 @@
export const groupInvite = (token: string, callback: CallableFunction) => {
fetch('api/groups/invite', {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
token: token
})
})
.then(result => result.json())
.then(result => callback(result))
}

View file

@ -15,30 +15,8 @@ export const getSlackChannels = (callback: CallableFunction) => {
.then(res => { .then(res => {
console.log('Got slack channels', res); console.log('Got slack channels', res);
if (res.status === "success"){ if (res.status === "success"){
console.log('channels api client', res.data) callback(res.data.channels.sort())
callback(res.data.channels)
} }
}) })
} }
export const joinSlackChannel = (channel: string, callback: CallableFunction) => {
fetch('api/slack/channels', {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
channel_id: channel
})
}).then(res => res.json())
.then(res => {
callback(res)
})
}
export const getBotInfo = (callback: CallableFunction) => {
fetch('api/slack/info')
.then(res => res.json())
.then(res => callback(res))
}

View file

@ -1,58 +1,26 @@
import Grid from "@mui/material/Grid"; import {Grid} from "@mui/material";
import TextField from "@mui/material/TextField"; import {TextField} from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import {setBridgeLabel} from "../../api/bridge";
import {useState} from "react";
export const JoinBridge = ({ export const JoinBridge = ({
bridge, setBridge, setStepComplete, stepComplete bridge, setBridge
}) => { }) => {
const onSetLabel = (label) => {
const [errored, setErrored] = useState(false); setBridge({...bridge, Label:label})
const [errorMessage, setErrorMessage] = useState('')
const updateBridgeLabel = () => {
setBridgeLabel(bridge.Label, (res) => {
if (res.status === 'success') {
setErrored(false)
setStepComplete({...stepComplete, bridge:true})
} else {
setErrored(true)
setErrorMessage(res.message)
}
})
}
const onSetBridge = (evt) => {
setBridge({...bridge, Label:evt.target.value})
} }
return ( return (
<div className={"list-row"}> <Grid container spacing={2} columns={2}>
{/*<FormControl sx={{width: "50%"}}>*/} <Grid item xs={1}>
{/* <InputLabel>Bridge Label</InputLabel>*/}
</Grid>
<Grid item xs={1}>
<TextField <TextField
value={bridge ? bridge.Label : ''} onChange={(event) => {onSetLabel(event.target.value)}}>
onChange={onSetBridge}
label={"Bridge Label"}
error={errored}
color={stepComplete.bridge ? 'success' : undefined}
helperText={errored ? errorMessage : "A short label shown before messages from this bridge"}
>
</TextField> </TextField>
<Button </Grid>
variant={"outlined"} </Grid>
onClick={updateBridgeLabel}
color={errored ? 'error': stepComplete.bridge ? 'success' : undefined}
>
{stepComplete.bridge ? 'Label Updated!' : 'Update Label!'}
</Button>
</div>
) )
} }

View file

@ -1,98 +0,0 @@
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import {stepCompleteType} from "./joinForm";
import {joinSlackChannel} from "../../api/slack";
import {useState} from "react";
export interface JoinChannelProps {
channels: Array<{
name: string;
id: string;
is_member: boolean;
}>;
selectedChannel: string;
setSelectedChannel: CallableFunction;
setStepComplete: CallableFunction;
stepComplete: stepCompleteType
}
const JoinChannel = ({
channels,
selectedChannel,
setSelectedChannel,
setStepComplete,
stepComplete
}: JoinChannelProps) => {
console.log('joinchannel channels', channels)
const [errored, setErrored] = useState(false);
const onJoinChannel = (response) => {
if (response.status === 'success'){
setErrored(false)
setStepComplete({...stepComplete, channel:true})
} else {
setErrored(true)
}
}
const onChannelChanged = (evt:any) => {
setStepComplete({...stepComplete, channel:false})
setSelectedChannel(evt.target.value)
}
const onJoinButtonClicked = () => {
let channel_id = channels.filter(chan => chan.name === selectedChannel)
.map(chan => chan.id)[0]
joinSlackChannel(channel_id, onJoinChannel)
}
return (
<div className={"list-row"}>
<FormControl sx={{width: "50%"}}>
<InputLabel>Select Channel</InputLabel>
<Select
// value={selectedChannel}
onChange={onChannelChanged}
label={"Select Channel"}
error={errored}
color={stepComplete.channel ? 'success': undefined}
>
<MenuItem value={''} key={''}>Select Channel</MenuItem>
{
channels ?
channels.map(chan => chan.name)
.sort()
.map(chan => {
return(
<MenuItem
value={chan}
key={chan}
>
{chan}
</MenuItem>)
})
: undefined
}
</Select>
</FormControl>
<Button
variant={"outlined"}
onClick={onJoinButtonClicked}
disabled={selectedChannel === ''}
color={stepComplete.channel ? 'success': undefined}
>
{stepComplete.channel ? 'Channel Joined!' : 'Join Channel'}
</Button>
</div>
)
}
export default JoinChannel

View file

@ -13,41 +13,29 @@ import TextField from "@mui/material/TextField";
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import {setBridgeLabel} from "../../api/bridge"; import {setBridgeLabel} from "../../api/bridge";
import {getSlackChannels} from "../../api/slack"; import {getSlackChannels} from "../../api/slack";
import {createChannel} from "../../api/channel";
import FormControl from "@mui/material/FormControl"; import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select" import Select from "@mui/material/Select"
import InputLabel from '@mui/material/InputLabel'; import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import {JoinBridge} from "./joinBridge";
import JoinChannel from "./joinChannel";
export interface JoinFormProps { export interface JoinFormProps {
group?: Group group: Group
invite_token: string
} }
export interface stepCompleteType { interface stepCompleteType {
login: boolean; login: boolean;
bridge: boolean; bridge: boolean;
channel: boolean; channel: boolean;
} }
export const JoinForm = ({group, invite_token}: JoinFormProps) => { export const JoinForm = ({group}: JoinFormProps) => {
const [bridgeCreated, setBridgeCreated] = useState(false);
const [bridgeErrorMessage, setBridgeErrorMessage] = useState('');
const [platform, setPlatform] = useState<string>(); const [platform, setPlatform] = useState<string>();
const [channels, setChannels] = useState<Array<{ const [channels, setChannels] = useState<string[]>();
name: string; const [selectedChannel, setSelectedChannel] = useState<string>();
id: string;
is_member: boolean;
}>
>();
const [selectedChannel, setSelectedChannel] = useState<string>('');
const [bridge, setBridge] = useState<{ const [bridge, setBridge] = useState<{
Label: string; Label: string;
Protocol: string; Protocol: string;
team_name: string; team_name: string;
id: string;
}>(); }>();
const [stepComplete, setStepComplete] = useState<stepCompleteType>({ const [stepComplete, setStepComplete] = useState<stepCompleteType>({
login: false, login: false,
@ -55,29 +43,31 @@ export const JoinForm = ({group, invite_token}: JoinFormProps) => {
channel: false channel: false
}); });
const createBridgedChannel = () => {
createChannel(selectedChannel, bridge.id, invite_token, onBridgedChannelCreated)
}
const onBridgedChannelCreated = (result) => {
if (result.status === 'success'){
setBridgeCreated(true)
setBridgeErrorMessage('')
} else {
setBridgeCreated(false)
setBridgeErrorMessage(result.message)
}
}
useEffect(() => { useEffect(() => {
if (bridge !== undefined){ if (bridge !== undefined){
setStepComplete({...stepComplete, login:true}) setStepComplete({...stepComplete, login:true})
} }
if (bridge !== undefined && channels === undefined){ if (channels === undefined){
getSlackChannels(setChannels) getSlackChannels(setChannels)
} }
}, [bridge]) }, [bridge])
const onSetBridge = (evt) => {
setBridge({...bridge, Label:evt.target.value})
}
const updateBridgeLabel = () => {
setBridgeLabel(bridge.Label, () => {setStepComplete({...stepComplete, bridge:true})})
}
const handleSelectChannel = (evt) => {
}
const joinChannel = () => {
}
return ( return (
<> <>
<header className={'section-header'}> <header className={'section-header'}>
@ -96,44 +86,81 @@ export const JoinForm = ({group, invite_token}: JoinFormProps) => {
/> />
</JoinStep> </JoinStep>
<JoinStep <JoinStep
title={"2) Configure Bridge"} title={"2) Set Bridge Label"}
details={"Settings for all channels bridged from this platform"} details={"A short identifier shown before your messages"}
id={'bridge'} id={'bridge'}
disabled={!stepComplete.login} disabled={!stepComplete.login}
completed={stepComplete.bridge} completed={stepComplete.bridge}
> >
<JoinBridge <Grid container spacing={2} columns={4} alignItems="center">
bridge={bridge} <Grid item xs={1}>
setBridge={setBridge} <Typography>Label:</Typography>
setStepComplete={setStepComplete} </Grid>
stepComplete={stepComplete} <Grid item xs={2}>
/> <TextField
value={bridge?.Label}
onChange={onSetBridge}>
</TextField>
</Grid>
<Grid item xs={1}>
<Button
variant={"outlined"}
onClick={updateBridgeLabel}>
Update Label!
</Button>
</Grid>
</Grid>
</JoinStep> </JoinStep>
<JoinStep <JoinStep
title={"3) Select a channel!"} title={"3) Select a channel!"}
details={"The bot will join :)"} details={"The bot will join :)"}
id={'channel'} id={'bridge'}
disabled={!stepComplete.login} disabled={!stepComplete.login}
completed={stepComplete.channel} completed={stepComplete.channel}
> >
<JoinChannel <div className={"list-row"}>
channels={channels} <FormControl sx={{width: "50%"}}>
selectedChannel={selectedChannel} <InputLabel>Select Platform</InputLabel>
setSelectedChannel={setSelectedChannel} <Select
setStepComplete={setStepComplete} // value={platform}
stepComplete={stepComplete} onChange={(evt:any) => {setSelectedChannel(evt.target.value)}}
/> label={"Select Channel"}
>
{
channels ?
channels.map(chan => {
return(<MenuItem value={chan} key={chan}>{chan}</MenuItem>)
})
: undefined
}
</Select>
</FormControl>
<Button
variant={"outlined"}
onClick={joinChannel}>
Join Channel
</Button>
</div>
<Grid container spacing={2} columns={2} alignItems="center">
<Grid item xs={1}>
<Typography>Label:</Typography>
</Grid>
<Grid item xs={2}>
<TextField
value={bridge?.Label}
onChange={onSetBridge}>
</TextField>
</Grid>
<Grid item xs={1}>
<Button
variant={"outlined"}
onClick={updateBridgeLabel}>
Update Label!
</Button>
</Grid>
</Grid>
</JoinStep> </JoinStep>
<Button
className={'create-button'}
sx={{marginTop: "1em"}}
variant={"outlined"}
disabled={group === undefined || !stepComplete.login || !stepComplete.bridge || !stepComplete.channel}
onClick={createBridgedChannel}
color={bridgeErrorMessage !== '' ? 'error' : bridgeCreated ? 'success' : undefined}
>
{bridgeErrorMessage !== '' ? bridgeErrorMessage : bridgeCreated ? 'Bridge Created!' : 'Create New Bridge' }
</Button>
</> </>
) )
} }

View file

@ -48,6 +48,13 @@ export const JoinPlatform = ({
// pingForBridge() // pingForBridge()
} }
// const pingForBridge = () =>{
// if (bridge === undefined){
// console.log('bridge is', bridge)
// getBridgeByStateToken(setBridge);
// setTimeout(pingForBridge, 1000);
// }
// }
useEffect(() => { useEffect(() => {
const pingForBridge = () => { const pingForBridge = () => {
@ -103,7 +110,7 @@ export const JoinPlatform = ({
> >
<MenuItem value={'Slack'}>Slack</MenuItem> <MenuItem value={'Slack'}>Slack</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
{ {
installLink && platform == "Slack" ? installLink && platform == "Slack" ?

View file

@ -5,7 +5,6 @@ import Button from '@mui/material/Button';
import {JoinForm} from "../join/joinForm"; import {JoinForm} from "../join/joinForm";
import {Group} from "../../types/group"; import {Group} from "../../types/group";
import {groupInvite} from "../../api/groups";
export default function JoinPanel(){ export default function JoinPanel(){
const [text, setText] = useState(''); const [text, setText] = useState('');
@ -13,26 +12,40 @@ export default function JoinPanel(){
const [errorText, setErrorText] = useState(''); const [errorText, setErrorText] = useState('');
const [group, setGroup] = useState<Group>(undefined); const [group, setGroup] = useState<Group>(undefined);
const onGroupLogin = (response) => { const getGroup = () => {
if (response.status !== "success"){ fetch('api/groups/invite', {
setAuthError(true); method: "POST",
setErrorText(response.message); headers: {
setGroup(undefined); "Content-Type": "application/json"
} else if (response.status === "success"){ },
setAuthError(false); body: JSON.stringify({
setErrorText(''); token: text
setGroup(response.data) })
console.log(response) })
} .then(result => result.json())
.then(
response => {
if (response.status !== "success"){
setAuthError(true);
setErrorText(response.message);
setGroup(undefined);
} else if (response.status === "success"){
setAuthError(false);
setErrorText('');
setGroup(response.data)
console.log(response)
}
}
)
} }
const handleClick = () => { const handleClick = () => {
groupInvite(text, onGroupLogin) getGroup()
} }
const textChanged = (event: React.ChangeEvent<HTMLInputElement>) => { const textChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
setText(event.target.value) setText(event.target.value)
setGroup(undefined)
} }
return( return(
<div className={"JoinPanel"}> <div className={"JoinPanel"}>
@ -43,11 +56,15 @@ export default function JoinPanel(){
className={"Input"} className={"Input"}
label={"Join with invite token"} label={"Join with invite token"}
onChange={textChanged} onChange={textChanged}
// disabled={loggedIn}
// color={loggedIn ? "success" : undefined}
// type={loggedIn ? "password" : undefined}
/> />
<Button <Button
variant="contained" variant="contained"
onClick={handleClick} onClick={handleClick}
color={authError ? "error" : undefined} color={authError ? "error" : undefined}
// disabled={loggedIn}
> >
Submit Submit
</Button> </Button>
@ -55,7 +72,6 @@ export default function JoinPanel(){
{ group ? { group ?
<JoinForm <JoinForm
group = {group} group = {group}
invite_token = {text}
/> : undefined /> : undefined
} }
</div> </div>

View file

@ -1,7 +1,7 @@
.list-row { .list-row {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
align-items: flex-start; align-items: center;
gap: 2em; gap: 2em;
&>div { &>div {
@ -14,14 +14,6 @@
} }
} }
.create-button {
width: 100%;
height: 3rem;
margin: {
top: 1em;
}
}
.InputSlot { .InputSlot {
min-width: 320px; min-width: 320px;
width: 100%; width: 100%;

View file

@ -12,7 +12,6 @@ import groupRoutes from "./routes/group.routes";
import slackRoutes from "./routes/slack.routes"; import slackRoutes from "./routes/slack.routes";
import authRoutes from "./routes/auth.routes"; import authRoutes from "./routes/auth.routes";
import bridgeRoutes from './routes/bridge.routes'; import bridgeRoutes from './routes/bridge.routes';
import channelRoutes from './routes/channel.routes';
@ -40,8 +39,7 @@ AppDataSource.initialize()
// }); // });
app.use('/slack', slackRoutes); app.use('/slack', slackRoutes);
app.use('/bridge', bridgeRoutes); app.use('/bridge', bridgeRoutes)
app.use('/channel', channelRoutes);
app.all('*', (req: Request, res: Response, next: NextFunction) => { app.all('*', (req: Request, res: Response, next: NextFunction) => {
next(new AppError(404, `Route ${req.originalUrl} not found`)); next(new AppError(404, `Route ${req.originalUrl} not found`));

View file

@ -16,8 +16,7 @@ export const getBridgeHandler = async(
select: { select: {
Protocol: true, Protocol: true,
Label: true, Label: true,
team_name: true, team_name: true
id: true
} }
}) })
if (!bridge){ if (!bridge){

View file

@ -1,96 +0,0 @@
import {NextFunction, Request, Response} from 'express';
import {CreateGroupInput, GetGroupInput, getGroupSchema} from "../schemas/group.schema";
import config from 'config';
import {Group} from "../entities/group.entity";
import {Bridge} from "../entities/bridge.entity";
import {Channel} from "../entities/channel.entity";
import AppError from "../errors/appError";
import {randomBytes} from "crypto";
import {AppDataSource} from "../db/data-source";
import {CreateChannelInput} from "../schemas/channel.schema";
import {channel} from "diagnostics_channel";
const groupRepository = AppDataSource.getRepository(Group)
const bridgeRepository = AppDataSource.getRepository(Bridge)
const channelRepository = AppDataSource.getRepository(Channel)
import MatterbridgeManager from '../matterbridge/process';
export const createChannelHandler = async(
req: Request<{}, {}, CreateChannelInput>,
res: Response
) => {
try {
// Validate that we were given the right bridge
let bridge = await bridgeRepository.findOne({
where: {state_token: req.session.state_token},
relations: {channels: true}
})
if (bridge.id !== req.body.bridge_id) {
return res.status(403).json({
status: 'failed',
message: 'Bridge id does not match session token'
})
}
let group = await groupRepository.findOne({
where: {invite_token: req.body.invite_token},
relations: {channels: true}
})
let channel = await channelRepository.findOneBy({
name: req.body.channel_name,
bridge: {id: bridge.id},
group: {id: group.id}
})
if (!channel) {
// create new
channel = new Channel()
}
// update properties
channel.name = req.body.channel_name
channel.bridge = bridge
channel.group = group
console.log('have channel', channel)
console.log('have group', group)
console.log('have bridge', bridge)
channel = await channelRepository.save(channel)
await groupRepository
.createQueryBuilder()
.relation(Group, 'channels')
.of(group)
.add(channel)
await bridgeRepository
.createQueryBuilder()
.relation(Bridge, 'channels')
.of(bridge)
.add(channel)
console.log('newchannel', channel)
console.log('saved group', group)
await MatterbridgeManager.spawnProcess(group.name)
return res.status(200).json({
status: 'success',
data: channel
})
} catch (err) {
console.log('failed to create channe', err)
return res.status(502).json({
status: 'failed',
message: 'failed to create channel'
})
}
}

View file

@ -9,7 +9,7 @@ import { randomUUID } from "crypto";
import {log} from "util"; import {log} from "util";
import {Join} from "typeorm"; import {Join} from "typeorm";
const scopes = ['bot', 'channels:write', 'channels:write.invites', 'chat:write:bot', 'chat:write:user', 'users.profile:read']; const scopes = ['bot', 'channels:write', 'chat:write:bot', 'chat:write:user', 'users.profile:read'];
const bridgeRepository = AppDataSource.getRepository(Bridge) const bridgeRepository = AppDataSource.getRepository(Bridge)
const groupRepository = AppDataSource.getRepository(Group) const groupRepository = AppDataSource.getRepository(Group)
@ -22,43 +22,6 @@ const slackConfig = config.get<{
state_secret: string state_secret: string
}>('slackConfig'); }>('slackConfig');
export interface slackChannel {
id: string;
name: string;
is_channel: boolean;
is_group: boolean;
is_im: boolean;
is_mpim: boolean;
is_private: boolean;
created: number;
is_archived: boolean;
is_general: boolean;
unlinked: number;
name_normalized: string;
is_shared: boolean;
is_org_shared: boolean;
is_pending_ext_shared: boolean;
pending_shared: [];
context_team_id: string;
updated: number;
creator: string;
is_ext_shared: boolean;
shared_team_ids: string[];
is_member: boolean;
num_members: number;
}
export interface authTest {
ok: boolean;
url: string;
team: string;
user: string;
team_id: string;
user_id: string;
bot_id: string;
is_enterprise_install: boolean;
}
const installer = new InstallProvider({ const installer = new InstallProvider({
clientId: slackConfig.client_id, clientId: slackConfig.client_id,
clientSecret: slackConfig.client_secret, clientSecret: slackConfig.client_secret,
@ -72,6 +35,17 @@ const installer = new InstallProvider({
}) })
export const SlackInstallHandler = async(
req: Request,
res: Response
) => {
await installer.handleInstallPath(req, res, {}, {
scopes,
metadata: {'name':'my-slack-name','group':'MyGroup'}
});
}
export const SlackInstallLinkHandler = async( export const SlackInstallLinkHandler = async(
req: Request, req: Request,
@ -109,21 +83,17 @@ export const SlackCallbackHandler = async(
) => { ) => {
// using custom success and failure handlers // using custom success and failure handlers
const callbackOptions = { const callbackOptions = {
success: async (installation:any, installOptions:any, req:Request, res:Response) => { success: async (installation, installOptions, req, res) => {
// console.log(installation, installOptions, req.body, req.content, req.query, req.params) // console.log(installation, installOptions, req.body, req.content, req.query, req.params)
// console.log(installation.team.id, installation.team.name, installation.bot.token); // console.log(installation.team.id, installation.team.name, installation.bot.token);
console.log('istallation info', installation)
let bridge_data = { let bridge_data = {
'Protocol': 'slack', 'Protocol': 'slack',
'Label': installation.team.name, 'Label': installation.team.name,
'team_id': installation.team.id, 'team_id': installation.team.id,
'team_name': installation.team.name, 'team_name': installation.team.name,
'state_token': req.session.state_token, 'state_token': req.session.state_token,
'Token': installation.bot.token, 'Token': installation.bot.token
'user_token': installation.user.token,
'bot_id': installation.bot.userId
} }
console.log('bot token', installation.bot.token)
// check if we have an entity // check if we have an entity
let bridge = await bridgeRepository.findOneBy({Token: installation.bot.token}) let bridge = await bridgeRepository.findOneBy({Token: installation.bot.token})
@ -133,30 +103,16 @@ export const SlackCallbackHandler = async(
await bridgeRepository.save(bridge); await bridgeRepository.save(bridge);
console.log('created bridge') console.log('created bridge')
} else { } else {
// await bridgeRepository.update( await bridgeRepository.update(
// {Token: installation.bot.token}, {Token: installation.bot.token},
// { {state_token: req.session.state_token})
// state_token: req.session.state_token,
// user_token: installation.access_token,
// bot_id: installation.bot.bot_user_id
//
// },
//
// )
console.log('existing bridge', bridge)
// Don't overwrite existing label
bridge_data.Label = bridge.Label
bridge_data = {...bridge, ...bridge_data}
console.log('updating bridge with', bridge_data)
let newbridge = await bridgeRepository.save(bridge_data)
console.log('updated bridge', newbridge)
console.log('updated bridge') console.log('updated bridge')
} }
res.send('<html><body><h1>Success! Return to the chatbridge login window.</h1><h3>This tab will close in 3 seconds...</h3><script>window.setTimeout(window.close, 3000)</script></body>') res.send('<html><body><h1>Success! Return to the chatbridge login window.</h1><h3>This tab will close in 3 seconds...</h3><script>window.setTimeout(window.close, 3000)</script></body>')
}, },
failure: (error:any, installOptions:any , req:Request, res:Response) => { failure: (error, installOptions , req, res) => {
// console.log(error, installOptions, req.body, req.content, req.query, req.params) console.log(error, installOptions, req.body, req.content, req.query, req.params)
res.send('failure. Something is broken about chatbridge :('); res.send('failure. Something is broken about chatbridge :(');
}, },
} }
@ -171,7 +127,7 @@ export const getChannelsHandler = async(
res: Response res: Response
) => { ) => {
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token}) let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
// console.log('bridge data', bridge) console.log('bridge data', bridge)
try { try {
fetch('https://slack.com/api/conversations.list', { fetch('https://slack.com/api/conversations.list', {
@ -184,12 +140,8 @@ export const getChannelsHandler = async(
.then((result) => { .then((result) => {
console.log('channels',result) console.log('channels',result)
let channels = result.channels.map( let channels = result.channels.map(
(chan: slackChannel) => { (chan: { name: string; }) => {
return { return chan.name
'name': chan.name,
'id': chan.id,
'is_member': chan.is_member
}
} }
); );
res.status(200).json({ res.status(200).json({
@ -212,138 +164,57 @@ export const joinChannelsHandler = async(
req: Request<{}, {}, JoinSlackChannelInput>, req: Request<{}, {}, JoinSlackChannelInput>,
res: Response res: Response
) => { ) => {
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
console.log('bridge data', bridge)
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token}) try {
console.log('joinchannel bridge', bridge) // Get channel ID from channel name
console.log('joinchannel chanid', req.body.channel_id) let channels_res = await fetch('https://slack.com/api/conversations.list', {
console.log('joinchannel userid', bridge.user_token)
console.log('joinchannel botid', bridge.bot_id)
fetch('https://slack.com/api/conversations.invite', {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": `Bearer ${bridge.user_token}` "Authorization": `Bearer ${bridge.Token}`
}
})
let channels = <{ channels: { name: string, id: string }[] }>channels_res.json()
let channel_id = channels.channels.filter(
(chan) => {
return (chan.name == req.body.channel)
}
).map(chan => chan.id)[0]
console.log('channel id', channel_id)
// Join channel from ID
fetch('https://slack.com/api/conversations.join', {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${bridge.Token}`
}, },
body: JSON.stringify({ body: JSON.stringify({
users: bridge.bot_id, channel: channel_id
channel: req.body.channel_id
}) })
}).then(result => result.json()) }).then(res => res.json())
.then(result => { .then((res) => {
console.log(result); if (res.ok) {
if (result.ok || result.error === 'already_in_channel'){ res.status(200).json({
return res.status(200).json({
status: 'success', status: 'success',
data: { data: {
id: req.body.channel_id channels
} }
}) })
} else { } else {
return res.status(502).json({ res.status(502).json({
status: 'failed', status: 'failure',
message: "could not invite bot to channel!" message: 'Couldnt join channel'
}) })
} }
}) })
// try and invite bot } catch {
}
// export const joinChannelsHandler = async(
// req: Request<{}, {}, JoinSlackChannelInput>,
// res: Response
// ) => {
// let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
// console.log('bridge data', bridge)
//
// try {
// // Get channel ID from channel name
// let channels_res = await fetch('https://slack.com/api/conversations.list', {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// "Authorization": `Bearer ${bridge.Token}`
// }
// })
// let channels = <{ channels: slackChannel[] }><unknown> await channels_res.json()
// let channel_id = channels.channels.filter(
// (chan) => {
// return (chan.name == req.body.channel)
// }
// ).map(chan => chan.id)[0]
// console.log('channel id', channel_id)
// console.log('bridge token', `Bearer ${bridge.Token}`)
// // Join channel from ID
// fetch('https://slack.com/api/conversations.join', {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// "Authorization": `Bearer ${bridge.Token}`
// },
// body: JSON.stringify({
// channel: channel_id
// })
// }).then(result => result.json())
// .then((result) => {
// console.log('result', result)
// if (res.ok) {
// res.status(200).json({
// status: 'success',
// data: {
// channels
// }
// })
// } else {
// res.status(502).json({
// status: 'failure',
// message: 'Couldnt join channel'
// })
// }
// })
//
// } catch (error) {
// console.log('channel join error', error)
// return res.status(502).json({
// status: 'failure',
// message: "Couldn't join channel!"
// })
// }
// }
export const getBotInfo = async(
req: Request,
res: Response
) => {
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
// console.log('bridge data', bridge)
try {
fetch('https://slack.com/api/auth.test', {
headers: {
'Authorization': `Bearer ${bridge.Token}`
}
}).then(response => response.json())
.then((response: authTest) => {
if (response.ok){
return res.status(200).json({
status: 'success',
data: response
})
} else {
return res.status(502).json({
status: 'failure',
message: 'unknown error getting auth test'
})
}
})
} catch (error) {
console.log('auth.test error', error)
return res.status(502).json({ return res.status(502).json({
status:'failure', status: 'failure',
message:'Couldnt get bot info' message: "Couldn't join channel!"
}) })
} }
} }

View file

@ -31,22 +31,11 @@ export class Bridge extends Model {
@Column() @Column()
state_token: string; state_token: string;
// Bot token for slack
@Column({ @Column({
unique: true unique: true
}) })
Token: string; Token: string;
@Column({
nullable: true
})
user_token: string;
@Column({
nullable:true
})
bot_id: string;
@Column({ @Column({
default: true default: true
}) })

View file

@ -17,7 +17,7 @@ export class Group extends Model {
@Column() @Column()
invite_token: string; invite_token: string;
@OneToMany(() => Channel, (channel) => channel.group) @OneToMany(() => Channel, (channel) => channel.bridge)
channels: Channel[] channels: Channel[]
} }

View file

@ -57,7 +57,7 @@ export const getGroupConfig = async (group_name: string, group:object): Promise<
where: {name: group_name}, where: {name: group_name},
relations: {channels: true} relations: {channels: true}
}) })
console.log('config group', group)
// Construct config in the gateway style for programmatic use // Construct config in the gateway style for programmatic use
let gateway = <Gateway>{ let gateway = <Gateway>{
name: group.name, name: group.name,
@ -79,7 +79,6 @@ export const getGroupConfig = async (group_name: string, group:object): Promise<
} }
}) })
} }
console.log('config group transformed', gateway)
return gateway return gateway
} }
@ -154,7 +153,6 @@ export const GatewayToTOML = (gateway: Gateway) => {
protocols[bridge.protocol][bridge.name] = TOML.Section(bridgeEntry) protocols[bridge.protocol][bridge.name] = TOML.Section(bridgeEntry)
}) })
console.log('gateway toml protocols', protocols)
return { return {
...protocols, ...protocols,
@ -174,8 +172,6 @@ export const writeTOML = (gateway_toml: object, out_file: string) => {
} }
) )
console.log('toml string', toml_string)
fs.writeFileSync(out_file, toml_string) fs.writeFileSync(out_file, toml_string)
} }

View file

@ -11,7 +11,6 @@ import config from "config";
import { writeGroupConfig } from "./config"; import { writeGroupConfig } from "./config";
import {Group} from "../entities/group.entity"; import {Group} from "../entities/group.entity";
import slugify from "slugify";
const groupRepository = AppDataSource.getRepository(Group) const groupRepository = AppDataSource.getRepository(Group)
@ -54,35 +53,21 @@ class MatterbridgeManager {
){} ){}
async spawnProcess(group_name: string) { async spawnProcess(group_name: string) {
let group_name_slug = slugify(group_name)
let group_filename = `${this.matterbridge_config_dir}/matterbridge-${group_name_slug}.toml`
await writeGroupConfig(group_name, group_filename);
console.log('matterbridge config written')
if (!this.process_list.includes(group_name_slug)){
console.log('matterbridge new process')
await pm2.start(
{
name: group_name_slug,
script: this.matterbridge_bin,
args: `-conf ${group_filename}`,
interpreter: 'none'
},
(err:any, apps:object) => {
console.log('error starting matterbridge process', err, apps)
}
)
this.process_list.push(group_name_slug)
} else {
console.log('matterbridge restarting!')
await pm2.restart(group_name_slug, (err:any, proc:any) => {
console.log('error restarting matterbridge process', err, apps)}
)
}
}
async refreshConfig(group_name: string) {
let group_filename = `${this.matterbridge_config_dir}/matterbridge-${group_name}.toml` let group_filename = `${this.matterbridge_config_dir}/matterbridge-${group_name}.toml`
await writeGroupConfig(group_name, group_filename); await writeGroupConfig(group_name, group_filename);
await pm2.start(
{
name: group_name,
script: this.matterbridge_bin,
args: `-conf ${group_filename}`,
interpreter: 'none'
},
(err:any, apps:object) => {
// TODO: Handle errors!
}
)
this.process_list.push(group_name)
} }
async spawnAll(){ async spawnAll(){

View file

@ -1,22 +0,0 @@
import express from 'express';
import {
createChannelHandler
} from "../controllers/channel.controller";
import {
requireStateToken
} from "../middleware/cookies";
import {validate} from "../middleware/validate";
import {createChannelSchema} from "../schemas/channel.schema";
const router = express.Router();
router.route('/create')
.post(requireStateToken,
validate(createChannelSchema),
createChannelHandler)
export default router

View file

@ -4,8 +4,7 @@ import {
SlackInstallLinkHandler, SlackInstallLinkHandler,
SlackCallbackHandler, SlackCallbackHandler,
getChannelsHandler, getChannelsHandler,
joinChannelsHandler, joinChannelsHandler
getBotInfo
} from '../controllers/slack.controller' } from '../controllers/slack.controller'
import { import {
@ -24,8 +23,5 @@ router.route('/channels')
.get(requireStateToken, getChannelsHandler) .get(requireStateToken, getChannelsHandler)
.post(requireStateToken, joinChannelsHandler) .post(requireStateToken, joinChannelsHandler)
router.route('/info')
.get(requireStateToken, getBotInfo)
export default router export default router

View file

@ -1,17 +0,0 @@
import { object, string, TypeOf } from 'zod';
export const createChannelSchema = object({
body: object({
invite_token: string({
required_error: "Group invite token required to create channel!"
}),
bridge_id: string({
required_error: "Bridge column ID required to create channel!"
}),
channel_name: string({
required_error: "Channel name required to create channel!"
})
})
})
export type CreateChannelInput = TypeOf<typeof createChannelSchema>['body'];

View file

@ -2,8 +2,8 @@ import { object, string, TypeOf } from 'zod';
export const joinSlackChannelSchema = object({ export const joinSlackChannelSchema = object({
body: object({ body: object({
channel_id: string({ channel: string({
required_error: "Channel ID required!" required_error: "Channel name required!"
}), }),
}) })
}) })

View file

@ -2127,7 +2127,7 @@
dependencies: dependencies:
debug "^4.3.1" debug "^4.3.1"
"@pmmmwh/react-refresh-webpack-plugin@^0.5.10", "@pmmmwh/react-refresh-webpack-plugin@^0.5.3": "@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
version "0.5.10" version "0.5.10"
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz#2eba163b8e7dbabb4ce3609ab5e32ab63dda3ef8" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.10.tgz#2eba163b8e7dbabb4ce3609ab5e32ab63dda3ef8"
integrity sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA== integrity sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==
@ -9667,21 +9667,11 @@ react-is@^18.0.0, react-is@^18.2.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-refresh-typescript@^2.0.9:
version "2.0.9"
resolved "https://registry.yarnpkg.com/react-refresh-typescript/-/react-refresh-typescript-2.0.9.tgz#f8a86efcb34f8d717100230564b9b57477d74b10"
integrity sha512-chAnOO4vpxm/3WkgOVmti+eN8yUtkJzeGkOigV6UA9eDFz12W34e/SsYe2H5+RwYJ3+sfSZkVbiXcG1chEBxlg==
react-refresh@^0.11.0: react-refresh@^0.11.0:
version "0.11.0" version "0.11.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
react-scripts@5.0.1: react-scripts@5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"