Compare commits
2 commits
7725860a96
...
3671cbd7ae
Author | SHA1 | Date | |
---|---|---|---|
|
3671cbd7ae | ||
|
eea1e14906 |
26 changed files with 688 additions and 221 deletions
|
@ -2,6 +2,10 @@ 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')
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +24,10 @@ 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
|
||||||
|
@ -42,12 +50,13 @@ 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()
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -32,8 +32,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
|
|
@ -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>React App</title>
|
<title>ChatBridge2</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>
|
||||||
|
|
|
@ -22,8 +22,9 @@ export const setBridgeLabel = (label:string, callback: CallableFunction) => {
|
||||||
})
|
})
|
||||||
}).then(res => res.json())
|
}).then(res => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "success") {
|
callback(res)
|
||||||
callback()
|
// if (res.status === "success") {
|
||||||
}
|
// callback()
|
||||||
|
// }
|
||||||
})
|
})
|
||||||
}
|
}
|
20
client/src/api/channel.ts
Normal file
20
client/src/api/channel.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
13
client/src/api/groups.ts
Normal file
13
client/src/api/groups.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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))
|
||||||
|
}
|
|
@ -15,8 +15,30 @@ 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"){
|
||||||
callback(res.data.channels.sort())
|
console.log('channels api client', res.data)
|
||||||
|
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))
|
||||||
}
|
}
|
|
@ -1,26 +1,58 @@
|
||||||
import {Grid} from "@mui/material";
|
import Grid from "@mui/material/Grid";
|
||||||
import {TextField} from "@mui/material";
|
import TextField from "@mui/material/TextField";
|
||||||
|
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
|
bridge, setBridge, setStepComplete, stepComplete
|
||||||
}) => {
|
}) => {
|
||||||
const onSetLabel = (label) => {
|
|
||||||
setBridge({...bridge, Label:label})
|
const [errored, setErrored] = useState(false);
|
||||||
|
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 (
|
||||||
<Grid container spacing={2} columns={2}>
|
<div className={"list-row"}>
|
||||||
<Grid item xs={1}>
|
{/*<FormControl sx={{width: "50%"}}>*/}
|
||||||
|
{/* <InputLabel>Bridge Label</InputLabel>*/}
|
||||||
</Grid>
|
|
||||||
<Grid item xs={1}>
|
|
||||||
<TextField
|
<TextField
|
||||||
onChange={(event) => {onSetLabel(event.target.value)}}>
|
value={bridge ? bridge.Label : ''}
|
||||||
|
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>
|
||||||
</Grid>
|
<Button
|
||||||
</Grid>
|
variant={"outlined"}
|
||||||
|
onClick={updateBridgeLabel}
|
||||||
|
color={errored ? 'error': stepComplete.bridge ? 'success' : undefined}
|
||||||
|
>
|
||||||
|
{stepComplete.bridge ? 'Label Updated!' : 'Update Label!'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
98
client/src/components/join/joinChannel.tsx
Normal file
98
client/src/components/join/joinChannel.tsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
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
|
|
@ -13,29 +13,41 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
interface stepCompleteType {
|
export interface stepCompleteType {
|
||||||
login: boolean;
|
login: boolean;
|
||||||
bridge: boolean;
|
bridge: boolean;
|
||||||
channel: boolean;
|
channel: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JoinForm = ({group}: JoinFormProps) => {
|
export const JoinForm = ({group, invite_token}: JoinFormProps) => {
|
||||||
|
const [bridgeCreated, setBridgeCreated] = useState(false);
|
||||||
|
const [bridgeErrorMessage, setBridgeErrorMessage] = useState('');
|
||||||
const [platform, setPlatform] = useState<string>();
|
const [platform, setPlatform] = useState<string>();
|
||||||
const [channels, setChannels] = useState<string[]>();
|
const [channels, setChannels] = useState<Array<{
|
||||||
const [selectedChannel, setSelectedChannel] = useState<string>();
|
name: 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,
|
||||||
|
@ -43,31 +55,29 @@ export const JoinForm = ({group}: 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 (channels === undefined){
|
if (bridge !== undefined && 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'}>
|
||||||
|
@ -86,81 +96,44 @@ export const JoinForm = ({group}: JoinFormProps) => {
|
||||||
/>
|
/>
|
||||||
</JoinStep>
|
</JoinStep>
|
||||||
<JoinStep
|
<JoinStep
|
||||||
title={"2) Set Bridge Label"}
|
title={"2) Configure Bridge"}
|
||||||
details={"A short identifier shown before your messages"}
|
details={"Settings for all channels bridged from this platform"}
|
||||||
id={'bridge'}
|
id={'bridge'}
|
||||||
disabled={!stepComplete.login}
|
disabled={!stepComplete.login}
|
||||||
completed={stepComplete.bridge}
|
completed={stepComplete.bridge}
|
||||||
>
|
>
|
||||||
<Grid container spacing={2} columns={4} alignItems="center">
|
<JoinBridge
|
||||||
<Grid item xs={1}>
|
bridge={bridge}
|
||||||
<Typography>Label:</Typography>
|
setBridge={setBridge}
|
||||||
</Grid>
|
setStepComplete={setStepComplete}
|
||||||
<Grid item xs={2}>
|
stepComplete={stepComplete}
|
||||||
<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={'bridge'}
|
id={'channel'}
|
||||||
disabled={!stepComplete.login}
|
disabled={!stepComplete.login}
|
||||||
completed={stepComplete.channel}
|
completed={stepComplete.channel}
|
||||||
>
|
>
|
||||||
<div className={"list-row"}>
|
<JoinChannel
|
||||||
<FormControl sx={{width: "50%"}}>
|
channels={channels}
|
||||||
<InputLabel>Select Platform</InputLabel>
|
selectedChannel={selectedChannel}
|
||||||
<Select
|
setSelectedChannel={setSelectedChannel}
|
||||||
// value={platform}
|
setStepComplete={setStepComplete}
|
||||||
onChange={(evt:any) => {setSelectedChannel(evt.target.value)}}
|
stepComplete={stepComplete}
|
||||||
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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -48,13 +48,6 @@ 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 = () => {
|
||||||
|
@ -110,7 +103,7 @@ export const JoinPlatform = ({
|
||||||
>
|
>
|
||||||
<MenuItem value={'Slack'}>Slack</MenuItem>
|
<MenuItem value={'Slack'}>Slack</MenuItem>
|
||||||
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
{
|
{
|
||||||
installLink && platform == "Slack" ?
|
installLink && platform == "Slack" ?
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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('');
|
||||||
|
@ -12,40 +13,26 @@ 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 getGroup = () => {
|
const onGroupLogin = (response) => {
|
||||||
fetch('api/groups/invite', {
|
if (response.status !== "success"){
|
||||||
method: "POST",
|
setAuthError(true);
|
||||||
headers: {
|
setErrorText(response.message);
|
||||||
"Content-Type": "application/json"
|
setGroup(undefined);
|
||||||
},
|
} else if (response.status === "success"){
|
||||||
body: JSON.stringify({
|
setAuthError(false);
|
||||||
token: text
|
setErrorText('');
|
||||||
})
|
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 = () => {
|
||||||
getGroup()
|
groupInvite(text, onGroupLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
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"}>
|
||||||
|
@ -56,15 +43,11 @@ 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>
|
||||||
|
@ -72,6 +55,7 @@ export default function JoinPanel(){
|
||||||
{ group ?
|
{ group ?
|
||||||
<JoinForm
|
<JoinForm
|
||||||
group = {group}
|
group = {group}
|
||||||
|
invite_token = {text}
|
||||||
/> : undefined
|
/> : undefined
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.list-row {
|
.list-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 2em;
|
gap: 2em;
|
||||||
|
|
||||||
&>div {
|
&>div {
|
||||||
|
@ -14,6 +14,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.create-button {
|
||||||
|
width: 100%;
|
||||||
|
height: 3rem;
|
||||||
|
margin: {
|
||||||
|
top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.InputSlot {
|
.InputSlot {
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +40,8 @@ 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`));
|
||||||
|
|
|
@ -16,7 +16,8 @@ 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){
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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', 'chat:write:bot', 'chat:write:user', 'users.profile:read'];
|
const scopes = ['bot', 'channels:write', 'channels:write.invites', '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,6 +22,43 @@ 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,
|
||||||
|
@ -35,17 +72,6 @@ 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,
|
||||||
|
@ -83,17 +109,21 @@ export const SlackCallbackHandler = async(
|
||||||
) => {
|
) => {
|
||||||
// using custom success and failure handlers
|
// using custom success and failure handlers
|
||||||
const callbackOptions = {
|
const callbackOptions = {
|
||||||
success: async (installation, installOptions, req, res) => {
|
success: async (installation:any, installOptions:any, req:Request, res:Response) => {
|
||||||
// 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})
|
||||||
|
@ -103,16 +133,30 @@ 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, installOptions , req, res) => {
|
failure: (error:any, installOptions:any , req:Request, res:Response) => {
|
||||||
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 :(');
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -127,7 +171,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', {
|
||||||
|
@ -140,8 +184,12 @@ 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: { name: string; }) => {
|
(chan: slackChannel) => {
|
||||||
return chan.name
|
return {
|
||||||
|
'name': chan.name,
|
||||||
|
'id': chan.id,
|
||||||
|
'is_member': chan.is_member
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
|
@ -164,57 +212,138 @@ 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)
|
|
||||||
|
|
||||||
try {
|
let bridge = await bridgeRepository.findOneBy({state_token: req.session.state_token})
|
||||||
// Get channel ID from channel name
|
console.log('joinchannel bridge', bridge)
|
||||||
let channels_res = await fetch('https://slack.com/api/conversations.list', {
|
console.log('joinchannel chanid', req.body.channel_id)
|
||||||
|
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.Token}`
|
"Authorization": `Bearer ${bridge.user_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({
|
||||||
channel: channel_id
|
users: bridge.bot_id,
|
||||||
|
channel: req.body.channel_id
|
||||||
})
|
})
|
||||||
}).then(res => res.json())
|
}).then(result => result.json())
|
||||||
.then((res) => {
|
.then(result => {
|
||||||
if (res.ok) {
|
console.log(result);
|
||||||
res.status(200).json({
|
if (result.ok || result.error === 'already_in_channel'){
|
||||||
|
return res.status(200).json({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: {
|
data: {
|
||||||
channels
|
id: req.body.channel_id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
res.status(502).json({
|
return res.status(502).json({
|
||||||
status: 'failure',
|
status: 'failed',
|
||||||
message: 'Couldnt join channel'
|
message: "could not invite bot to channel!"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch {
|
// try and invite bot
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: "Couldn't join channel!"
|
message:'Couldnt get bot info'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,22 @@ 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
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,7 +17,7 @@ export class Group extends Model {
|
||||||
@Column()
|
@Column()
|
||||||
invite_token: string;
|
invite_token: string;
|
||||||
|
|
||||||
@OneToMany(() => Channel, (channel) => channel.bridge)
|
@OneToMany(() => Channel, (channel) => channel.group)
|
||||||
channels: Channel[]
|
channels: Channel[]
|
||||||
|
|
||||||
}
|
}
|
|
@ -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,6 +79,7 @@ export const getGroupConfig = async (group_name: string, group:object): Promise<
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
console.log('config group transformed', gateway)
|
||||||
return gateway
|
return gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +154,7 @@ 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,
|
||||||
|
@ -172,6 +174,8 @@ 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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,21 +54,35 @@ 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(){
|
||||||
|
|
22
server/src/routes/channel.routes.ts
Normal file
22
server/src/routes/channel.routes.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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
|
|
@ -4,7 +4,8 @@ import {
|
||||||
SlackInstallLinkHandler,
|
SlackInstallLinkHandler,
|
||||||
SlackCallbackHandler,
|
SlackCallbackHandler,
|
||||||
getChannelsHandler,
|
getChannelsHandler,
|
||||||
joinChannelsHandler
|
joinChannelsHandler,
|
||||||
|
getBotInfo
|
||||||
} from '../controllers/slack.controller'
|
} from '../controllers/slack.controller'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -23,5 +24,8 @@ 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
|
17
server/src/schemas/channel.schema.ts
Normal file
17
server/src/schemas/channel.schema.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
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'];
|
|
@ -2,8 +2,8 @@ import { object, string, TypeOf } from 'zod';
|
||||||
|
|
||||||
export const joinSlackChannelSchema = object({
|
export const joinSlackChannelSchema = object({
|
||||||
body: object({
|
body: object({
|
||||||
channel: string({
|
channel_id: string({
|
||||||
required_error: "Channel name required!"
|
required_error: "Channel ID required!"
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -2127,7 +2127,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
|
|
||||||
"@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
|
"@pmmmwh/react-refresh-webpack-plugin@^0.5.10", "@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,11 +9667,21 @@ 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"
|
||||||
|
|
Loading…
Reference in a new issue