improve URL and image handling, user/room objects, store device ID
This commit is contained in:
parent
28cb4fe513
commit
1ce6505f00
@ -1,21 +1,28 @@
|
||||
try
|
||||
{Robot,Adapter,TextMessage,User} = require 'hubot'
|
||||
{ Robot
|
||||
, Adapter
|
||||
, TextMessage } = require 'hubot'
|
||||
catch
|
||||
prequire = require('parent-require')
|
||||
{Robot,Adapter,TextMessage,User} = prequire 'hubot'
|
||||
prequire = require 'parent-require'
|
||||
{ Robot
|
||||
, Adapter
|
||||
, TextMessage } = prequire 'hubot'
|
||||
|
||||
{ LocalStorage } = require 'node-localstorage'
|
||||
|
||||
sdk = require 'matrix-js-sdk'
|
||||
request = require 'request'
|
||||
sizeOf = require 'image-size'
|
||||
ne = require 'needle'
|
||||
syn = require 'async'
|
||||
gm = require 'gm'
|
||||
url = require 'url'
|
||||
|
||||
unless localStorage?
|
||||
{LocalStorage} = require('node-localstorage')
|
||||
localStorage = new LocalStorage('./hubot-matrix.localStorage')
|
||||
|
||||
class Matrix extends Adapter
|
||||
constructor: ->
|
||||
super
|
||||
@robot.logger.info "Constructor"
|
||||
@local_storage = new LocalStorage process.env.HUBOT_MATRIX_DATA || 'matrix-data'
|
||||
@text = []
|
||||
|
||||
|
||||
handleUnknownDevices: (err) ->
|
||||
for stranger, devices of err.devices
|
||||
@ -23,95 +30,165 @@ class Matrix extends Adapter
|
||||
@robot.logger.info "Acknowledging #{stranger}'s device #{device}"
|
||||
@client.setDeviceKnown(stranger, device)
|
||||
|
||||
send: (envelope, strings...) ->
|
||||
for str in strings
|
||||
@robot.logger.info "Sending to #{envelope.room}: #{str}"
|
||||
if /^(f|ht)tps?:\/\//i.test(str)
|
||||
@sendURL envelope, str
|
||||
else
|
||||
@client.sendNotice(envelope.room, str).catch (err) =>
|
||||
|
||||
handleURL: (envelope) -> (line, done) =>
|
||||
# supported image mime types
|
||||
accepted = ['image/jpeg', 'image/png', 'image/tiff']
|
||||
|
||||
|
||||
if not url.parse(line).hostname
|
||||
return @sendText envelope, line, -> done()
|
||||
|
||||
# fetch headers
|
||||
ne.head line, follow_max: 5, (err, res) =>
|
||||
@robot.logger.info 'found url ' + line
|
||||
if err?
|
||||
@robot.logger.warning "headers download failed:\n#{err}"
|
||||
return @sendText envelope, line, -> done()
|
||||
|
||||
mimetype = res.headers['content-type'].split(';')[0]
|
||||
if not (mimetype in accepted)
|
||||
@robot.logger.info 'url ignored'
|
||||
return @sendText envelope, line, -> done()
|
||||
|
||||
@robot.logger.info 'found image: downloading...'
|
||||
|
||||
@getImage line, (buffer, info) =>
|
||||
@sendImage envelope, buffer, info, -> done()
|
||||
|
||||
|
||||
getImage: (imageURL, callback) ->
|
||||
# process the image a bit
|
||||
ne.get imageURL, follow_max: 5, (err, res, body) =>
|
||||
gm(body)
|
||||
.noProfile()
|
||||
.quality(80)
|
||||
.resize(360000,'@>')
|
||||
.toBuffer (err, buffer) =>
|
||||
@robot.logger.info 'image downloaded and processed'
|
||||
|
||||
gm(buffer).identify "%m %w %h", (err, format) ->
|
||||
[type, width, height] = format.split ' '
|
||||
callback buffer,
|
||||
mimetype: "image/" + type.toLowerCase()
|
||||
w: width
|
||||
h: height
|
||||
size: buffer.length
|
||||
url: imageURL
|
||||
|
||||
|
||||
send: (envelope, lines...) ->
|
||||
[..., last] = lines
|
||||
if typeof last is 'function'
|
||||
callback = lines.pop()
|
||||
syn.eachSeries lines, (@handleURL envelope), ->
|
||||
callback() if callback?
|
||||
|
||||
|
||||
sendText: (envelope, text, callback) ->
|
||||
@client.sendNotice(envelope.room.id, text).catch (err) =>
|
||||
if err.name == 'UnknownDeviceError'
|
||||
@handleUnknownDevices err
|
||||
@client.sendNotice(envelope.room, str)
|
||||
@client.sendNotice(envelope.room.id, text)
|
||||
callback() if callback?
|
||||
|
||||
emote: (envelope, strings...) ->
|
||||
for str in strings
|
||||
@client.sendEmoteMessage(envelope.room, str).catch (err) =>
|
||||
if err.name == 'UnknownDeviceError'
|
||||
@handleUnknownDevices err
|
||||
@client.sendEmoteMessage(envelope.room, str)
|
||||
|
||||
reply: (envelope, strings...) ->
|
||||
for str in strings
|
||||
@send envelope, "#{envelope.user.name}: #{str}"
|
||||
|
||||
topic: (envelope, strings...) ->
|
||||
for str in strings
|
||||
@client.sendStateEvent envelope.room, "m.room.topic", {
|
||||
topic: str
|
||||
}, ""
|
||||
|
||||
sendURL: (envelope, url) ->
|
||||
@robot.logger.info "Downloading #{url}"
|
||||
request url: url, encoding: null, (error, response, body) =>
|
||||
if error
|
||||
@robot.logger.info "Request error: #{JSON.stringify error}"
|
||||
else if response.statusCode == 200
|
||||
sendImage: (envelope, buffer, info, callback) ->
|
||||
try
|
||||
dims = sizeOf body
|
||||
@robot.logger.info "Image has dimensions #{JSON.stringify dims}, size #{body.length}"
|
||||
dims.type = 'jpeg' if dims.type == 'jpg'
|
||||
info = { mimetype: "image/#{dims.type}", h: dims.height, w: dims.width, size: body.length }
|
||||
@client.uploadContent(body, name: url, type: info.mimetype, rawResponse: false, onlyContentUri: true).done (content_uri) =>
|
||||
@client.sendImageMessage(envelope.room, content_uri, info, url).catch (err) =>
|
||||
@client.uploadContent(buffer, name: info.url, type: info.mimetype, rawResponse: false, onlyContentUri: true).done (content_uri) =>
|
||||
@client.sendImageMessage(envelope.room.id, content_uri, info, info.url).catch (err) =>
|
||||
if err.name == 'UnknownDeviceError'
|
||||
@handleUnknownDevices err
|
||||
@client.sendImageMessage(envelope.room, content_uri, info, url)
|
||||
@client.sendImageMessage(envelope.room.id, content_uri, info, info.url)
|
||||
catch error
|
||||
@robot.logger.info error.message
|
||||
@send envelope, " #{url}"
|
||||
@robot.logger.info "image upload failed: #{error.message}"
|
||||
finally
|
||||
@robot.logger.info 'image sent'
|
||||
callback() if callback?
|
||||
|
||||
|
||||
emote: (envelope, lines...) ->
|
||||
for line in lines
|
||||
@client.sendEmoteMessage(envelope.room.id, line).catch (err) =>
|
||||
if err.name == 'UnknownDeviceError'
|
||||
@handleUnknownDevices err
|
||||
@client.sendEmoteMessage(envelope.room.id, line)
|
||||
|
||||
|
||||
reply: (envelope, lines...) ->
|
||||
for line in lines
|
||||
@send envelope, "#{envelope.user.name}: #{line}"
|
||||
|
||||
|
||||
topic: (envelope, lines...) ->
|
||||
for line in lines
|
||||
@client.sendStateEvent envelope.room.id, "m.room.topic", topic: line, ""
|
||||
|
||||
|
||||
run: ->
|
||||
@robot.logger.info "Run #{@robot.name}"
|
||||
@robot.logger.info "starting matrix adapter"
|
||||
client = sdk.createClient(process.env.HUBOT_MATRIX_HOST_SERVER || 'https://matrix.org')
|
||||
client.login 'm.login.password', {
|
||||
client.login 'm.login.password',
|
||||
user: @robot.name
|
||||
password: process.env.HUBOT_MATRIX_PASSWORD
|
||||
}, (err, data) =>
|
||||
if err
|
||||
@robot.logger.error err
|
||||
return
|
||||
, (err, data) =>
|
||||
|
||||
return @robot.logger.error err if err?
|
||||
|
||||
@user_id = data.user_id
|
||||
@device_id = @local_storage.getItem 'device_id'
|
||||
@device_id = data.device_id unless @device_id?
|
||||
@access_token = data.access_token
|
||||
@device_id = data.device_id
|
||||
@robot.logger.info "Logged in #{@user_id} on device #{@device_id}"
|
||||
|
||||
@local_storage.setItem 'device_id', @device_id
|
||||
|
||||
@robot.logger.info "logged in #{@user_id} on device #{@device_id}"
|
||||
|
||||
@client = sdk.createClient
|
||||
baseUrl: process.env.HUBOT_MATRIX_HOST_SERVER || 'https://matrix.org'
|
||||
accessToken: @access_token
|
||||
userId: @user_id
|
||||
deviceId: @device_id
|
||||
sessionStore: new sdk.WebStorageSessionStore(localStorage)
|
||||
accessToken: @access_token
|
||||
sessionStore: new sdk.WebStorageSessionStore(@local_storage)
|
||||
|
||||
@client.on 'sync', (state, prevState, data) =>
|
||||
switch state
|
||||
when "PREPARED"
|
||||
@robot.logger.info "Synced #{@client.getRooms().length} rooms"
|
||||
@robot.logger.info "synced #{@client.getRooms().length} rooms"
|
||||
@emit 'connected'
|
||||
|
||||
createUser = (user) =>
|
||||
id: user.userId
|
||||
name: user.name
|
||||
avatar: user.getAvatarUrl @client.baseUrl, 120, 120, allowDefault: false
|
||||
|
||||
@client.on 'Room.timeline', (event, room, toStartOfTimeline) =>
|
||||
if event.getType() == 'm.room.message' and toStartOfTimeline == false
|
||||
@client.setPresence "online"
|
||||
|
||||
message = event.getContent()
|
||||
name = event.getSender()
|
||||
user = @robot.brain.userForId name
|
||||
user.room = room.roomId
|
||||
if user.name != @user_id
|
||||
@robot.logger.info "Received message: #{JSON.stringify message} in room: #{user.room}, from: #{user.name}."
|
||||
user = @robot.brain.userForId event.sender.userId
|
||||
user.name = event.sender.name
|
||||
user.avatar = event.sender.getAvatarUrl @client.baseUrl, 120, 120, allowDefault: false
|
||||
user.room =
|
||||
id: room.roomId
|
||||
name: room.name
|
||||
private: room.getJoinedMembers().length == 2
|
||||
members: room.getJoinedMembers().map createUser
|
||||
invitees: room.getMembersWithMembership("invite").map createUser
|
||||
|
||||
if user.id != @user_id
|
||||
@receive new TextMessage user, message.body if message.msgtype == "m.text"
|
||||
@client.sendReadReceipt(event) if message.msgtype != "m.text" or message.body.indexOf(@robot.name) != -1
|
||||
if message.msgtype != "m.text" or message.body.indexOf(@robot.name) != -1
|
||||
@client.sendReadReceipt(event)
|
||||
|
||||
@client.on 'RoomMember.membership', (event, member) =>
|
||||
if member.membership == 'invite' and member.userId == @user_id
|
||||
@client.joinRoom(member.roomId).done =>
|
||||
@robot.logger.info "Auto-joined #{member.roomId}"
|
||||
@robot.logger.info "auto-joined #{member.roomId}"
|
||||
|
||||
@client.startClient 0
|
||||
|
||||
|
||||
exports.use = (robot) ->
|
||||
new Matrix robot
|
||||
new Matrix robot # sentinel
|
||||
|
Loading…
Reference in New Issue
Block a user