Code Monkey home page Code Monkey logo

telegram-bot-api's People

Contributors

acran avatar birkhofflee avatar headler avatar helloworld017 avatar herrzatacke avatar jkantr avatar m1n0s avatar mast avatar natsvora avatar pedroaf0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

telegram-bot-api's Issues

Webhook

Hello! Sorry for disturbing you, but I don't understand how to use the Webhook in my project. Can you explain me how to use it? Can you give an example?

Webhook version

As I saw on the source, this implementations uses getUpdates to retrieve data.
There is a way to use a webhook instead?

Exception StatusCodeError: 403

I think when users block the bot and we try to send message to him
throw exception

StatusCodeError: 403 - [object Object]
at new StatusCodeError (C:\Users\parsa\node_modules\telegram-bot-api\node_modules\request-promise\lib\errors.js:26:15)
at Request.RP$callback [as _callback] (C:\Users\parsa\node_modules\telegram-bot-api\node_modules\request-promise\lib\rp.js:68:32)
at Request.self.callback (C:\Users\parsa\node_modules\request\request.js:188:22)
at emitTwo (events.js:106:13)
at Request.emit (events.js:191:7)
at Request. (C:\Users\parsa\node_modules\request\request.js:1171:10)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
at IncomingMessage. (C:\Users\parsa\node_modules\request\request.js:1091:12)
at IncomingMessage.g (events.js:291:16)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
how to handle it?

Q: Did you manage to send custom keyboard?

Hello, I tried almost 40 times to send custom keyboard layout, but it didn't worked.
Apperantly I'm doing something wrong, did you try to send custom keyboard layout?

Buffer in sendPhoto

I need to send photo from buffer. Example:

api.on("message", function(msg) {
	if (msg.text == "/photo") {
		var canvas = new Canvas(512, 512);
		var ctx = canvas.getContext("2d");

		ctx.fillStyle = "#fff";
		ctx.fillRect(0, 0, 512, 512);

		ctx.fillStyle = "#000";
		ctx.fillRect(128, 128, 512 - 128, 512 - 128);

		api.sendPhoto({
			chat_id: msg.chat.id,
			caption: "...",
			photo: canvas.toBuffer()
		});
	}
});

Can it be done on current version?

callback_query keeps being called

My bot used to ask question with an inline keyboard markup. Whenever you click a button, it emits the callback_query. At some point I realized that it keeps firing this event even when I don't click the button.
I end up commenting out almost everything, my bot's app looks like this now:

'use strict';

const config = require('./config');
const TelegramBot = require('node-telegram-bot-api');
const token = config.tg_token;
const bot = new TelegramBot(token, {polling: true});
const debug = require('debug')('my-bot');

bot.on('callback_query', cbData => {
  debug("bot.on callback_query", cbData);
});

And when I start the app, it keeps firing this callback every 10-ish seconds. Any ideas why and how to debug that weird behavior?

One group with multiple bot

Hi, I have a problem with this module,
if in a group have one more bot, the bot can't work, why? ><
help me, please ><

thank you

Update request-promise version

I noticed that Github gives a security vulnerability (CVE-2018-3721) warning because of an outdated lodash version (3.10). This version is included because it's a dependency of request-promise 1.0.2 which this package still depends on.

Are there breaking changes which stop us from bumping the request-promise version?

Add stop method

Once the bot is created it runs forever and there is no way to stop it. Please add this ability.

caption in sendAudio does not insert in telegram

caption in sendAudio does not insert in telegram, only audio upload on Telegram!!!

api.sendAudio({
    chat_id: message.from.id,   
   performer: 'my performer',
   title: 'my title',
 caption: 'maycaption',    
  audio: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'
})
.then(function(data)
{
    console.log(util.inspect(data, false, null));
});

Bug: answerInlineQuery error

This is my code for test answerInlineQuery:

onInlineQuery = (query) => {
  console.log("===> onInlineQuery: ", stringify(query));

  var
  post = data.posts['test'].messages[0],
  results = [{
    type: "photo",
    id: 1,
    photo_file_id: post.photo.id,
    title: "title1",
    description: "description1",
    caption: "caption1",
    input_message_content: {
      message_text: "message_text1"
    }
  }]
  ;

  console.log("results: " + stringify(results));

  bot.answerInlineQuery({
    inline_query_id: query.id,
    results: results,
    cache_time: 1,
    is_personal: false,
    next_offset: ""
  }, (err, data) => {
    console.log("answerInlineQueryCallback: " + stringify({err: err, query: query}));
  })
  .then((data) => {
    console.log("answerInlineQuerySuccess: " + stringify(data))
  })
  .catch((err) => {
    console.log("answerInlineQueryError: " + stringify({err: err, query: query}));
  })
  ;
}

And this is console error

===> onInlineQuery:  {
  "id": "250780611822216560",
  "from": {
    "id": 58389411,
    "first_name": "Ali",
    "last_name": "Mihandoost",
    "username": "Al1MD"
  },
  "query": "",
  "offset": ""
}
results: [
  {
    "type": "photo",
    "id": 1,
    "photo_file_id": "AgADBAADQagxG3Y-EwLpdVrRB_1FdXWoYzAABII7tf8pCsFbQ1gBAAEC",
    "title": "title1",
    "description": "description1",
    "caption": "caption1",
    "input_message_content": {
      "message_text": "message_text1"
    }
  }
]

[TelegramBot]: Failed to get updates from Telegram servers
_http_outgoing.js:443
    throw new TypeError('first argument must be a string or Buffer');
    ^

TypeError: first argument must be a string or Buffer
    at ClientRequest.OutgoingMessage.write (_http_outgoing.js:443:11)
    at Request.write (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/request/request.js:1385:25)
    at FormData.ondata (stream.js:31:26)
    at emitOne (events.js:90:13)
    at FormData.emit (events.js:182:7)
    at FormData.CombinedStream.write (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:118:8)
    at FormData.CombinedStream._pipeNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:106:8)
    at FormData.CombinedStream._getNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:79:10)
    at FormData.CombinedStream._pipeNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:107:8)
    at FormData.CombinedStream._getNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:79:10)
    at FormData.CombinedStream._pipeNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:107:8)
    at FormData.<anonymous> (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:91:10)
    at FormData.<anonymous> (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/form-data/lib/form_data.js:246:5)
    at FormData.CombinedStream._getNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:84:3)
    at FormData.CombinedStream._pipeNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:107:8)
    at FormData.CombinedStream._getNext (/Volumes/Repositories/metrofrq_bot/node_modules/telegram-bot-api/node_modules/combined-stream/lib/combined_stream.js:79:10)

Adding webhook

How about adding setting webhook to get updates by webhook?

Sudden error after getUpdates

screenshot_134

What has happened? I cannot start the server, because of the error.

As far as I remember, I did npm update telegram-bot-api just before.

Is this the problem of telegram-bot-api?

bot over ipv6

Hi.
Is it possible to run bot over ipv6?
When I run application on node, I receive this error message:

Error: RequestError: Error: connect ETIMEDOUT 149.154.167.220:443
at /var/www/telegram.bot/node_modules/telegram-bot-api/lib/telegram-bot.js:1686:19

I know, many telegram's ipv4 IPs blocked now. I have ipv6 address on server. If I ping -4 api.telegram.org, server cannot resolv address. If I ping -6 api.telegram.org - it works fine.

Example for getFile?

From the examples folder, there is no example of how to get a file sent to the Bot by a user. Can you add the example? Thanks.

webp format not supported in this module

I have a webp format images which I tried sending through sendPhoto(), sendSticker and sendDocument as well which doesn't work at all. How can I send webp photos using your module?

sendVideo : Bad request

Hi,
trying to send a video using API, but i get an error 400 (or "host invalid") since i updated from 1.3 -> 2.0.0

code :

let api = new telegram({
        token: '****************************',
        updates: { 
            enabled: true
        }
});

const mp = new telegram.GetUpdateMessageProvider()

api.setMessageProvider(mp)
api.start()
.then(() => {
    console.log('API is started')
    api.sendVideo({
        chat_id: **********,
        video: "./temp/wdZGjR9m0D.mp4"
    }).then(() => { 
        //Fs.unlinkSync(output)
    })
})
.catch(console.err)

result :

{
  code: 400,
  description: 'Bad Request: URL host is empty',
  body: {
    ok: false,
    error_code: 400,
    description: 'Bad Request: URL host is empty'
  }
}

crach on calling api.getChatMember()

/Users/g66k/Code/JavaScript/node_modules/bluebird/js/release/async.js:49
fn = function () { throw arg; };
^
StatusCodeError: 400 - {"ok":false,"error_code":400,"description":"Bad Request: wrong user_id specified"}
at new StatusCodeError (/Users/g66k/Code/JavaScript/node_modules/request-promise-core/lib/errors.js:32:15)
at Request.plumbing.callback (/Users/g66k/Code/JavaScript/node_modules/request-promise-core/lib/plumbing.js:104:33)
at Request.RP$callback [as _callback] (/Users/g66k/Code/JavaScript/node_modules/request-promise-core/lib/plumbing.js:46:31)
at Request.self.callback (/Users/g66k/Code/JavaScript/node_modules/request/request.js:185:22)
at Request.emit (events.js:310:20)
at Request. (/Users/g66k/Code/JavaScript/node_modules/request/request.js:1161:10)
at Request.emit (events.js:310:20)
at IncomingMessage. (/Users/g66k/Code/JavaScript/node_modules/request/request.js:1083:12)
at Object.onceWrapper (events.js:416:28)
at IncomingMessage.emit (events.js:322:22)
at endReadableNT (_stream_readable.js:1187:12)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {

Help me please ! :((

hi dear friend ! would you give me a sample code for telegram bot ?!
i want a code for this :
when a client send a message to bot , for example

 "hi"

the bot send him/her :

"hello dear friend"

i wrote some code but i didn't see any message from robot !

@mast thank you :*

Use getUpdates

Hi
I don't know how to use getUpdates method in this package

Google custom search API

Possible to include the Google custom search api for searching the image and then send it with photo?

throw new TypeError('first argument must be a string or Buffer');

Hi folk,

When I use the method "sendContact" throws this error:

http_outgoing.js:447 throw new TypeError('first argument must be a string or Buffer'); ^

TypeError: first argument must be a string or Buffer at ClientRequest.OutgoingMessage.write (_http_outgoing.js:447:11) at Request.write (/home/ubuntu/workspace/node_modules/request/request.js:1407:27) at FormData.ondata (stream.js:31:26) at emitOne (events.js:77:13) at FormData.emit (events.js:169:7) at FormData.CombinedStream.write (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:118:8) at FormData.CombinedStream._pipeNext (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:106:8) at FormData.CombinedStream._getNext (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:79:10) at FormData.CombinedStream._pipeNext (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:107:8) at FormData.CombinedStream._getNext (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:79:10) at FormData.CombinedStream._pipeNext (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:107:8) at FormData.<anonymous> (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:91:10) at FormData.<anonymous> (/home/ubuntu/workspace/node_modules/form-data/lib/form_data.js:246:5) stream/lib/combined_stream.js:84:3) at FormData.CombinedStream._pipeNext (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:107:8) at FormData.CombinedStream._getNext (/home/ubuntu/workspace/node_modules/combined-stream/lib/combined_stream.js:79:10)

Do yo know what happend?

socks

how to make this library work with socks/socks5/TOR ?

Let sendDocument and sendPhoto take a stream instead of a filename

I'm writing a bot where I download Pictures and Videos and send them over Telegram. To do that with the current API I need to save the HTTP Stream to a file and then let the API read that file. With a small change to those functions I'm able to pass them a stream and they send it happily.

Migrate to es6

I want to rewrite code to es6 with babeljs.
Make a pull or own repo ?

issue with getChat

Hi,
i need to fetch chat from a channel as i doing right?
api.getChat({
chat_id: "@altcoins_signal"
}, function(message)
{
// New incoming callback query
console.log(message);
});

Bug: inline query

Bot closed at inlineQuery

[TelegramBot]: Failed to get updates from Telegram servers

Error sent from .catch of internalGetUpdates

setting reply_markup throws error in sendMessage method

Description

Adding reply_markup key to sendMessage method's object argument
throws an error.

Am I doing wrong or is it a bug?

Package version

telegram-bot-api: ^1.3.4

Thanks in advance.

Code

import telegramBot = require('telegram-bot-api');

botApi = new telegramBot({
        token: process.env['BOT_TOKEN'],
});

await this.botApi.sendMessage({
                chat_id: user.chatId,
                text: message.toTemplate(),
                parse_mode: 'Markdown',
                reply_markup: { //<-- adding this key causes error
                    inline_keyboard: [
                        [
                            {
                                text: 'test',
                                callback_data: 'testCallback',
                            },
                        ],
                    ],
                },
            });

Stacktrace

TypeError: source.on is not a function
    at Function.DelayedStream.create (E:\Projects\github-watchman\node_modules\delayed-stream\lib\delayed_stream.js:33:10)
    at FormData.CombinedStream.append (E:\Projects\github-watchman\node_modules\combined-stream\lib\combined_stream.js:45:37)
    at FormData.append (E:\Projects\github-watchman\node_modules\form-data\lib\form_data.js:74:3)
    at appendFormValue (E:\Projects\github-watchman\node_modules\request\request.js:326:21)
    at Request.init (E:\Projects\github-watchman\node_modules\request\request.js:337:11)
    at Request.RP$initInterceptor [as init] (E:\Projects\github-watchman\node_modules\request-promise-core\configure\request2.js:45:29)
    at new Request (E:\Projects\github-watchman\node_modules\request\request.js:127:8)
    at request (E:\Projects\github-watchman\node_modules\request\index.js:53:10)
    at E:\Projects\github-watchman\node_modules\request\index.js:100:12
    at E:\Projects\github-watchman\node_modules\telegram-bot-api\lib\telegram-bot.js:253:13
    at Promise._execute (E:\Projects\github-watchman\node_modules\bluebird\js\release\debuggability.js:384:9)
    at Promise._resolveFromExecutor (E:\Projects\github-watchman\node_modules\bluebird\js\release\promise.js:518:18)
    at new Promise (E:\Projects\github-watchman\node_modules\bluebird\js\release\promise.js:103:10)
    at TelegramApi.sendMessage (E:\Projects\github-watchman\node_modules\telegram-bot-api\lib\telegram-bot.js:251:16)
    at TelegramBot.<anonymous> (E:\Projects\github-watchman\src\bot\telegram\telegram_bot.ts:36:31)
    at Generator.next (<anonymous>)
    at E:\Projects\github-watchman\src\bot\telegram\telegram_bot.ts:27:71
    at new Promise (<anonymous>)
    at __awaiter (E:\Projects\github-watchman\src\bot\telegram\telegram_bot.ts:23:12)
    at TelegramBot.sendMessage (E:\Projects\github-watchman\src\bot\telegram\telegram_bot.ts:67:16)
    at E:\Projects\github-watchman\src\telegram_api\telegrammessage_mapper.ts:20:23
    at Generator.next (<anonymous>)

@mast

Adding a bot to the group and let it send messages without user input

I would like to have my bot within a group/or channel and let it send messages to the group or channel.

var api = new telegram({ token: 'MY_TOKEN_HERE', updates: { enabled: true } }); api.sendMessage({ chat_id:, text: 'test TEXT' })

Right now the bot is sending the messages to the bot itself but not in the group where I added it as an admin. I also already change the privacy settings of the bot so it can see all messages.

Sending photos leads to error

Sending a photo with a filepath leads to a Photo can't be empty error.

    api.sendMessage({chat_id: chatID, text: image}, function(err, data)
    {
    if(err)
       log(err.description, 'Telegram-Server');
    });

Error: socket hang up

When bot gets this issue it does not react on any further commands. The simple restart of process helps.

Investigation: Have checked the code and figure out that it is trying to repeat the request later but it still fails

[TelegramBot]: Failed to get updates from Telegram servers
{ RequestError: Error: socket hang up
    at new RequestError (/root/workspace/node-app/node_modules/request-promise-core/lib/errors.js:14:15)
    at Request.plumbing.callback (/root/workspace/velas-god/node_modules/request-promise-core/lib/plumbing.js:87:29)
    at Request.RP$callback [as _callback] (/root/workspace/node-app/node_modules/request-promise-core/lib/plumbing.js:46:31)
    at self.callback (/root/workspace/node-app/node_modules/request/request.js:185:22)
    at emitOne (events.js:116:13)
    at Request.emit (events.js:211:7)
    at Request.onRequestError (/root/workspace/node-app/node_modules/request/request.js:877:8)
    at emitOne (events.js:116:13)
    at ClientRequest.emit (events.js:211:7)
    at TLSSocket.socketErrorListener (_http_client.js:387:9)
    at emitOne (events.js:116:13)
    at TLSSocket.emit (events.js:211:7)
    at emitErrorNT (internal/streams/destroy.js:64:8)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)
  name: 'RequestError',
  message: 'Error: socket hang up',
  cause:
   { Error: socket hang up
    at TLSSocket.onHangUp (_tls_wrap.js:1137:19)
    at Object.onceWrapper (events.js:313:30)
    at emitNone (events.js:111:20)
    at TLSSocket.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)
     code: 'ECONNRESET',
     path: null,
     host: 'api.telegram.org',
     port: 443,
     localAddress: undefined },
  error:
   { Error: socket hang up
    at TLSSocket.onHangUp (_tls_wrap.js:1137:19)
    at Object.onceWrapper (events.js:313:30)
    at emitNone (events.js:111:20)
    at TLSSocket.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)
     code: 'ECONNRESET',
     path: null,
     host: 'api.telegram.org',
     port: 443,
     localAddress: undefined },
  options:
   { proxy: null,
     method: 'GET',
     json: true,
     formData: { timeout: 0, offset: 764361189, limit: 50 },
     uri: 'https://api.telegram.org/..../getUpdates',
     callback: [Function: RP$callback],
     transform: undefined,
     simple: true,
     resolveWithFullResponse: false,
     transform2xxOnly: false },
  response: undefined }

sendPhoto parse_mode

Hello! sendPhoto does not support parse_mode correct? It doesnt seem to do anything with either 'html' or 'parse_node'

const response = await api.sendPhoto({
    chat_id,
    photo,
    caption,
    parse_mode: 'Markdown'
  });
  return response;

Great API by the way. Cheers!

TypeError on reply_markup

when trying to send a keyboard in sendMessage this error comes up:

source.on('error', function() {});

TypeError: undefined is not a function at Function.DelayedStream.create

can bot send message with new chat?

hi. thanks to previous question.. it save huge of time. :D
i have some problem.

i got user id from message : message.from.id

and send message to user like this code..

let user_id = message.from.id;
let message = "how are you?";
api.sendMesssage({
    chat_id : user_id,
    text : message
}).then(..........)

if target user opened chat with this bot, message sent successfully.
but user has not talked to this bot, send process fail with 400 error.
error code and description below.

error_code: 400, description: 'Bad Request: chat not found'

is there any problem? how can i fix this issue?

Confused with reply_markup

Hi. I'm trying to change Keyboard after sending a photo, this is my code:

...
api.sendPhoto(
{
chat_id: message.chat.id,
caption: '',
photo: 'img/help.png',
reply_markup: {"keyboard" : [['help more!'],['cancel']]}
}, function(err, data)
{
...

and this is the console output:
{}

can you give me an example about syntax of "reply_markup", please?

thank you and excuse me for my bad English

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.