Git Product home page Git Product logo

altcointip's People

Contributors

doged avatar goldenchrysus avatar jg-you avatar m0glie avatar peoplma avatar radnor avatar vindimy avatar whitj00 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

altcointip's Issues

Latest update seemed to have caused this error + crash:

The bot just crashed after this message:

ERROR 2014-01-29 19:29:01,992 CointipBot::check_inbox(): CtbAction::__init__(atype=givetip, from_user=ViewSauce): u_to xor addr_to must be set
ERROR 2014-01-29 19:29:01,992 CointipBot::main(): exception: CtbAction::__init__(atype=givetip, from_user=ViewSauce): u_to xor addr_to must be set
ERROR 2014-01-29 19:29:01,993 CointipBot::main(): traceback: Traceback (most recent call last):
  File "cointipbot.py", line 558, in main
    self.check_inbox()
  File "cointipbot.py", line 235, in check_inbox
    action = ctb_action.eval_message(m, self)
  File "ctb/ctb_action.py", line 1074, in eval_message
    ctb=ctb)
  File "ctb/ctb_action.py", line 87, in __init__
    raise Exception("CtbAction::__init__(atype=%s, from_user=%s): u_to xor addr_to must be set" % (self.type, self.u_from.name))
Exception: CtbAction::__init__(atype=givetip, from_user=ViewSauce): u_to xor addr_to must be set

hi, tryin to set this up for my coin..

so after it connects to database.. it gets a socket error..

INFO:cointipbot:CtbCoin::init():: connected to DogecoinDark
INFO:cointipbot:Setting tx fee of 0.001000
DEBUG:bitcoin:Starting "settxfee" JSON-RPC request
Traceback (most recent call last):
File "", line 1, in
File "cointipbot.py", line 511, in init
self.coins[c] = ctb_coin.CtbCoin(_conf=self.conf.coins[c])
File "ctb/ctb_coin.py", line 56, in init
self.conn.settxfee(self.conf.txfee)
File "/usr/lib/python2.7/dist-packages/pifkoin/bitcoind.py", line 69, in call
return server._rpc_call(self.method, *args)
File "/usr/lib/python2.7/dist-packages/pifkoin/bitcoind.py", line 207, in _rpc_call
'Content-Type': 'application/json',
File "/usr/lib/python2.7/httplib.py", line 1001, in request
self._send_request(method, url, body, headers)
File "/usr/lib/python2.7/httplib.py", line 1035, in _send_request
self.endheaders(body)
File "/usr/lib/python2.7/httplib.py", line 997, in endheaders
self._send_output(message_body)
File "/usr/lib/python2.7/httplib.py", line 850, in _send_output
self.send(msg)
File "/usr/lib/python2.7/httplib.py", line 812, in send
self.connect()
File "/usr/lib/python2.7/httplib.py", line 793, in connect
self.timeout, self.source_address)
File "/usr/lib/python2.7/socket.py", line
raise err
socket.error: [Errno 111] Connection refused

any idea where i should look?

Issue when adding a custom coin

I am working on adding LiteDoge coin altcointip bot to Reddit.

My coins.yml looks like;

 Litedoge
ldoge:
    enabled: true
    unit: ldoge
    name: Litedoge
    symbol: 'LDOGE'
    config_file: '~/.litedoge/litedoge.conf'
    config_rpcserver: '127.0.0.1'
    minconf:
        givetip: 10
        withdraw: 144
    txmin:
        givetip: 0.00000001
        withdraw: 0.05
    txfee: 0.01
    walletpassphrase: 'removed'
    explorer:
        address: 'http://www.presstab.pw/phpexplorer/LDOGE/address.php'
        transaction: 'http://www.presstab.pw/phpexplorer/LDOGE/tx.php'
    regex:
        address: '(P[1-9a-z]{20,40})'
        units: '(ldoge|ldogecoin|ldogecoins)'

I ran python _add_coin.py ldoge

INFO:cointipbot:CointipBot::init_logging(): -------------------- logging initialized --------------------
DEBUG:cointipbot:CointipBot::connect_db(): connecting to database...
INFO:cointipbot:CointipBot::connect_db(): connected to database ldogetipbot as root
INFO:cointipbot:< CointipBot::__init__(): DONE, batch-limit = 1000, sleep-seconds = 60
DEBUG:cointipbot:CtbCoin::__init__(): connecting to Litedoge...
DEBUG:bitcoin:Read 5 parameters from ~/.litedoge/litedoge.conf
DEBUG:bitcoin:Making HTTP connection to 127.0.0.1:53333
INFO:cointipbot:CtbCoin::__init__():: connected to Litedoge
INFO:cointipbot:Setting tx fee of 0.010000
DEBUG:bitcoin:Starting "settxfee" JSON-RPC request
DEBUG:bitcoin:Got 36 byte response from server in 1 ms
DEBUG:cointipbot:> add_coin(ldoge)
DEBUG:bitcoin:Starting "walletpassphrase" JSON-RPC request
DEBUG:bitcoin:Got 36 byte response from server in 128 ms
DEBUG:bitcoin:Starting "getnewaddress" JSON-RPC request
DEBUG:bitcoin:Got 68 byte response from server in 2 ms
DEBUG:bitcoin:Starting "walletlock" JSON-RPC request
DEBUG:bitcoin:Got 36 byte response from server in 1 ms
INFO:cointipbot:add_coin(): got new address dcfqDvYeYEzdFTnnjmDoaJiZcFKBbcCWRG for thedoctor___
/usr/local/lib/python2.7/dist-packages/sqlalchemy/engine/default.py:450: Warning: Data truncated for column 'coin' at row 1
  cursor.execute(statement, parameters)
DEBUG:cointipbot:< add_coin(ldoge) DONE

when I run sh _start.sh it fails to start with Error 500

    INFO:cointipbot:< init_regex() DONE (96 expressions)
DEBUG:cointipbot:> CtbUser::__init__(thedoctor___)
DEBUG:cointipbot:< CtbUser::__init__(thedoctor___) DONE
DEBUG:cointipbot:> CtbUser::is_registered(thedoctor___)
DEBUG:cointipbot:< CtbUser::is_registered(thedoctor___) DONE (yes)
DEBUG:cointipbot:> CtbUser::balance(thedoctor___)
INFO:cointipbot:CtbUser::balance(thedoctor___): getting ldoge givetip balance
DEBUG:cointipbot:CtbCoin::getbalance(thedoctor___, 10)
DEBUG:bitcoin:Starting "getbalance" JSON-RPC request
ERROR:bitcoin:500 (Internal Server Error) response from bitcoind
ERROR:cointipbot:CtbCoin.getbalance(): error getting Litedoge (minconf=10) balance for thedoctor___: 500 (Internal Server Error) response from bitcoind
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "cointipbot.py", line 533, in __init__
    self.self_checks()
  File "cointipbot.py", line 142, in self_checks
    ctb_balance = b.get_balance(coin=c, kind='givetip')
  File "ctb/ctb_user.py", line 91, in get_balance
    balance = self.ctb.coins[coin].getbalance(_user=self.name, _minconf=self.ctb.conf.coins[coin].minconf[kind])
  File "ctb/ctb_coin.py", line 70, in getbalance
    balance = self.conn.getbalance(user, minconf)
  File "ctb/pifkoin/bitcoind.py", line 69, in __call__
    return server._rpc_call(self.method, *args)
  File "ctb/pifkoin/bitcoind.py", line 216, in _rpc_call
    raise BitcoindException('%d (%s) response from bitcoind' % (response.status, response.reason))
ctb.pifkoin.bitcoind.BitcoindException: 500 (Internal Server Error) response from bitcoind

I think I am close to getting this up and running but cannot seem to track down the problem.

Ripple support

Create a cryptocoin class to be used for cryptocoin transactions. Then create a ripple class to do the same with Ripple.

Bot ignores tips made while it's processing

I've noticed that the bot will completely skip any tips made while it's actively processing requests. I'm pretty sure this is due to the bot passing x checkpoint right at the time the tip request is made by the user. I'm not really sure what can be done to improve the bot's performance in catching all requests, but it seems to miss a lot of them.

Bot unable to proceed if pending tip is deleted by author or mod

Example of message when attempting to expire tip for which message has been deleted:

WARNING 2013-06-24 20:13:07,341 _get_actions(): couldn't fetch msg.author (deleted?) from msg_link http://www.reddit.com/r/BitcoinDevBounties/comments/1c0icu/worldwide_decentralised_human_node_based_postal/cao4v56

GPL is considered ineffective for web applications

Correct me if I'm wrong, but I thought that the GPL doesn't provide any protections for web applications, because running it on your server is not considered "distributing" it. Just curious about the license choice.

Donation campaign functionality

It would be great to have special keywords for donation campaigns. A campaign would be initiated by user registering a keyword, then a user would donate by using the keyword.

Password prompting

Implement ability to prompt for (database, Reddit, wallet) passwords during startup instead of reading them from config files.

Crashing issues with latest versions of praw

Bot keeps crashing at random intervals with this error:

ERROR 2015-08-12 20:53:12,255 CointipBot::check_inbox(): 
ERROR 2015-08-12 20:53:12,263 CointipBot::main(): exception: 
ERROR 2015-08-12 20:53:12,307 CointipBot::main(): traceback: Traceback (most recent call last):
  File "cointipbot.py", line 558, in main
    self.check_inbox()
  File "cointipbot.py", line 195, in check_inbox
    messages = list(ctb_misc.praw_call(self.reddit.get_unread, limit=self.conf.reddit.scan.batch_limit))
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 524, in get_content
    page_data = self.request_json(url, params=params)
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 173, in wrapped
    return_value = function(reddit_session, *args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 579, in request_json
    retry_on_error=retry_on_error)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 424, in _request
    _raise_response_exceptions(response)
  File "/usr/local/lib/python2.7/dist-packages/praw/internal.py", line 203, in _raise_response_exceptions
    raise HTTPException(_raw=exc.response)
HTTPException
ERROR 2015-08-21 06:00:20,182 CointipBot::check_inbox(): 
ERROR 2015-08-21 06:00:20,182 CointipBot::main(): exception: 
ERROR 2015-08-21 06:00:20,326 CointipBot::main(): traceback: Traceback (most recent call last):
  File "cointipbot.py", line 558, in main
    self.check_inbox()
  File "cointipbot.py", line 195, in check_inbox
    messages = list(ctb_misc.praw_call(self.reddit.get_unread, limit=self.conf.reddit.scan.batch_limit))
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 549, in get_content
    page_data = self.request_json(url, params=params)
  File "<string>", line 2, in request_json
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 113, in raise_api_exceptions
    return_value = function(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 604, in request_json
    retry_on_error=retry_on_error)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 437, in _request
    _raise_response_exceptions(response)
  File "/usr/local/lib/python2.7/dist-packages/praw/internal.py", line 207, in _raise_response_exceptions
    raise HTTPException(_raw=exc.response)
HTTPException

This one is different:

ERROR 2015-08-23 05:08:58,386 CointipBot::check_inbox(): 
ERROR 2015-08-23 05:08:58,386 CointipBot::main(): exception: 
ERROR 2015-08-23 05:08:58,433 CointipBot::main(): traceback: Traceback (most recent call last):
  File "cointipbot.py", line 558, in main
    self.check_inbox()
  File "cointipbot.py", line 241, in check_inbox
    action.do()
  File "ctb/ctb_action.py", line 310, in do
    ctb_stats.update_user_stats(ctb=self.ctb, username=self.u_from.name)
  File "ctb/ctb_stats.py", line 215, in update_user_stats
    res = ctb_misc.praw_call(r.set_flair, username, flair, '')
  File "ctb/ctb_misc.py", line 38, in praw_call
    res = prawFunc(*extraArgs, **extraKwArgs)
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 57, in wrapped
    return function(self.reddit_session, self, *args, **kwargs)
  File "<string>", line 2, in set_flair
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 245, in wrap
    if mod and not _is_mod_of_all(obj.user, subreddit):
  File "/usr/local/lib/python2.7/dist-packages/praw/decorator_helpers.py", line 23, in _is_mod_of_all
    mod_subs = user.get_cached_moderated_reddits()
  File "/usr/local/lib/python2.7/dist-packages/praw/objects.py", line 962, in get_cached_moderated_reddits
    for sub in self.reddit_session.get_my_moderation(limit=None):
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 549, in get_content
    page_data = self.request_json(url, params=params)
  File "<string>", line 2, in request_json
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 123, in raise_api_exceptions
    raise exc
HTTPException
ERROR 2015-08-24 02:42:59,019 CointipBot::check_inbox(): 
ERROR 2015-08-24 02:42:59,019 CointipBot::main(): exception: 
ERROR 2015-08-24 02:42:59,041 CointipBot::main(): traceback: Traceback (most recent call last):
  File "cointipbot.py", line 558, in main
    self.check_inbox()
  File "cointipbot.py", line 195, in check_inbox
    messages = list(ctb_misc.praw_call(self.reddit.get_unread, limit=self.conf.reddit.scan.batch_limit))
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 549, in get_content
    page_data = self.request_json(url, params=params)
  File "<string>", line 2, in request_json
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 113, in raise_api_exceptions
    return_value = function(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 604, in request_json
    retry_on_error=retry_on_error)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 437, in _request
    _raise_response_exceptions(response)
  File "/usr/local/lib/python2.7/dist-packages/praw/internal.py", line 207, in _raise_response_exceptions
    raise HTTPException(_raw=exc.response)
HTTPException

Bug when trying to run on custom coind

I am trying to run the altcointipbot on 'Gabencoin'. However, whenever I run it I come across this error:

DEBUG:bitcoin:Starting "walletpassphrase" JSON-RPC request
ERROR:bitcoin:500 (Internal Server Error) response from bitcoind
ERROR:cointipbot:CtbCoin::getnewaddr(gabencointipbot): BitcoindException: 500 (Internal Server Error) response from bitcoind
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "cointipbot.py", line 533, in __init__
    self.self_checks()
  File "cointipbot.py", line 138, in self_checks
    b.register()
  File "ctb/ctb_user.py", line 230, in register
    new_addrs[c] = self.ctb.coins[c].getnewaddr(_user=self.name.lower())
  File "ctb/ctb_coin.py", line 171, in getnewaddr
    self.conn.walletpassphrase(self.conf.walletpassphrase, 1)
  File "pifkoin/bitcoind.py", line 69, in __call__
    return server._rpc_call(self.method, *args)
  File "pifkoin/bitcoind.py", line 216, in _rpc_call
    raise BitcoindException('%d (%s) response from bitcoind' % (response.status, response.reason))
pifkoin.bitcoind.BitcoindException: 500 (Internal Server Error) response from bitcoind
You have new mail in /var/mail/gabenpool
gabenpool@capyspool:~/altcointip/src$

I think it may be pifkoin trying to load up the default configuration for a bitcoind, however I am running gabencoind with different rpc settings. Is there a way to fix this?

Lazy PRAW calls

Optimize CtbAction and CtbUser classes to reduce number of PRAW calls being made during initialization.

Coin trade functionality

It would be great to have the functionality to trade many types of alt-coins. For this, a different instance of the bot would be needed, one that focuses on trading rather than tipping. It can utilize the same user database and coin daemons, though.

Convert all SQL statements to SQLAlchemy ORM

In order to gain ability to support multiple database types, as well as improve security and reliability, convert all hardcoded SQL statements to SQLAlchemy ORM statements.

For example, instead of mysqlconn.execute("SELECT * FROM t_action") use conn.select(...).

Bot crashed and won't start again.

crash

WARNING 2014-06-18 15:35:51,393 eval_message(): CtbAction::__init__(atype=withdraw, from_user=*****): couldn't determine coinval from keyword 'all' (not float)
ERROR 2014-06-18 15:48:38,147 get_actions(): error executing <SELECT * FROM t_action WHERE type = 'givetip' AND state = 'pending' AND to_user = '********'>: can't set attribute
ERROR 2014-06-18 15:48:38,147 CointipBot::check_inbox(): can't set attribute
ERROR 2014-06-18 15:48:38,147 CointipBot::main(): exception: can't set attribute
ERROR 2014-06-18 15:48:38,236 CointipBot::main(): traceback: Traceback (most recent call last):
  File "cointipbot.py", line 558, in main
    self.check_inbox()
  File "cointipbot.py", line 241, in check_inbox
    action.do()
  File "ctb/ctb_action.py", line 303, in do
    return self.accept()
  File "ctb/ctb_action.py", line 379, in accept
    actions = get_actions(atype='givetip', to_user=self.u_from.name, state='pending', ctb=self.ctb)
  File "ctb/ctb_action.py", line 1254, in get_actions
    submission = ctb_misc.praw_call(ctb.reddit.get_submission, m['msg_link'])
  File "ctb/ctb_misc.py", line 38, in praw_call
    res = prawFunc(*extraArgs, **extraKwArgs)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 888, in get_submission
    comment_sort=comment_sort)
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 323, in wrapped
    return function(cls, *args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/praw/objects.py", line 842, in from_url
    s_info, c_info = reddit_session.request_json(url, params=params)
  File "/usr/local/lib/python2.7/dist-packages/praw/decorators.py", line 161, in wrapped
    return_value = function(reddit_session, *args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 496, in request_json
    data = json.loads(response, object_hook=hook)
  File "/usr/lib/python2.7/json/__init__.py", line 339, in loads
    return cls(encoding=encoding, **kw).decode(s)
  File "/usr/lib/python2.7/json/decoder.py", line 365, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python2.7/json/decoder.py", line 381, in raw_decode
    obj, end = self.scan_once(s, idx)
  File "/usr/local/lib/python2.7/dist-packages/praw/__init__.py", line 391, in _json_reddit_objecter
    return object_class.from_api_response(self, json_data['data'])
  File "/usr/local/lib/python2.7/dist-packages/praw/objects.py", line 56, in from_api_response
    return cls(reddit_session, json_dict=json_dict)
  File "/usr/local/lib/python2.7/dist-packages/praw/objects.py", line 510, in __init__
    underscore_names=['replies'])
  File "/usr/local/lib/python2.7/dist-packages/praw/objects.py", line 70, in __init__
    self.has_fetched = self._populate(json_dict, fetch)
  File "/usr/local/lib/python2.7/dist-packages/praw/objects.py", line 137, in _populate
    setattr(self, name, value)
  File "/usr/local/lib/python2.7/dist-packages/praw/objects.py", line 98, in __setattr__
    object.__setattr__(self, name, value)
AttributeError: can't set attribute

ERROR 2014-06-19 03:24:08,351 get_actions(): error executing <SELECT * FROM t_action WHERE type = 'givetip' AND state = 'pending' AND coin = 'ltc'>: can't set attribute
ERROR 2014-06-19 03:24:21,654 get_actions(): error executing <SELECT * FROM t_action WHERE type = 'givetip' AND state = 'pending' AND coin = 'ltc'>: can't set attribute

info.log when trying to restart

INFO 2014-06-19 03:43:46,523 < init_regex() DONE (96 expressions)
INFO 2014-06-19 03:43:46,525 CtbUser::balance(litetip): getting ltc givetip balance
ERROR 2014-06-19 03:43:48,582 get_actions(): error executing <SELECT * FROM t_action WHERE type = 'givetip' AND state = 'pending' AND coin = 'ltc'>: can't set attribute

end of debug.log when trying to restart

DEBUG 2014-06-19 03:43:47,040 get_actions(): <SELECT * FROM t_action WHERE type = 'givetip' AND state = 'pending' AND coin = 'ltc'>
DEBUG 2014-06-19 03:43:47,043 get_actions(): found http://www.reddit.com/r/teenagers/comments/28cuzp/discussion_thirst_thread/ci9owg4 / ci9owg4
ERROR 2014-06-19 03:43:48,582 get_actions(): error executing : can't set attribute

Listen to this

Append a random music track to the end of each message.

Bug report: ignore quotes

I did a write-up on Reddit and then decided it would be better just to come to the source directly anyhow. See: http://www.reddit.com/r/ALTcointip/comments/1zczq2/bug_report_ignore_quotes/

Essentially: quoted text should not be scanned for commands. I tested this without funds, so it wasn't a problem, but proper behavior should be to ignore quoted commands because they are unlikely to be intended as instructions directed at the bot. Additionally, the bot should function on a "better safe than sorry" principle as is general practice with these types of bots.

If you've got the time to do it vindimy, I'll gladly leave it to you (although I certainly wouldn't mind reviewing the patch if you'd like). If you'd like another contributor, I can also roll up my sleeves and take a look at the code. Just let me know that you agree with my correct behavior (ignore quoted text for command parsing), and would like me to do so, and I'll get started as time permits.

Thanks,
-mathwizard1232

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.