From 0e9f221443b50991a95307d96aadaf8bce594c51 Mon Sep 17 00:00:00 2001 From: Alex Wolfe Date: Tue, 24 Feb 2015 10:42:33 +0900 Subject: [PATCH] Support for uploading files --- .hubot_history | 0 package.json | 5 +- src/client.coffee | 126 ++++++++++++++++++++++++++++++++++++++++----- src/comment.coffee | 32 ++++++++++++ src/file.coffee | 105 +++++++++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 16 deletions(-) create mode 100644 .hubot_history create mode 100644 src/comment.coffee create mode 100644 src/file.coffee diff --git a/.hubot_history b/.hubot_history new file mode 100644 index 000000000..e69de29bb diff --git a/package.json b/package.json index 25e709c8d..38b3c49e1 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,9 @@ }, "dependencies": { "coffee-script": "~1.9.0", - "ws": "0.4.31", - "log": "1.4.0" + "form-data": "^0.2.0", + "log": "1.4.0", + "ws": "0.4.31" }, "devDependencies": { "grunt": "^0.4.5", diff --git a/src/client.coffee b/src/client.coffee index 60d3c9aa0..25f00100c 100644 --- a/src/client.coffee +++ b/src/client.coffee @@ -1,13 +1,17 @@ https = require 'https' +fs = require 'fs' +path = require 'path' querystring = require 'querystring' WebSocket = require 'ws' Log = require 'log' {EventEmitter} = require 'events' +FormData = require 'form-data' User = require './user' Team = require './team' Channel = require './channel' Group = require './group' +File = require './file' DM = require './dm' Message = require './message' Bot = require './bot' @@ -26,6 +30,7 @@ class Client extends EventEmitter @channels = {} @dms = {} @groups = {} + @files = {} @users = {} @bots = {} @@ -33,6 +38,7 @@ class Client extends EventEmitter @ws = null @_messageID = 0 @_pending = {} + @_fetchedFiles = false @_connAttempts = 0 @@ -83,6 +89,7 @@ class Client extends EventEmitter g = data.groups[k] @groups[g.id] = new Group @, g + @setFiles() # TODO: Process bots @emit 'loggedIn', @self, @team @@ -204,6 +211,23 @@ class Client extends EventEmitter _onCreateGroup: (data) => @logger.debug data + createFile: (params, callback) -> + if params.channels and typeof params.channels == 'string' + toChannelId = (channelName) => + @getChannelGroupOrDMByName(channelName).id + params.channels = params.channels.split(',').map(toChannelId).join(',') + + fs.readFile params.file, (err, content) => + return callback?(err) if err + params.filename = path.basename(params.file) + params.file = content + @_apiCall 'files.upload', params, => + @_onCreateFile arguments... + callback? arguments... + + _onCreateFile: (data) => + @logger.debug data + setPresence: (presence, callback) -> if presence is not 'away' and presence is not 'active' then return null @@ -240,6 +264,19 @@ class Client extends EventEmitter _onSetStatus: (data) => @logger.debug data + setFiles: (callback) -> + @_apiCall 'files.list', {}, => + @_onSetFiles arguments... + callback? arguments... + + _onSetFiles: (data)=> + @logger.debug data + @_fetchedFiles = true + for k of data.files + f = data.files[k] + @files[f.id] = new File @, f + @emit 'fetchFiles', data.files + # # Utility functions # @@ -333,6 +370,18 @@ class Client extends EventEmitter onStarRemoved: (data) -> @emit 'star_removed', data + getFileByID: (id) -> + @files[id] + + getCommentByID: (file_id, id) -> + @files[file_id].comments[id] + + getFiles: ()-> + if @_fetchedFiles + @files + else + null + # # Message handler callback and dispatch # @@ -492,6 +541,37 @@ class Client extends EventEmitter when 'star_removed' @emit 'star_removed', message + when "file_created" + f = message.file + @files[message.file.id] = new File @, f + @emit 'fileCreated', f + + when "file_change" + f = message.file + @files[message.file.id] = new File @, f + @emit 'fileChange', f + + when "file_deleted" + if @files[message.file_id] then @emit "fileDeleted", @files[message.file_id] + + when "file_comment_added" + if @files[message.file.id] + c = message.comment + c.file_id = message.file.id + @files[message.file.id].comments[c.id] = @files[message.file.id].newComment c + @emit 'fileCommentAdded', c + + when "file_comment_edited" + if @files[message.file.id] + c = message.comment + c.file_id = message.file.id + @files[message.file.id].comments[c.id] = @files[message.file.id].newComment c + @emit 'fileCommentEdited', c + + when "file_comment_deleted" + if @files[message.file.id] and @files[message.file.id].comments[message.comment] + @emit "fileCommentDeleted" + else if message.reply_to if message.type == 'pong' @@ -513,7 +593,7 @@ class Client extends EventEmitter @emit 'error', if message.error? then message.error else message # TODO: resend? else - if message.type not in ["file_created", "file_shared", "file_unshared", "file_comment", "file_public", "file_comment_edited", "file_comment_deleted", "file_change", "file_deleted", "star_added", "star_removed"] + if message.type not in ["file_shared", "file_unshared", "file_public", "star_added", "star_removed"] @logger.debug 'Unknown message type: '+message.type @logger.debug message @@ -532,19 +612,37 @@ class Client extends EventEmitter return message _apiCall: (method, params, callback) -> - params['token'] = @token - - post_data = querystring.stringify(params) - - options = - hostname: @host, - method: 'POST', - path: '/api/' + method, - headers: - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': post_data.length - - req = https.request(options) + params['token'] ?= @token + + req = + if params.file? + # multipart upload + fileName = params.filename + delete params.filename + form = new FormData() + for name, value of params + if name == 'file' + form.append(name, value, filename: fileName) + else + form.append(name, value) + request = https.request({ + hostname: @host, + method: 'POST', + path: '/api/' + method, + headers: form.getHeaders() + }) + form.pipe(request) + request + else + post_data = querystring.stringify(params) + https.request({ + hostname: @host, + method: 'POST', + path: '/api/' + method, + headers: + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': post_data.length + }) req.on 'response', (res) => buffer = '' diff --git a/src/comment.coffee b/src/comment.coffee new file mode 100644 index 000000000..92d0dd07a --- /dev/null +++ b/src/comment.coffee @@ -0,0 +1,32 @@ +Channel = require './channel' + +class Comment extends Channel + constructor: (@_file, data = {}) -> + super @_file._client, data + + edit: (text, callback)-> + params = { + "id": @id + "file": @_file.id + "comment": text + } + @_client._apiCall 'files.comments.edit', params, => + @_onEditComment arguments... + callback? arguments... + + _onEdit: (data)=> + @_client.logger.debug data + + delete: (callback) -> + params = { + "id": @id + "file": @_file.id + } + @_client._apiCall 'files.comments.delete', params, => + @_onDeleteComment arguments... + callback? arguments... + + _onDelete: (data)=> + @_client.logger.debug data + +module.exports = Comment \ No newline at end of file diff --git a/src/file.coffee b/src/file.coffee new file mode 100644 index 000000000..407821635 --- /dev/null +++ b/src/file.coffee @@ -0,0 +1,105 @@ +Channel = require './channel' +Comment = require './comment' + +class File extends Channel + constructor: (@_client, data = {}) -> + super @_client, data + + @comments = {} + @_fetchedComments = false + + if @comments_count > 0 + @setComments() + else + @_fetchedComments = true + + setComments: (callback) -> + params = { + "file": @id + } + @_client._apiCall 'files.info', params, => + @_onSetComments arguments... + callback? arguments... + + _onSetComments: (data) => + @_client.logger.debug data + + @_fetchedComments = true + for k of data.comments + c = data.comments[k] + @comments[c.id] = @newComment c + + @_client.emit 'fetchCommentsOn' + @id, data.comments + + newComment: (comment) -> + return new Comment @, comment + + getComments: -> + if @_fetchedComments + @comments + else + null + + edit: (params, callback) -> + params.file = @id + @_client._apiCall 'files.edit', params, => + @_onEdit arguments... + callback? arguments... + + _onEdit: (data) => + @_client.logger.debug data + + delete: (callback) -> + params = { + "file": @id + } + @_client._apiCall 'files.delete', params, => + @_onDelete arguments... + callback? arguments... + + _onDelete: (data) => + @_client.logger.debug data + + + addComment: (text, callback) -> + params = { + "file": @id, + "comment": text + } + @_client._apiCall 'files.comments.add', params, => + @_onAddComment arguments... + callback arguments... + + _onAddComment: (data)=> + @_client.logger.debug data + + # TODO Not Supported yet + + # share: (text)-> + # params = { + # "file": @id + # } + # @_client._apiCall 'files.share', params, @_onShare + + # _onShare: (data)=> + # @_client.logger.debug data + + # unshared: (text)-> + # params = { + # "file": @id + # } + # @_client._apiCall 'files.unshared', params, @_onUnshared + + # _onUnshared: (data)=> + # @_client.logger.debug data + + # public: (text)-> + # params = { + # "file": @id + # } + # @_client._apiCall 'files.public', params, @_onPublic + + # _onPublic: (data)=> + # @_client.logger.debug data + +module.exports = File \ No newline at end of file