Skip to content

Commit

Permalink
Automatically post attachments. Addresses #7
Browse files Browse the repository at this point in the history
  • Loading branch information
turt2live committed Mar 2, 2017
1 parent 66793e6 commit ed273ca
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -13,6 +13,8 @@ config/production.yaml
config/development.yaml
test.eml

db/attachments/*.attachment

# Logs
logs
*.log
Expand Down
31 changes: 31 additions & 0 deletions config/default.yaml
Expand Up @@ -79,6 +79,37 @@ defaultRoomConfig:
# being posted in the rough to the room.
plaintextOnly: false

# Attachment processing settings
attachments:
# If true, the bot will post allowed attachments to the room
post: true

# If true, the bot will accept all types of attachments (ignoring whatever is defined
# in the allowedTypes list). Anything blocked by blockedTypes will not be sent.
allowAllTypes: false

# MIME type mapping to Matrix events. Defaults to m.file if not listed here.
# This is intended for advanced usage of the bot.
contentMapping:
"image/jpeg": "m.image"
"image/jpg": "m.image"
"image/png": "m.image"
"image/gif": "m.image"
"video/mp4": "m.video"
"audio/aac": "m.audio"

# The allowed MIME types to be posted to the room. Only applies if allowAllTypes is off
allowedTypes:
- 'image/png'
- 'image/jpeg'
- 'image/jpg'
- 'image/gif'
- 'application/pdf'

# The blocked MIME types. Attachments of these types will be skipped.
blockedTypes:
- 'application/exe'

# If true, the bot will post fragments to the room. Fragments are replies, quoted parts of
# the message, signatures, and other parts that don't appear to be part of the "primary"
# message. By default this is turned off to avoid flooding the room with quoted messages.
Expand Down
Empty file added db/attachments/.keep
Empty file.
2 changes: 2 additions & 0 deletions db/attachments/README.txt
@@ -0,0 +1,2 @@
This folder contains all of the saved attachments for messages. The file names are generated to avoid conflicts - please
see the database to see which files belong to which messages.
30 changes: 30 additions & 0 deletions migrations/20170302051233-add-attachments-table.js
@@ -0,0 +1,30 @@
'use strict';

var dbm;
var type;
var seed;

/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate;
type = dbm.dataType;
seed = seedLink;
};

exports.up = function (db) {
return db.createTable('attachments', {
id: {type: 'string', primaryKey: true},
email_id: 'string'
});
};

exports.down = function (db) {
return db.dropTable('attachments');
};

exports._meta = {
"version": 1
};
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -19,6 +19,7 @@
"request": "^2.79.0",
"sanitize-html": "^1.14.1",
"sqlite3": "^3.1.8",
"streamifier": "^0.1.1",
"striptags": "^3.0.1",
"uuid": "^3.0.1"
}
Expand Down
22 changes: 22 additions & 0 deletions src/DataStore.js
Expand Up @@ -2,6 +2,8 @@ var sqlite3 = require('sqlite3');
var uuid = require("uuid");
var DBMigrate = require("db-migrate");
var log = require("npmlog");
var fs = require("fs");
var path = require("path");

/**
* Represents the storage mechanism for emails and other information
Expand Down Expand Up @@ -107,6 +109,26 @@ class DataStore {
});
}

/**
* Saves an attachment
* @param {{name: string, content: Buffer}} attachment the attachment to save
* @param {String} messageId the message ID to link the attachment to
*/
saveAttachment(attachment, messageId) {
log.info("DataStore", "saveAttachment - Starting write for " + attachment.name);
return new Promise((resolve, reject) => {
var id = uuid.v4();
var target = path.join(".", "db", "attachments", id + ".attachment");
fs.writeFileSync(target, attachment.content);
log.info("DataStore", "saveAttachment - Attachment written to file: " + target);
this._db.run("INSERT INTO attachments (id, email_id) VALUES (?, ?)", id, messageId, function (generatedId, error) {
log.info("DataStore", "saveAttachment - Attachment saved to DB (" + (error ? false : true) + ": " + id + " to message " + messageId);
if (error)reject(error);
else resolve();
}.bind(this));
});
}

/**
* Retrieves a raw email message from the underlying storage system. For example, the database record for the email
* @param {string} id the record ID to lookup
Expand Down
39 changes: 39 additions & 0 deletions src/EmailHandler.js
Expand Up @@ -152,6 +152,31 @@ class EmailHandler {
continue;
}

var attachments = [];
if (message.attachments) {
var allowedTypes = (roomConfig["attachments"]["allowedTypes"] || []);
var blockedTypes = (roomConfig["attachments"]["blockedTypes"] || []);
for (var attachment of message.attachments) {
if (!roomConfig["attachments"]["allowAllTypes"] && allowedTypes.indexOf(attachment.contentType) === -1) {
log.warn("EmailHandler", "Not processing attachment '" + attachment.generatedFileName + "': Content type '" + attachment.contentType + "' is not allowed");
continue;
}

if (blockedTypes.indexOf(attachment.contentType) !== -1) {
log.warn("EmailHandler", "Not processing attachment '" + attachment.generatedFileName + "': Content type '" + attachment.contentType + "' is blocked");
continue;
}

attachments.push({
name: attachment.generatedFileName,
content: attachment.content,
post: roomConfig["attachments"]["post"],
type: attachment.contentType
});
}
} else log.warn("EmailHandler", "Not processing attachments: Either no attachments or posting is not permitted");
log.info("EmailHandler", "Found " + attachments.length + " valid attachments");

rooms.push(roomConfig.roomId);

var contentTypeHeader = (message.headers['content-type'] || "text/plain").toLowerCase();
Expand Down Expand Up @@ -192,15 +217,29 @@ class EmailHandler {
log.info("EmailHandler", "Message saved. Id = " + msg.id);
matrix.postMessageToRoom(msg, roomConfig.roomId, msgType);
msgType = MessageType.FRAGMENT;

this._saveAttachments(attachments, msg);
});
}
}

for (var attachment of attachments) {
if (!attachment.post) continue;
matrix.postAttachmentToRoom(attachment, roomConfig.roomId);
}
}
}
}, err => {
log.error("EmailHandler", "Error checking for message: " + err);
});
}

_saveAttachments(attachments, message) {
for (var attachment of attachments) {
log.info("EmailHandler", "Linking " + attachment.name + " to message " + message.id);
this._db.saveAttachment(attachment, message.id);
}
}
}

module.exports = EmailHandler;
43 changes: 43 additions & 0 deletions src/MatrixHandler.js
Expand Up @@ -4,6 +4,7 @@ var striptags = require("striptags");
var log = require("npmlog");
var util = require("./utils");
var MessageType = require("./MessageType");
var streamifier = require("streamifier");

/**
* Handles matrix traffic for the bot
Expand Down Expand Up @@ -111,6 +112,48 @@ class MatrixHandler {
log.info("MatrixHandler", "Sending message to room " + roomId);
this._client.sendMessage(roomId, mtxContent);
}

/**
* Posts an email attachment to the room given
* @param {{name: string, content: Buffer, type: string}} attachment the attachment to post
* @param {String} roomId the room ID to post to
*/
postAttachmentToRoom(attachment, roomId) {
log.info("MatrixHandler", "Posting attachment '" + attachment.name + "' to room " + roomId);
if (this._roomList.indexOf(roomId) === -1) {
log.warn("MatrixHandler", "Attempt to send message to room " + roomId + ", but not in that room");
return; // not in room - skip message
}

var config = util.getRoomConfig(roomId);
if (!config) {
log.error("MatrixHandler", "No configuration for room " + roomId + ", but a message was supposed to go there");
return;
}

var eventType = "m.file";
if (config["attachments"]["contentMapping"][attachment.type]) {
eventType = config["attachments"]["contentMapping"][attachment.type];
}

log.info("MatrixHandler", "Uploading attachment '" + attachment.name + "' to room " + roomId);
this._client.uploadContent({
stream: streamifier.createReadStream(attachment.content),
name: attachment.name
}).then(url => {
log.info("MatrixHandler", "Got MXC URL for '" + attachment.name + "': " + url);
var content = {
msgtype: eventType,
body: attachment.name,
url: JSON.parse(url).content_uri,
info: {
mimetype: attachment.type
}
};
log.info("MatrixHandler", "Posting attachment '" + attachment.name + "' to room " + roomId + " as event type " + eventType);
this._client.sendMessage(roomId, content);
});
}
}

module.exports = MatrixHandler;

0 comments on commit ed273ca

Please sign in to comment.