Skip to content

Commit

Permalink
Enable voice recordings in thread replies
Browse files Browse the repository at this point in the history
  • Loading branch information
streamer45 committed Apr 11, 2020
1 parent 0554ae5 commit cc9eb51
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 58 deletions.
4 changes: 3 additions & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"id": "com.mattermost.voice",
"name": "Voice",
"description": "Mattermost plugin to enable voice messaging.",
"version": "0.1.1",
"homepage_url": "https://github.com/streamer45/mattermost-plugin-voice",
"support_url": "https://github.com/streamer45/mattermost-plugin-voice/issues",
"version": "0.2.0",
"min_server_version": "5.12.0",
"server": {
"executables": {
Expand Down
4 changes: 3 additions & 1 deletion server/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const manifestStr = `
"id": "com.mattermost.voice",
"name": "Voice",
"description": "Mattermost plugin to enable voice messaging.",
"version": "0.1.1",
"homepage_url": "https://github.com/streamer45/mattermost-plugin-voice",
"support_url": "https://github.com/streamer45/mattermost-plugin-voice/issues",
"version": "0.2.0",
"min_server_version": "5.12.0",
"server": {
"executables": {
Expand Down
38 changes: 16 additions & 22 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"path"
"strings"
"regexp"
"sync"
"time"

Expand All @@ -26,31 +24,28 @@ type Plugin struct {
configuration *configuration
}

var re *regexp.Regexp = regexp.MustCompile(`^\/recordings\/([A-Za-z0-9]+)$`)

func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
config := p.getConfiguration()
userID := r.Header.Get("Mattermost-User-Id")
if userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

if r.URL.Path == "/config" {
config := p.getConfiguration()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(config)
return
} else if strings.HasPrefix(r.URL.Path, "/recordings/") {

userID := r.Header.Get("Mattermost-User-Id")

if userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

_, postID := path.Split(r.URL.Path)

} else if matches := re.FindStringSubmatch(r.URL.Path); len(matches) == 2 {
postID := matches[1]
if len(postID) != 26 {
http.NotFound(w, r)
return
}

post, err := p.API.GetPost(postID)

if err != nil || post.DeleteAt > 0 || post.Type != "custom_voice" {
http.NotFound(w, r)
return
Expand All @@ -61,17 +56,19 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
return
}

fileID := fmt.Sprintf("%v", post.Props["fileId"])
fileID, ok := post.Props["fileId"].(string)
if !ok {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}

info, err := p.API.GetFileInfo(fileID)

if err != nil {
http.NotFound(w, r)
return
}

file, err := p.API.GetFile(fileID)

if err != nil {
http.NotFound(w, r)
return
Expand All @@ -80,15 +77,12 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
if info.MimeType != "" {
w.Header().Set("Content-Type", info.MimeType)
}

w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'")

reader := bytes.NewReader(file)

secs := int64(info.UpdateAt / 1000)
ns := int64((info.UpdateAt - (secs * 1000)) * 1000000)

http.ServeContent(w, r, info.Name, time.Unix(secs, ns), reader)

return
Expand Down
2 changes: 1 addition & 1 deletion webapp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "voice",
"version": "0.1.1",
"version": "0.2.0",
"description": "Mattermost plugin to enable voice messaging.",
"main": "src/index.js",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions webapp/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ export const cancelRecording = () => (dispatch) => {
closeRecordingModal()(dispatch);
};

export const sendRecording = () => (dispatch) => {
export const sendRecording = (channelId, rootId) => (dispatch) => {
// console.log('send recording');
dispatch({
type: STOP_RECORDING,
});
Client.sendRecording().then(() => {
Client.sendRecording(channelId, rootId).then(() => {
// console.log('DONE');
});
closeRecordingModal()(dispatch);
Expand Down
53 changes: 29 additions & 24 deletions webapp/src/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export default class Client {
// console.log('client: recorder initialized');
});
});
this.channelId = null;
this.recorder.on('maxduration', () => {
if (this.timerID) {
clearInterval(this.timerID);
Expand All @@ -34,13 +33,9 @@ export default class Client {
});
}

startRecording(channelId) {
if (!channelId) {
return Promise.reject(new Error('channel id is required'));
}
startRecording() {
// console.log('client: start recording');
this._recording = null;
this.channelId = channelId;
return this.recorder.start().then(() => {
this.timerID = setInterval(() => {
if (this._onUpdate && this.recorder.startTime) {
Expand Down Expand Up @@ -68,39 +63,49 @@ export default class Client {
return this.recorder.cancel();
}

_sendRecording({blob, duration}) {
const filename = `${new Date().getTime() - duration}.mp3`;
_sendRecording({channelId, rootId, recording}) {
const filename = `${new Date().getTime() - recording.duration}.mp3`;
return request.
post(Client4.getFilesRoute()).
set(Client4.getOptions({method: 'post'}).headers).
attach('files', blob, filename).
field('channel_id', this.channelId).
attach('files', recording.blob, filename).
field('channel_id', channelId).
accept('application/json').then((res) => {
const fileId = res.body.file_infos[0].id;
const data = {
channel_id: channelId,
root_id: rootId,
message: 'Voice Message',
type: 'custom_voice',
props: {
fileId: res.body.file_infos[0].id,
duration: recording.duration,
},
};
return request.post(Client4.getPostsRoute()).
set(Client4.getOptions({method: 'post'}).headers).
send({
channel_id: this.channelId,
message: '',
type: 'custom_voice',
props: {
fileId,
duration,
},
}).accept('application/json');
send(data).
accept('application/json');
});
}

sendRecording() {
if (!this.channelId) {
sendRecording(channelId, rootId) {
if (!channelId) {
return Promise.reject(new Error('channel id is required'));
}
// console.log('client: send recording');
if (this._recording) {
return this._sendRecording(this._recording);
return this._sendRecording({
channelId,
rootId,
recording: this._recording,
});
}
return this.recorder.stop().then((res) => {
return this._sendRecording(res);
return this._sendRecording({
channelId,
rootId,
recording: res,
});
});
}

Expand Down
1 change: 0 additions & 1 deletion webapp/src/components/post_type.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ div.voice-player {
padding: 4px;
border-radius: 4px;
margin: 2px 0;
/*width: 160px;*/
}

progress.voice-player-progress {
Expand Down
12 changes: 8 additions & 4 deletions webapp/src/components/root/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import Root from './root';
const connect = window.ReactRedux.connect;
const bindActionCreators = window.Redux.bindActionCreators;

const mapStateToProps = (state) => ({
visible: isRecordingModalVisible(state),
duration: recordingDuration(state),
});
const mapStateToProps = (state) => {
return {
visible: isRecordingModalVisible(state),
duration: recordingDuration(state),
channelId: state.entities.channels.currentChannelId,
rootId: state.views.rhs.selectedPostId,
};
};

const mapDispatchToProps = (dispatch) => bindActionCreators({
cancel: cancelRecording,
Expand Down
9 changes: 8 additions & 1 deletion webapp/src/components/root/root.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export default class Root extends React.Component {
static propTypes = {
visible: PropTypes.bool.isRequired,
duration: PropTypes.number.isRequired,
channelId: PropTypes.string.isRequired,
rootId: PropTypes.string,
cancel: PropTypes.func.isRequired,
send: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
Expand All @@ -34,6 +36,10 @@ export default class Root extends React.Component {
return pad2nozero(secs / 60) + ':' + pad2(secs % 60);
}

send = () => {
this.props.send(this.props.channelId, this.props.rootId);
}

render() {
if (!this.props.visible) {
return null;
Expand All @@ -56,7 +62,7 @@ export default class Root extends React.Component {
>{'Cancel'}</button>
<button
className='voice-recording-button'
onClick={this.props.send}
onClick={this.send}
style={style.button}
>{'Send'}</button>
</div>
Expand All @@ -83,6 +89,7 @@ const getStyle = (theme) => ({
backgroundColor: theme.centerChannelBg,
color: theme.centerChannelColor,
border: `1px solid ${changeOpacity(theme.centerChannelColor, 0.1)}`,
fontSize: '1.3em',
},
button: {
background: 'none',
Expand Down
4 changes: 3 additions & 1 deletion webapp/src/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const manifest = JSON.parse(`
"id": "com.mattermost.voice",
"name": "Voice",
"description": "Mattermost plugin to enable voice messaging.",
"version": "0.1.1",
"homepage_url": "https://github.com/streamer45/mattermost-plugin-voice",
"support_url": "https://github.com/streamer45/mattermost-plugin-voice/issues",
"version": "0.2.0",
"min_server_version": "5.12.0",
"server": {
"executables": {
Expand Down

0 comments on commit cc9eb51

Please sign in to comment.