diff --git a/.eslintignore b/.eslintignore
index 417342d..66758cc 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1,3 @@
bin
+lib
*.md
diff --git a/doc/api.md b/doc/api.md
index adb8c25..031db25 100644
--- a/doc/api.md
+++ b/doc/api.md
@@ -30,13 +30,13 @@ TelegramBot
* [.sendMessage(chatId, text, [options])](#TelegramBot+sendMessage) ⇒ Promise
* [.answerInlineQuery(inlineQueryId, results, [options])](#TelegramBot+answerInlineQuery) ⇒ Promise
* [.forwardMessage(chatId, fromChatId, messageId, [options])](#TelegramBot+forwardMessage) ⇒ Promise
- * [.sendPhoto(chatId, photo, [options])](#TelegramBot+sendPhoto) ⇒ Promise
- * [.sendAudio(chatId, audio, [options])](#TelegramBot+sendAudio) ⇒ Promise
+ * [.sendPhoto(chatId, photo, [options], [fileOpts])](#TelegramBot+sendPhoto) ⇒ Promise
+ * [.sendAudio(chatId, audio, [options], [fileOpts])](#TelegramBot+sendAudio) ⇒ Promise
* [.sendDocument(chatId, doc, [options], [fileOpts])](#TelegramBot+sendDocument) ⇒ Promise
* [.sendSticker(chatId, sticker, [options])](#TelegramBot+sendSticker) ⇒ Promise
- * [.sendVideo(chatId, video, [options])](#TelegramBot+sendVideo) ⇒ Promise
- * [.sendVideoNote(chatId, videoNote, [options])](#TelegramBot+sendVideoNote) ⇒ Promise
- * [.sendVoice(chatId, voice, [options])](#TelegramBot+sendVoice) ⇒ Promise
+ * [.sendVideo(chatId, video, [options], [fileOpts])](#TelegramBot+sendVideo) ⇒ Promise
+ * [.sendVideoNote(chatId, videoNote, [options], [fileOpts])](#TelegramBot+sendVideoNote) ⇒ Promise
+ * [.sendVoice(chatId, voice, [options], [fileOpts])](#TelegramBot+sendVoice) ⇒ Promise
* [.sendChatAction(chatId, action, [options])](#TelegramBot+sendChatAction) ⇒ Promise
* [.kickChatMember(chatId, userId, [options])](#TelegramBot+kickChatMember) ⇒ Promise
* [.unbanChatMember(chatId, userId, [options])](#TelegramBot+unbanChatMember) ⇒ Promise
@@ -339,31 +339,41 @@ Forward messages of any kind.
-### telegramBot.sendPhoto(chatId, photo, [options]) ⇒ Promise
+### telegramBot.sendPhoto(chatId, photo, [options], [fileOpts]) ⇒ Promise
Send photo
**Kind**: instance method of [TelegramBot](#TelegramBot)
-**See**: https://core.telegram.org/bots/api#sendphoto
+**See**
+
+- https://core.telegram.org/bots/api#sendphoto
+- https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
+
| Param | Type | Description |
| --- | --- | --- |
| chatId | Number | String | Unique identifier for the message recipient |
| photo | String | stream.Stream | Buffer | A file path or a Stream. Can also be a `file_id` previously uploaded |
| [options] | Object | Additional Telegram query options |
+| [fileOpts] | Object | Optional file related meta-data |
-### telegramBot.sendAudio(chatId, audio, [options]) ⇒ Promise
+### telegramBot.sendAudio(chatId, audio, [options], [fileOpts]) ⇒ Promise
Send audio
**Kind**: instance method of [TelegramBot](#TelegramBot)
-**See**: https://core.telegram.org/bots/api#sendaudio
+**See**
+
+- https://core.telegram.org/bots/api#sendaudio
+- https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
+
| Param | Type | Description |
| --- | --- | --- |
| chatId | Number | String | Unique identifier for the message recipient |
| audio | String | stream.Stream | Buffer | A file path, Stream or Buffer. Can also be a `file_id` previously uploaded. |
| [options] | Object | Additional Telegram query options |
+| [fileOpts] | Object | Optional file related meta-data |
@@ -371,7 +381,11 @@ Send audio
Send Document
**Kind**: instance method of [TelegramBot](#TelegramBot)
-**See**: https://core.telegram.org/bots/api#sendDocument
+**See**
+
+- https://core.telegram.org/bots/api#sendDocument
+- https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
+
| Param | Type | Description |
| --- | --- | --- |
@@ -396,46 +410,61 @@ Send .webp stickers.
-### telegramBot.sendVideo(chatId, video, [options]) ⇒ Promise
+### telegramBot.sendVideo(chatId, video, [options], [fileOpts]) ⇒ Promise
Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as Document).
**Kind**: instance method of [TelegramBot](#TelegramBot)
-**See**: https://core.telegram.org/bots/api#sendvideo
+**See**
+
+- https://core.telegram.org/bots/api#sendvideo
+- https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
+
| Param | Type | Description |
| --- | --- | --- |
| chatId | Number | String | Unique identifier for the message recipient |
| video | String | stream.Stream | Buffer | A file path or Stream. Can also be a `file_id` previously uploaded. |
| [options] | Object | Additional Telegram query options |
+| [fileOpts] | Object | Optional file related meta-data |
-### telegramBot.sendVideoNote(chatId, videoNote, [options]) ⇒ Promise
+### telegramBot.sendVideoNote(chatId, videoNote, [options], [fileOpts]) ⇒ Promise
Use this method to send rounded square videos of upto 1 minute long.
**Kind**: instance method of [TelegramBot](#TelegramBot)
**Info**: The length parameter is actually optional. However, the API (at time of writing) requires you to always provide it until it is fixed.
-**See**: https://core.telegram.org/bots/api#sendvideonote
+**See**
+
+- https://core.telegram.org/bots/api#sendvideonote
+- https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
+
| Param | Type | Description |
| --- | --- | --- |
| chatId | Number | String | Unique identifier for the message recipient |
| videoNote | String | stream.Stream | Buffer | A file path or Stream. Can also be a `file_id` previously uploaded. |
| [options] | Object | Additional Telegram query options |
+| [fileOpts] | Object | Optional file related meta-data |
-### telegramBot.sendVoice(chatId, voice, [options]) ⇒ Promise
+### telegramBot.sendVoice(chatId, voice, [options], [fileOpts]) ⇒ Promise
Send voice
**Kind**: instance method of [TelegramBot](#TelegramBot)
-**See**: https://core.telegram.org/bots/api#sendvoice
+**See**
+
+- https://core.telegram.org/bots/api#sendvoice
+- https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
+
| Param | Type | Description |
| --- | --- | --- |
| chatId | Number | String | Unique identifier for the message recipient |
| voice | String | stream.Stream | Buffer | A file path, Stream or Buffer. Can also be a `file_id` previously uploaded. |
| [options] | Object | Additional Telegram query options |
+| [fileOpts] | Object | Optional file related meta-data |
diff --git a/doc/usage.md b/doc/usage.md
index 3a17700..e3499d3 100644
--- a/doc/usage.md
+++ b/doc/usage.md
@@ -115,6 +115,45 @@ const url = 'https://telegram.org/img/t_logo.png';
bot.sendPhoto(chatId, url);
```
+If you wish to explicitly specify the filename or
+[MIME type](http://en.wikipedia.org/wiki/Internet_media_type),
+you may pass the an additional argument as file options, like so:
+
+```js
+const fileOpts = {
+ // Explicitly specify the file name.
+ filename: 'customfilename',
+ // Explicitly specify the MIME type.
+ contentType: 'audio/mpeg'
+};
+bot.sendAudio(chatId, data, {}, fileOpts);
+```
+
+
+### File Options (metadata)
+
+When sending files, the library automatically resolves
+the `filename` and `contentType` properties.
+**For now, this has to be manually activated using environment
+variable `NTBA_FIX_350`.**
+
+In order of highest-to-lowest precedence in searching for
+a value, when resolving the `filename`:
+
+1. Is `fileOptions.filename` explictly defined?
+1. Does `Stream#path` exist?
+1. Is `filepath` provided?
+1. Default to `"filename"`
+
+And the `contentType`:
+
+1. Is `fileOptions.contentType` explictly-defined?
+1. Does `Stream#path` exist?
+1. Try detecting file-type from the `Buffer`
+1. Is `filepath` provided?
+1. Is `fileOptions.filename` explicitly defined?
+1. Default to `"application/octet-stream`
+
### Performance Issue
diff --git a/src/telegram.js b/src/telegram.js
index b69fa59..3d011bd 100644
--- a/src/telegram.js
+++ b/src/telegram.js
@@ -284,6 +284,9 @@ class TelegramBot extends EventEmitter {
* Format data to be uploaded; handles file paths, streams and buffers
* @param {String} type
* @param {String|stream.Stream|Buffer} data
+ * @param {Object} fileOpts File options
+ * @param {String} [fileOpts.filename] File name
+ * @param {String} [fileOpts.contentType] Content type (i.e. MIME)
* @return {Array} formatted
* @return {Object} formatted[0] formData
* @return {String} formatted[1] fileId
@@ -291,55 +294,68 @@ class TelegramBot extends EventEmitter {
* @see https://npmjs.com/package/file-type
* @private
*/
- _formatSendData(type, data) {
- let formData;
- let fileName;
- let fileId;
+ _formatSendData(type, data, fileOpts = {}) {
+ let filedata = data;
+ let filename = fileOpts.filename;
+ let contentType = fileOpts.contentType;
+
if (data instanceof stream.Stream) {
- // Will be 'null' if could not be parsed. Default to 'filename'.
- // For example, 'data.path' === '/?id=123' from 'request("https://example.com/?id=123")'
- fileName = URL.parse(path.basename(data.path.toString())).pathname || 'filename';
- formData = {};
- formData[type] = {
- value: data,
- options: {
- filename: qs.unescape(fileName),
- contentType: mime.lookup(fileName)
- }
- };
+ if (!filename && data.path) {
+ // Will be 'null' if could not be parsed.
+ // For example, 'data.path' === '/?id=123' from 'request("https://example.com/?id=123")'
+ const url = URL.parse(path.basename(data.path.toString()));
+ filename = qs.unescape(url.pathname);
+ }
} else if (Buffer.isBuffer(data)) {
- const filetype = fileType(data);
- if (!filetype) {
- throw new errors.FatalError('Unsupported Buffer file type');
+ if (!filename && !process.env.NTBA_FIX_350) {
+ deprecate('Buffers will have their filenames default to "filename" instead of "data".');
+ filename = 'data';
}
- formData = {};
- formData[type] = {
- value: data,
- options: {
- filename: `data.${filetype.ext}`,
- contentType: filetype.mime
+ if (!contentType) {
+ const filetype = fileType(data);
+ if (filetype) {
+ contentType = filetype.mime;
+ const ext = filetype.ext;
+ if (ext && !process.env.NTBA_FIX_350) {
+ filename = `${filename}.${ext}`;
+ }
+ } else if (!process.env.NTBA_FIX_350) {
+ deprecate('An error will no longer be thrown if file-type of buffer could not be detected.');
+ throw new errors.FatalError('Unsupported Buffer file-type');
}
- };
- } else if (!this.options.filepath) {
- /**
- * When the constructor option 'filepath' is set to
- * 'false', we do not support passing file-paths.
- */
- fileId = data;
- } else if (fs.existsSync(data)) {
- fileName = path.basename(data);
- formData = {};
- formData[type] = {
- value: fs.createReadStream(data),
- options: {
- filename: fileName,
- contentType: mime.lookup(fileName)
+ }
+ } else if (data) {
+ if (this.options.filepath && fs.existsSync(data)) {
+ filedata = fs.createReadStream(data);
+ if (!filename) {
+ filename = path.basename(data);
}
- };
+ } else {
+ return [null, data];
+ }
} else {
- fileId = data;
+ return [null, data];
}
- return [formData, fileId];
+
+ filename = filename || 'filename';
+ contentType = contentType || mime.lookup(filename);
+ if (process.env.NTBA_FIX_350) {
+ contentType = contentType || 'application/octet-stream';
+ } else {
+ deprecate('In the future, content-type of files you send will default to "application/octet-stream".');
+ }
+
+ // TODO: Add missing file extension.
+
+ return [{
+ [type]: {
+ value: filedata,
+ options: {
+ filename,
+ contentType,
+ },
+ },
+ }, null];
}
/**
@@ -685,16 +701,18 @@ class TelegramBot extends EventEmitter {
* @param {String|stream.Stream|Buffer} photo A file path or a Stream. Can
* also be a `file_id` previously uploaded
* @param {Object} [options] Additional Telegram query options
+ * @param {Object} [fileOpts] Optional file related meta-data
* @return {Promise}
* @see https://core.telegram.org/bots/api#sendphoto
+ * @see https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
*/
- sendPhoto(chatId, photo, options = {}) {
+ sendPhoto(chatId, photo, options = {}, fileOpts = {}) {
const opts = {
qs: options,
};
opts.qs.chat_id = chatId;
try {
- const sendData = this._formatSendData('photo', photo);
+ const sendData = this._formatSendData('photo', photo, fileOpts);
opts.formData = sendData[0];
opts.qs.photo = sendData[1];
} catch (ex) {
@@ -709,16 +727,18 @@ class TelegramBot extends EventEmitter {
* @param {String|stream.Stream|Buffer} audio A file path, Stream or Buffer.
* Can also be a `file_id` previously uploaded.
* @param {Object} [options] Additional Telegram query options
+ * @param {Object} [fileOpts] Optional file related meta-data
* @return {Promise}
* @see https://core.telegram.org/bots/api#sendaudio
+ * @see https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
*/
- sendAudio(chatId, audio, options = {}) {
+ sendAudio(chatId, audio, options = {}, fileOpts = {}) {
const opts = {
qs: options
};
opts.qs.chat_id = chatId;
try {
- const sendData = this._formatSendData('audio', audio);
+ const sendData = this._formatSendData('audio', audio, fileOpts);
opts.formData = sendData[0];
opts.qs.audio = sendData[1];
} catch (ex) {
@@ -736,6 +756,7 @@ class TelegramBot extends EventEmitter {
* @param {Object} [fileOpts] Optional file related meta-data
* @return {Promise}
* @see https://core.telegram.org/bots/api#sendDocument
+ * @see https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
*/
sendDocument(chatId, doc, options = {}, fileOpts = {}) {
const opts = {
@@ -743,15 +764,12 @@ class TelegramBot extends EventEmitter {
};
opts.qs.chat_id = chatId;
try {
- const sendData = this._formatSendData('document', doc);
+ const sendData = this._formatSendData('document', doc, fileOpts);
opts.formData = sendData[0];
opts.qs.document = sendData[1];
} catch (ex) {
return Promise.reject(ex);
}
- if (opts.formData && Object.keys(fileOpts).length) {
- opts.formData.document.options = fileOpts;
- }
return this._request('sendDocument', opts);
}
@@ -785,16 +803,18 @@ class TelegramBot extends EventEmitter {
* @param {String|stream.Stream|Buffer} video A file path or Stream.
* Can also be a `file_id` previously uploaded.
* @param {Object} [options] Additional Telegram query options
+ * @param {Object} [fileOpts] Optional file related meta-data
* @return {Promise}
* @see https://core.telegram.org/bots/api#sendvideo
+ * @see https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
*/
- sendVideo(chatId, video, options = {}) {
+ sendVideo(chatId, video, options = {}, fileOpts = {}) {
const opts = {
qs: options
};
opts.qs.chat_id = chatId;
try {
- const sendData = this._formatSendData('video', video);
+ const sendData = this._formatSendData('video', video, fileOpts);
opts.formData = sendData[0];
opts.qs.video = sendData[1];
} catch (ex) {
@@ -809,17 +829,19 @@ class TelegramBot extends EventEmitter {
* @param {String|stream.Stream|Buffer} videoNote A file path or Stream.
* Can also be a `file_id` previously uploaded.
* @param {Object} [options] Additional Telegram query options
+ * @param {Object} [fileOpts] Optional file related meta-data
* @return {Promise}
* @info The length parameter is actually optional. However, the API (at time of writing) requires you to always provide it until it is fixed.
* @see https://core.telegram.org/bots/api#sendvideonote
+ * @see https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
*/
- sendVideoNote(chatId, videoNote, options = {}) {
+ sendVideoNote(chatId, videoNote, options = {}, fileOpts = {}) {
const opts = {
qs: options
};
opts.qs.chat_id = chatId;
try {
- const sendData = this._formatSendData('video_note', videoNote);
+ const sendData = this._formatSendData('video_note', videoNote, fileOpts);
opts.formData = sendData[0];
opts.qs.video_note = sendData[1];
} catch (ex) {
@@ -834,16 +856,18 @@ class TelegramBot extends EventEmitter {
* @param {String|stream.Stream|Buffer} voice A file path, Stream or Buffer.
* Can also be a `file_id` previously uploaded.
* @param {Object} [options] Additional Telegram query options
+ * @param {Object} [fileOpts] Optional file related meta-data
* @return {Promise}
* @see https://core.telegram.org/bots/api#sendvoice
+ * @see https://github.com/yagop/node-telegram-bot-api/blob/master/doc/usage.md#sending-files
*/
- sendVoice(chatId, voice, options = {}) {
+ sendVoice(chatId, voice, options = {}, fileOpts = {}) {
const opts = {
qs: options
};
opts.qs.chat_id = chatId;
try {
- const sendData = this._formatSendData('voice', voice);
+ const sendData = this._formatSendData('voice', voice, fileOpts);
opts.formData = sendData[0];
opts.qs.voice = sendData[1];
} catch (ex) {
diff --git a/test/telegram.js b/test/telegram.js
index b300ab2..c0501cf 100644
--- a/test/telegram.js
+++ b/test/telegram.js
@@ -630,13 +630,6 @@ describe('TelegramBot', function telegramSuite() {
assert.ok(is.object(resp.document));
});
});
- it('should send a document with custom file options', function test() {
- const document = fs.createReadStream(`${__dirname}/data/photo.gif`);
- const fileOpts = { filename: 'customfilename.gif' };
- return bot.sendDocument(USERID, document, {}, fileOpts).then(resp => {
- assert.equal(resp.document.file_name, fileOpts.filename);
- });
- });
});
describe('#sendSticker', function sendStickerSuite() {
diff --git a/test/test.sendfile.js b/test/test.sendfile.js
new file mode 100644
index 0000000..864d900
--- /dev/null
+++ b/test/test.sendfile.js
@@ -0,0 +1,109 @@
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const TelegramBot = require('..');
+
+const paths = {
+ audio: path.join(__dirname, "data/audio.mp3"),
+};
+
+
+// TODO:Enable all other tests
+describe.only('sending files', function sendfileSuite() {
+ const bot = new TelegramBot("token");
+
+ before(function beforeSuite() {
+ process.env.NTBA_FIX_350 = 1;
+ });
+ after(function afterSuite() {
+ delete process.env.NTBA_FIX_350;
+ });
+
+ describe('using fileOptions', function sendfileOptionsSuite() {
+ const type = 'file';
+ const stream = fs.createReadStream(paths.audio);
+ const nonPathStream = fs.createReadStream(paths.audio);
+ const buffer = fs.readFileSync(paths.audio);
+ const nonDetectableBuffer = fs.readFileSync(__filename);
+ const filepath = paths.audio;
+ const fileId = 'fileId';
+ const files = [stream, nonPathStream, buffer, nonDetectableBuffer, filepath];
+
+ delete nonPathStream.path;
+
+ describe('filename', function filenameSuite() {
+ it('(1) fileOptions.filename', function test() {
+ const filename = 'custom-filename';
+ files.forEach((file) => {
+ const [{ [type]: data }] = bot._formatSendData(type, file, { filename });
+ assert.equal(data.options.filename, filename);
+ });
+ });
+
+ it('(2) Stream#path', function test() {
+ if (!stream.path) {
+ return this.skip('Stream#path unsupported');
+ }
+ const [{ [type]: data }] = bot._formatSendData(type, stream);
+ assert.equal(data.options.filename, path.basename(paths.audio));
+ });
+
+ it('(3) filepath', function test() {
+ const [{ [type]: data }] = bot._formatSendData(type, filepath);
+ assert.equal(data.options.filename, path.basename(paths.audio));
+ });
+
+ it('(4) final default', function test() {
+ [nonPathStream, buffer, nonDetectableBuffer].forEach((file) => {
+ const [{ [type]: data }] = bot._formatSendData(type, file);
+ assert.equal(data.options.filename, 'filename');
+ });
+ });
+ });
+
+ describe('contentType', function contentTypeSuite() {
+ it('(1) fileOpts.contentType', function test() {
+ const contentType = 'application/custom-type';
+ files.forEach((file) => {
+ const [{ [type]: data }] = bot._formatSendData(type, file, { contentType });
+ assert.equal(data.options.contentType, contentType);
+ });
+ });
+
+ it('(2) Stream#path', function test() {
+ if (!stream.path) {
+ return this.skip('Stream#path unsupported');
+ }
+ const [{ [type]: data }] = bot._formatSendData(type, stream);
+ assert.equal(data.options.contentType, 'audio/mpeg');
+ });
+
+ it('(3) Buffer file-type', function test() {
+ const [{ [type]: data }] = bot._formatSendData(type, buffer);
+ assert.equal(data.options.contentType, 'audio/mpeg');
+ });
+
+ it('(4) filepath', function test() {
+ const [{ [type]: data }] = bot._formatSendData(type, filepath);
+ assert.equal(data.options.contentType, 'audio/mpeg');
+ });
+
+ it('(5) fileOptions.filename', function test() {
+ [nonPathStream, nonDetectableBuffer].forEach((file) => {
+ const [{ [type]: data }] = bot._formatSendData(type, file, {
+ filename: 'image.gif',
+ });
+ assert.equal(data.options.contentType, 'image/gif');
+ });
+ });
+
+ it('(6) Final default', function test() {
+ [nonPathStream, nonDetectableBuffer].forEach((file) => {
+ const [{ [type]: data }] = bot._formatSendData(type, file);
+ assert.equal(data.options.contentType, 'application/octet-stream');
+ });
+ });
+ });
+
+ });
+});