9 |
10 | // Apply regex'es
11 | const before = matrix.event.body;
12 | matrix.event.body = matrix.event.body.replace(linkRegex, "$3");
13 | matrix.event.body = matrix.event.body.replace(linkRegex2, "$1://$2");
14 | matrix.event.body = matrix.event.body.replace(mailtoRegex, "$2");
15 | matrix.event.body = matrix.event.body.replace(mailtoRegex2, "mailto:$1");
16 |
17 | if (before !== matrix.event.body) {
18 | webhook.format = "html"; // to force the HTML layer to process it
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/processing/layers/postprocess/upload_images.js:
--------------------------------------------------------------------------------
1 | const cheerio = require("cheerio");
2 | const util = require("../../../utils");
3 | const WebhookBridge = require("../../../WebhookBridge");
4 | const Promise = require("bluebird");
5 |
6 | module.exports = (webhook, matrix) => {
7 | if (matrix.event.format !== "org.matrix.custom.html") return;
8 |
9 | const ev = cheerio.load(matrix.event.formatted_body);
10 | let images = ev("img");
11 | if (!images) return;
12 |
13 | const promises = [];
14 | images.each((i, elem) => {
15 | const image = ev(elem);
16 |
17 | let src = image.attr("src");
18 | if (!src || src.startsWith("mxc://")) return;
19 |
20 | promises.push(util.uploadContentFromUrl(WebhookBridge, src, WebhookBridge.getBotIntent()).then(mxc => {
21 | image.attr('src', mxc);
22 | }));
23 | });
24 |
25 | return Promise.all(promises).then(() => {
26 | matrix.event.formatted_body = ev("body").html();
27 | });
28 | };
29 |
30 |
--------------------------------------------------------------------------------
/src/provisioning/InteractiveProvisioner.js:
--------------------------------------------------------------------------------
1 | const ProvisioningService = require("./ProvisioningService");
2 | const LogService = require("matrix-js-snippets").LogService;
3 | const WebService = require("../WebService");
4 | const striptags = require("striptags");
5 |
6 | /**
7 | * An in-chat way to create and manage webhooks
8 | */
9 | class InteractiveProvisioner {
10 | constructor() {
11 | }
12 |
13 | /**
14 | * Sets the bridge to interact with
15 | * @param {WebhookBridge} bridge the bridge to use
16 | */
17 | setBridge(bridge) {
18 | LogService.verbose("InteractiveProvisioner", "Received bridge. Using default bot intent");
19 | this._bridge = bridge;
20 | this._intent = this._bridge.getBotIntent();
21 | }
22 |
23 | /**
24 | * Processes a request to create a webhook
25 | * @param {string} userId the user ID requesting the webhook
26 | * @param {string} roomId the room ID the webhook is for
27 | * @param {string} inRoomId the room ID the request started in
28 | */
29 | createWebhook(userId, roomId, inRoomId) {
30 | ProvisioningService.createWebhook(roomId, userId, null).then(webhook => {
31 | return this._bridge.getOrCreateAdminRoom(userId).then(adminRoom => {
32 | const url = WebService.getHookUrl(webhook.id);
33 | const htmlMessage = "Here's your webhook url for " + roomId + ": " + url + "
To send a message, POST the following JSON to that URL:" +
34 | "" +
35 | "{\n" +
36 | " \"text\": \"Hello world!\",\n" +
37 | " \"format\": \"plain\",\n" +
38 | " \"displayName\": \"My Cool Webhook\",\n" +
39 | " \"avatarUrl\": \"http://i.imgur.com/IDOBtEJ.png\"\n" +
40 | "}" +
41 | "
" +
42 | "If you run into any issues, visit #webhooks:t2bot.io";
43 |
44 | return this._intent.sendMessage(adminRoom.roomId, {
45 | msgtype: "m.notice",
46 | body: striptags(htmlMessage),
47 | format: "org.matrix.custom.html",
48 | formatted_body: htmlMessage
49 | }).then(() => {
50 | if (adminRoom.roomId !== inRoomId) {
51 | return this._intent.sendMessage(inRoomId, {
52 | msgtype: "m.notice",
53 | body: "I've sent you a private message with your hook information"
54 | });
55 | }
56 | });
57 | });
58 | }).catch(error => {
59 | if (error === ProvisioningService.PERMISSION_ERROR_MESSAGE) {
60 | return this._intent.sendMessage(inRoomId, {
61 | msgtype: "m.notice",
62 | body: "Sorry, you don't have permission to create webhooks for " + (inRoomId === roomId ? "this room" : roomId)
63 | });
64 | }
65 |
66 | LogService.error("InteractiveProvisioner", error);
67 |
68 | if (error.errcode === "M_GUEST_ACCESS_FORBIDDEN") {
69 | this._intent.sendMessage(inRoomId, {
70 | msgtype: "m.notice",
71 | body: "Room is not public or not found"
72 | });
73 | } else {
74 | this._intent.sendMessage(inRoomId, {
75 | msgtype: "m.notice",
76 | body: "There was an error processing your command."
77 | });
78 | }
79 | });
80 | }
81 | }
82 |
83 | module.exports = new InteractiveProvisioner();
--------------------------------------------------------------------------------
/src/provisioning/ProvisioningService.js:
--------------------------------------------------------------------------------
1 | const WebhookStore = require("../storage/WebhookStore");
2 | const Promise = require("bluebird");
3 | const LogService = require("matrix-js-snippets").LogService;
4 |
5 | class ProvisioningService {
6 |
7 | constructor() {
8 | this.PERMISSION_ERROR_MESSAGE = "User does not have permission to manage webhooks in this room";
9 | }
10 |
11 | /**
12 | * Sets the intent object to use for permission checking
13 | * @param {Intent} intent the intent to use
14 | */
15 | setClient(intent) {
16 | LogService.verbose("ProvisioningService", "Received intent. Using account " + intent.getClient().credentials.userId);
17 | this._intent = intent;
18 | }
19 |
20 | /**
21 | * Gets the bot user ID for the bridge
22 | * @return {string} the bot user ID
23 | */
24 | getBotUserId() {
25 | return this._intent.getClient().credentials.userId;
26 | }
27 |
28 | /**
29 | * Creates a new webhook for a room
30 | * @param {string} roomId the room ID the webhook belongs to
31 | * @param {string} userId the user trying to create the webhook
32 | * @param {String|null} label optional label for the webhook
33 | * @returns {Promise} resolves to the created webhook
34 | */
35 | async createWebhook(roomId, userId, label) {
36 | LogService.info("ProvisioningService", "Processing create hook request for " + roomId + " by " + userId);
37 | await this._intent.join(roomId);
38 | return this.hasPermission(userId, roomId)
39 | .then(() => WebhookStore.createWebhook(roomId, userId, label), () => Promise.reject(this.PERMISSION_ERROR_MESSAGE));
40 | }
41 |
42 | /***
43 | * Updates a webhook's properties
44 | * @param {string} roomId the room ID the webhook belongs to
45 | * @param {string} userId the user trying to update the webhook
46 | * @param {string} hookId the webhook ID
47 | * @param {String|null} newLabel optional new label for the webhook
48 | * @returns {Promise} resolves to the updated webhook
49 | */
50 | updateWebhook(roomId, userId, hookId, newLabel) {
51 | LogService.info("ProvisioningService", "Processing webhook update request for " + roomId + " by " + userId);
52 | return this.hasPermission(roomId, userId)
53 | .then(async () => {
54 | const webhook = await WebhookStore.getWebhook(hookId);
55 | if (webhook.roomId !== roomId) return Promise.reject(this.PERMISSION_ERROR_MESSAGE);
56 |
57 | let changed = false;
58 | if (webhook.label !== newLabel) {
59 | webhook.label = newLabel;
60 | changed = true;
61 | }
62 |
63 | if (changed) await webhook.save();
64 | return webhook;
65 | }, () => Promise.reject(this.PERMISSION_ERROR_MESSAGE));
66 | }
67 |
68 | /**
69 | * Gets a webhook
70 | * @param {string} roomId the room ID to search in
71 | * @param {string} userId the user trying to view the room's webhook
72 | * @param {string} hookId the webhook ID
73 | * @returns {Promise} resolves to the found webhook
74 | */
75 | getWebhook(roomId, userId, hookId) {
76 | LogService.info("ProvisioningService", "Processing get hook request for " + roomId + " by " + userId);
77 | return this.hasPermission(userId, roomId)
78 | .then(async () => {
79 | const webhook = await WebhookStore.getWebhook(hookId);
80 | if (webhook.roomId !== roomId) return Promise.reject(this.PERMISSION_ERROR_MESSAGE);
81 | return webhook;
82 | }, () => Promise.reject(this.PERMISSION_ERROR_MESSAGE));
83 | }
84 |
85 | /**
86 | * Gets a list of all webhooks in a room
87 | * @param {string} roomId the room ID to search in
88 | * @param {string} userId the user trying to view the room's webhooks
89 | * @returns {Promise} resolves to the list of webhooks
90 | */
91 | getWebhooks(roomId, userId) {
92 | LogService.info("ProvisioningService", "Processing list hooks request for " + roomId + " by " + userId);
93 | return this.hasPermission(userId, roomId)
94 | .then(() => WebhookStore.listWebhooks(roomId), () => Promise.reject(this.PERMISSION_ERROR_MESSAGE));
95 | }
96 |
97 | /**
98 | * Gets a list of all webhooks in a room
99 | * @param {string} roomId the room ID to search in
100 | * @param {string} userId the user trying to view the room's webhooks
101 | * @param {string} hookId the webhook ID
102 | * @returns {Promise<*>} resolves when deleted
103 | */
104 | deleteWebhook(roomId, userId, hookId) {
105 | LogService.info("ProvisioningService", "Processing delete hook (" + hookId + ") request for " + roomId + " by " + userId);
106 |
107 | return this.hasPermission(userId, roomId)
108 | .then(async () => {
109 | const webhooks = await WebhookStore.listWebhooks(roomId);
110 | if (webhooks.length === 1 && webhooks[0].id === hookId) {
111 | await this._intent.leave(roomId);
112 | }
113 | return WebhookStore.deleteWebhook(roomId, hookId)
114 | }, () => Promise.reject(this.PERMISSION_ERROR_MESSAGE));
115 | }
116 |
117 | /**
118 | * Checks to see if a user has permission to manage webhooks in a given room
119 | * @param {string} userId the user trying to manage webhooks
120 | * @param {string} roomId the room they are trying to manage webhooks in
121 | * @returns {Promise<*>} resolves if the user has permission, rejected otherwise
122 | */
123 | hasPermission(userId, roomId) {
124 | LogService.verbose("ProvisioningService", "Checking permission for " + userId + " in " + roomId);
125 | if (!this._intent) {
126 | LogService.warn("ProvisioningService", "Unable to check permission for " + userId + " in " + roomId + " because there is no Intent assigned to this service");
127 | return Promise.reject();
128 | }
129 | return this._intent.getClient().getStateEvent(roomId, "m.room.power_levels", "").then(powerLevels => {
130 | if (!powerLevels) {
131 | LogService.warn("ProvisioningService", "Unable to check permission for " + userId + " in " + roomId + " because there is no powerlevel information in the room");
132 | return Promise.reject();
133 | }
134 |
135 | const userPowerLevels = powerLevels['users'] || {};
136 |
137 | let powerLevel = userPowerLevels[userId];
138 | if (!powerLevel) powerLevel = powerLevels['users_default'];
139 | if (!powerLevel) powerLevel = 0; // default
140 |
141 | let statePowerLevel = powerLevels["state_default"];
142 | if (!statePowerLevel) {
143 | LogService.warn("ProvisioningService", "Unable to check permission for " + userId + " in " + roomId + " because the powerlevel requirement is missing for state_default");
144 | return Promise.reject();
145 | }
146 |
147 | const hasPermission = statePowerLevel <= powerLevel;
148 |
149 | LogService.verbose("ProvisioningService", "User " + userId + " in room " + roomId + " has permission? " + hasPermission + " (required powerlevel = " + statePowerLevel + ", user powerlevel = " + powerLevel + ")");
150 |
151 | return hasPermission ? Promise.resolve() : Promise.reject();
152 | });
153 | }
154 | }
155 |
156 | module.exports = new ProvisioningService();
--------------------------------------------------------------------------------
/src/storage/WebhookStore.js:
--------------------------------------------------------------------------------
1 | const DBMigrate = require("db-migrate");
2 | const LogService = require("matrix-js-snippets").LogService;
3 | const Sequelize = require('sequelize');
4 | const _ = require("lodash");
5 | const path = require("path");
6 | const randomString = require('random-string');
7 |
8 | /**
9 | * Primary storage for the Webhook Bridge
10 | */
11 | class WebhookStore {
12 |
13 | /**
14 | * Creates a new Instagram store. Call `prepare` before use.
15 | */
16 | constructor() {
17 | this._orm = null;
18 | }
19 |
20 | /**
21 | * Prepares the store for use
22 | */
23 | prepare() {
24 | const env = process.env.NODE_ENV || "development";
25 | LogService.info("WebhookStore", "Running migrations");
26 | return new Promise((resolve, reject) => {
27 | const dbConfig = require.main.require(process.env["WEBHOOKS_DB_CONFIG_PATH"] || "./config/database.json");
28 | const dbMigrate = DBMigrate.getInstance(true, {
29 | config: process.env["WEBHOOKS_DB_CONFIG_PATH"] || "./config/database.json",
30 | env: env
31 | });
32 | dbMigrate.internals.argv.count = undefined; // HACK: Fix db-migrate from using `config/config.yaml` as the count. See https://github.com/turt2live/matrix-appservice-instagram/issues/11
33 | dbMigrate.up().then(() => {
34 | let dbConfigEnv = dbConfig[env];
35 | if (!dbConfigEnv) throw new Error("Could not find DB config for " + env);
36 |
37 | if (process.env["WEBHOOKS_ENV"] === "docker") {
38 | const expectedPath = path.join("data", path.basename(dbConfigEnv.filename));
39 | if (expectedPath !== dbConfigEnv.filename) {
40 | LogService.warn("WebhookStore", "Changing database path to be " + expectedPath + " to ensure data is persisted");
41 | dbConfigEnv.filename = expectedPath;
42 | }
43 | }
44 |
45 | const opts = {
46 | host: dbConfigEnv.host || 'localhost',
47 | dialect: 'sqlite',
48 | storage: dbConfigEnv.filename,
49 | pool: {
50 | max: 5,
51 | min: 0,
52 | idle: 10000
53 | },
54 | operatorsAliases: false,
55 | logging: i => LogService.verbose("WebhookStore [SQL]", i)
56 | };
57 |
58 | this._orm = new Sequelize(dbConfigEnv.database || 'webhooks', dbConfigEnv.username, dbConfigEnv.password, opts);
59 | this._bindModels();
60 | resolve();
61 | }, err => {
62 | LogService.error("WebhookStore", err);
63 | reject(err);
64 | }).catch(err => {
65 | LogService.error("WebhookStore", err);
66 | reject(err);
67 | });
68 | });
69 | }
70 |
71 | /**
72 | * Binds all of the models to the ORM.
73 | * @private
74 | */
75 | _bindModels() {
76 | // Models
77 | this.__AccountData = this._orm.import(__dirname + "/models/account_data");
78 | this.__Webhooks = this._orm.import(__dirname + "/models/webhooks");
79 | }
80 |
81 | /**
82 | * Gets the account data for the given object
83 | * @param {string} objectId the object that has account data to look for
84 | * @returns {Promise<*>} resolves to a json object representing the key/value pairs
85 | */
86 | getAccountData(objectId) {
87 | return this.__AccountData.findAll({where: {objectId: objectId}}).then(rows => {
88 | const container = {};
89 | for (let row of rows) {
90 | container[row.key] = row.value;
91 | }
92 | return container;
93 | });
94 | }
95 |
96 | /**
97 | * Saves the object's account data. Takes the value verbatim, expecting a string.
98 | * @param {string} objectId the object this account data belongs to
99 | * @param {*} data the data to save
100 | * @returns {Promise<>} resolves when complete
101 | */
102 | setAccountData(objectId, data) {
103 | return this.__AccountData.destroy({where: {objectId: objectId}, truncate: true}).then(() => {
104 | const promises = [];
105 |
106 | const keys = _.keys(data);
107 | for (let key of keys) {
108 | promises.push(this.__AccountData.create({objectId: objectId, key: key, value: data[key]}));
109 | }
110 |
111 | return Promise.all(promises);
112 | });
113 | }
114 |
115 | /**
116 | * Creates a new webhook
117 | * @param {string} roomId the matrix room ID the webhook is for
118 | * @param {string} userId the matrix user who created the webhook
119 | * @param {string} label optional label for the webhook
120 | * @returns {Promise} resolves to the created webhook
121 | */
122 | createWebhook(roomId, userId, label) {
123 | return this.__Webhooks.create({
124 | id: randomString({length: 64}),
125 | roomId: roomId,
126 | userId: userId,
127 | label: label,
128 | });
129 | }
130 |
131 | /**
132 | * Lists all of the webhooks for a given room
133 | * @param {string} roomId the room ID to search in
134 | * @returns {Promise} resolves to a list of webhooks, may be empty
135 | */
136 | listWebhooks(roomId) {
137 | return this.__Webhooks.findAll({where: {roomId: roomId}}).then(hooks => _.map(hooks, h => new Webhook(h)));
138 | }
139 |
140 | /**
141 | * Deletes a webhook from the store
142 | * @param {string} roomId the room ID
143 | * @param {string} hookId the hook's ID
144 | * @returns {Promise<*>} resolves when the hook has been deleted
145 | */
146 | deleteWebhook(roomId, hookId) {
147 | return this.__Webhooks.destroy({where: {roomId: roomId, id: hookId}});
148 | }
149 |
150 | /**
151 | * Gets a webhook from the database by ID
152 | * @param {string} hookId the hook ID to lookup
153 | * @returns {Promise} resolves to the webhook, or null if not found
154 | */
155 | getWebhook(hookId) {
156 | return this.__Webhooks.findById(hookId).then(hook => hook ? new Webhook(hook) : null);
157 | }
158 | }
159 |
160 | class Webhook {
161 | constructor(dbFields) {
162 | this.id = dbFields.id;
163 | this.roomId = dbFields.roomId;
164 | this.userId = dbFields.userId;
165 | this.label = dbFields.label;
166 | }
167 | }
168 |
169 | module.exports = new WebhookStore();
170 |
--------------------------------------------------------------------------------
/src/storage/models/account_data.js:
--------------------------------------------------------------------------------
1 | module.exports = function (sequelize, DataTypes) {
2 | return sequelize.define('account_data', {
3 | id: {
4 | type: DataTypes.INTEGER,
5 | allowNull: false,
6 | primaryKey: true,
7 | autoIncrement: true,
8 | field: 'id'
9 | },
10 | objectId: {
11 | type: DataTypes.STRING,
12 | allowNull: false,
13 | field: 'objectId'
14 | },
15 | key: {
16 | type: DataTypes.STRING,
17 | allowNull: false,
18 | field: 'key'
19 | },
20 | value: {
21 | type: DataTypes.STRING,
22 | allowNull: false,
23 | field: 'value'
24 | }
25 | }, {
26 | tableName: 'account_data',
27 | underscored: false,
28 | timestamps: false
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/src/storage/models/webhooks.js:
--------------------------------------------------------------------------------
1 | module.exports = function (sequelize, DataTypes) {
2 | return sequelize.define('webhooks', {
3 | id: {
4 | type: DataTypes.STRING,
5 | allowNull: false,
6 | primaryKey: true,
7 | field: 'id'
8 | },
9 | roomId: {
10 | type: DataTypes.STRING,
11 | allowNull: false,
12 | field: 'roomId'
13 | },
14 | userId: {
15 | type: DataTypes.STRING,
16 | allowNull: false,
17 | field: 'userId'
18 | },
19 | label: {
20 | type: DataTypes.STRING,
21 | allowNull: true,
22 | field: 'label',
23 | },
24 | }, {
25 | tableName: 'webhooks',
26 | underscored: false,
27 | timestamps: false
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | // File based on the following implementation of utils.js in matrix-appservice-twitter by Half-Shot:
2 | // https://github.com/Half-Shot/matrix-appservice-twitter/blob/6fc01588e51a9eb9a32e14a6b0338abfd7cc32ea/src/util.js
3 |
4 | const https = require('https');
5 | const http = require('http');
6 | const Buffer = require("buffer").Buffer;
7 | const mime = require('mime');
8 | const parseDataUri = require("parse-data-uri");
9 | const request = require('request');
10 | const fs = require('fs');
11 | const mkdirp = require('mkdirp');
12 | const uuidv4 = require("uuid/v4");
13 | const path = require('path');
14 | const LogService = require("matrix-js-snippets").LogService;
15 |
16 | /**
17 | Utility module for regularly used functions.
18 | */
19 |
20 | /**
21 | * uploadContentFromUrl - Upload content from a given URL to the homeserver
22 | * and return a MXC URL.
23 | *
24 | * @param {Bridge} bridge the bridge object of this application
25 | * @param {string} url the URL to be downloaded from.
26 | * @param {string|Intent} [id] either the ID of the uploader, or a Intent object - optional.
27 | * @param {string} [name] name of the file. Will use the URL filename otherwise - optional.
28 | * @return {Promise} Promise resolving with a MXC URL.
29 | */
30 | function uploadContentFromUrl(bridge, url, id, name) {
31 | LogService.verbose("utils", "Downloading image from " + url);
32 | let contenttype;
33 | id = id || null;
34 | name = name || null;
35 | return new Promise((resolve, reject) => {
36 |
37 | const ht = url.startsWith("https") ? https : http;
38 |
39 | ht.get((url), (res) => {
40 | if (res.headers.hasOwnProperty("content-type")) {
41 | contenttype = res.headers["content-type"];
42 | } else {
43 | LogService.info("utils", "No content-type given by server, guessing based on file name.");
44 | contenttype = mime.getType(url);
45 | }
46 |
47 | if (name == null) {
48 | const parts = url.split("/");
49 | name = parts[parts.length - 1];
50 | }
51 | let size = parseInt(res.headers["content-length"]);
52 | if (isNaN(size)) {
53 | LogService.warn("UploadContentFromUrl", "Content-length is not valid. Assuming 512kb size");
54 | size = 512 * 1024;
55 | }
56 | let buffer;
57 | if (Buffer.alloc) {//Since 5.10
58 | buffer = Buffer.alloc(size);
59 | } else {//Deprecated
60 | buffer = new Buffer(size);
61 | }
62 |
63 | let bsize = 0;
64 | res.on('data', (d) => {
65 | d.copy(buffer, bsize);
66 | bsize += d.length;
67 | });
68 | res.on('error', () => {
69 | reject("Failed to download.");
70 | });
71 | res.on('end', () => {
72 | resolve(buffer);
73 | });
74 | })
75 | }).then((buffer) => {
76 | if (typeof id === "string" || id == null) {
77 | id = bridge.getIntent(id);
78 | }
79 | return id.getClient().uploadContent({
80 | stream: buffer,
81 | name: name,
82 | type: contenttype
83 | });
84 | }).then((response) => {
85 | const content_uri = JSON.parse(response).content_uri;
86 | LogService.info("UploadContent", "Media uploaded to " + content_uri);
87 | return content_uri;
88 | }).catch(function (reason) {
89 | LogService.error("UploadContent", "Failed to upload content:\n" + reason)
90 | });
91 | }
92 |
93 | /**
94 | * Uploads the content contained in a data uri string to the homeserver
95 | *
96 | * @param {Bridge} bridge the bridge object of this application
97 | * @param {string} uri the data URI to upload
98 | * @param {string} id either the ID of the uploader
99 | * @param {string} [name] name of the file. Defaults to 'file'.
100 | * @return {Promise} Promise resolving with a MXC URL.
101 | */
102 | function uploadContentFromDataUri(bridge, id, uri, name) {
103 | if (!name || typeof(name) !== "string") name = "file";
104 | const parsed = parseDataUri(uri);
105 | return bridge.getIntent(id).getClient().uploadContent({
106 | stream: parsed.data,
107 | name: name,
108 | type: parsed.mimeType
109 | }).then(response=> {
110 | const content_uri = JSON.parse(response).content_uri;
111 | LogService.info("uploadContentFromDataUri", "Media uploaded to " + content_uri);
112 | return content_uri;
113 | }).catch(function (reason) {
114 | LogService.error("UploadContent", "Failed to upload content:\n" + reason)
115 | });
116 | }
117 |
118 | /**
119 | * Downloads a file from a web address to the file system
120 | * @param {string} uri the web resource to download
121 | * @param {string} path the filesystem path to download to
122 | * @returns {Promise} resolves with true if successful, false otherwise
123 | */
124 | function downloadFile(uri, path) {
125 | return new Promise((resolve, reject) => {
126 | let resolved = false;
127 | request(uri, (err, response, body) => {
128 | if (err) {
129 | resolved = true;
130 | resolve(false);
131 | }
132 | }).pipe(fs.createWriteStream(path)).on('close', () => {
133 | if (!resolved) resolve(true);
134 | });
135 | });
136 | }
137 |
138 | /**
139 | * Downloads a file from a web address to the file system
140 | * @param {string} uri the web resource to download
141 | * @param {string} [ext] optional extension for the filename
142 | * @returns {Promise} resolves to the file path, or null if something went wrong
143 | */
144 | function downloadFileTemp(uri, ext = '.data') {
145 | const root = "temp";
146 | const filename = uuidv4() + ext;
147 | const fullpath = path.join(root, filename);
148 |
149 | mkdirp.sync(root);
150 | return downloadFile(uri, fullpath).then(created => created ? fullpath : null);
151 | }
152 |
153 | module.exports = {
154 | uploadContentFromUrl: uploadContentFromUrl,
155 | uploadContentFromDataUri: uploadContentFromDataUri,
156 | downloadFile: downloadFile,
157 | downloadFileTemp: downloadFileTemp,
158 | };
--------------------------------------------------------------------------------