Coder Social home page Coder Social logo

cloudflare-ddns-client's People

Contributors

1deadpixl avatar anonymouslemming avatar bsod-ultimate avatar dylanvanassche avatar kyleboyer avatar linkiwi avatar mgrddsj avatar mygod avatar olimortimer avatar point-source avatar snuggle avatar vincejv avatar wtpascoe avatar zaanposni avatar zyeoman 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

cloudflare-ddns-client's Issues

Not updating all configured domain

I have 2 domains under my Cloudflare account
and I followed the instruction to set it up
a.com,b.net
but when I do

cloudflare-ddns --update-now

it only updates a.com and ignores b.net
Output:

Updating the A record (ID .....) of (sub)domain a.com (ID ...) to 123.456.789.0.
DNS record is already up-to-date; taking no action

Some issue report

Hello,

Fresh install (ubuntu 18), when I launched => cloudflare-ddns --configure
it asked me for my api token and sub domain (nothing else).

When I tried to launch => cloudflare-ddns --update-now

=> Configuration file /root/.cloudflare-ddns not found or invalid! Did you run cloudflare-ddns --configure?

I had to manually check your code and edit the configure file to manually add :

auth_type
email
api_key

because I only had "domains" in my configure file for some reason.

Also, this => https://api.ipify.org/?format=json
timed-out, had to use the http link.

Now it's working, I'm just letting you know about the issue I had.

Thanks for the script !

Client does not paginate through all account domains

When I configure to update sub.domain.com it comes back with:

The domain sub.domain.com doesn't appear to be one of your CloudFlare domains. We only found [...] and the list of TLDs it finds (.com, .co.uk, .uk) is a fraction of what I have in my CF account. Is there some restriction on TLDs that are pulled back?

Issues Detecting IPv6 Addresses

There seems to be an issue with alternate IP address services added in #39 on IPv6 capable networks where the IPv6 address is returned and this script tries to update an A record with it.

Thankfully, the AAAA record gets updated correctly! We should not be trying to update an A record with an IPv6 address though.

● ddns.service - Automatically updates the CloudFlare DNS record upon IP address change.
   Loaded: loaded (/etc/systemd/system/ddns.service; static; vendor preset: disabled)
   Active: inactive (dead) since Wed 2020-06-17 12:31:06 BST; 8s ago
  Process: 307083 ExecStart=/usr/local/bin/cloudflare-ddns --update-now (code=exited, status=0/SUCCESS)
 Main PID: 307083 (code=exited, status=0/SUCCESS)

Jun 17 12:33:21 hug systemd[1]: Starting Automatically updates the CloudFlare DNS record upon IP address change....
Jun 17 12:33:24 hug cloudflare-ddns[307665]: Cannot fetch your external ip. https://api.ipify.org not reachable.
Jun 17 12:33:24 hug cloudflare-ddns[307665]: Found external IPv4: "33a4:a2c9:9db2:4690:18d2:faed:64fe:087c"
Jun 17 12:33:24 hug cloudflare-ddns[307665]: Found external IPv6: "33a4:a2c9:9db2:4690:18d2:faed:64fe:087c"
Jun 17 12:33:24 hug cloudflare-ddns[307665]: Listing all zones.
Jun 17 12:33:24 hug cloudflare-ddns[307665]: Finding all DNS records.
Jun 17 12:33:24 hug cloudflare-ddns[307665]: Updating the A record (ID 123) of (sub)domain domain.com (ID 123) to 33a4:a2c9:9db2:4690:18d2:faed:64fe:087c.
⚠️ Jun 17 12:33:24 hug cloudflare-ddns[307665]: DNS record failed to update.
⚠️ Jun 17 12:33:24 hug cloudflare-ddns[307665]: CloudFlare returned the following errors: [{'code': 9004, 'message': 'This kind of record cannot be proxied'}, {'code': 9005, 'message': 'Content for A record is invalid. Must be a valid IPv4 address'}].
Jun 17 12:33:24 hug cloudflare-ddns[307665]: CloudFlare returned the following messages: []
Jun 17 12:33:24 hug cloudflare-ddns[307665]: Updating the AAAA record (ID 123) of (sub)domain domain.com (ID 123) to 33a4:a2c9:9db2:4690:18d2:faed:64fe:087c.
Jun 17 12:33:24 hug cloudflare-ddns[307665]: DNS record is already up-to-date; taking no action
Jun 17 12:33:24 hug systemd[1]: Started Automatically updates the CloudFlare DNS record upon IP address change..

Request: Quiet / Silent Mode

When using in a cron job I think I'd be nice to only receive output emails when an action was taken.

Running once a day at midnight with local mail results in the same repeated message of "no changes", but with a quiet option the command would only produce output (and hence an email) if a change was made.

Perhaps a --quiet or --silent-unchanged flag could be added?

--configure should ask if user wants to change the auth method

It would be aweseom if --configure would ask the user wether he wants to change his autherization method or not.

Since I am playing with my (sub)domains I often search for my old api token or just generate a new one. This
really annoying as the only thing I am changing are the domains.

That would be really helpful feature.

ImportError: No module named tld

I'm experiencing an weird error

image

As you can see above, tld module is installed alright, and I can import the module manually. But when I run cloudflare-ddns command it tells me no module named tld is there.

I reinstalled the package with umask set to 022 (as suggested here) and added /usr/local/lib to $PATH where python module packages reside just in case, none of which worked.

Maybe little bit of help would be really appreciated. Thanks.

Not working. ImportError.

After doing a git clone and make install as root ran the command: cloudflare-ddns --configure

Traceback (most recent call last): File "/usr/local/bin/cloudflare-ddns", line 10, in <module> from tld import get_fld File "/usr/local/lib/python2.7/dist-packages/tld/__init__.py", line 7, in <module> from .utils import get_fld, get_tld, get_tld_names, is_tld, parse_tld, Result, update_tld_names File "/usr/local/lib/python2.7/dist-packages/tld/utils.py", line 10, in <module> from backports.functools_lru_cache import lru_cache ImportError: No module named functools_lru_cache

Python 2.7.17
Python 3.7.5
pip 18.1 from /usr/lib/python2.7/dist-packages/pip (python 2.7)
pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7)

which output:
/usr/local/bin/cloudflare-ddns

OS: Ubuntu 20.04 LXC VPS

Not asking for email address

When I try to configure the software it is only asking for the API key and the domain(s) but it's not asking for the email address. Also, I do not see the API key in the config file, only the domains. Where is it stored?

Can you give me a sample for the config file so I can configure it manually and make it work?

image

Thanks

update dns record fail

[root@localhost ~]# /usr/local/bin/cloudflare-ddns --update-now

This is the output

Found external IPv4: "1.164.249.xxx"
Listing all zones.
Finding all DNS records.

Actually still not updated

get_fld

Hi,

there's a typo (I think) in your script. Instead of get_tld, you use get_fld. This does not work here (fails to import error). When I change get_fld to get_tld in your script, the script works

ddns-client wrongly changes sub-domain IP's

I have 3 servers, one local and 2 cloud servers.
The local uses the domain myserver.com and is running ddns-client.
The cloud servers use sub-domains of myserver.com;
cloudone.myserver.com & cloudtwo.myserver.com.

My .cloudflare-ddns config file is;

[email protected]
api_key=a5daf3b6c0if7f7dfsd6fsd9e56d7c2fb7c
domains=myserver.com

As both cloud servers using the sub-domain names have fixed IP addresses, I don't need ddns-client to update their IP's, but everytime ddns-client is run, it also updates one of the sub-domain IP addresses in Cloudflare to the IP address of my local server. The result is that one of my cloud servers cannot then be accessed, and I have to login to Cloudflare and manually change the IP back again to it's fixed IP address.

Occasional timeout with api.ipify.org

Hey! This doesn't seem to be the most major issue ever but occasionally I seem to get timeout errors from https://api.ipify.org. Perhaps it might be a good idea to use multiple redundant sources to get the machine's public IP address since a single service might one day go down. Some other external IP services include https://ifconfig.io/ or https://ident.me/ or https://ifconfig.me/ or https://icanhazip.com

Ideally if one service fails, the script would handle it and try another provider.

Mar 27 11:33:45 hug systemd[1]: Starting Automatically updates the CloudFlare DNS record upon IP address change....
Mar 27 11:33:52 hug cloudflare-ddns[9827]: Traceback (most recent call last):
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/connectionpool.py", line 384, in _make_request
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     six.raise_from(e, None)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "<string>", line 3, in raise_from
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/connectionpool.py", line 380, in _make_request
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     httplib_response = conn.getresponse()
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib64/python3.6/http/client.py", line 1346, in getresponse
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     response.begin()
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib64/python3.6/http/client.py", line 307, in begin
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     version, status, reason = self._read_status()
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib64/python3.6/http/client.py", line 268, in _read_status
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib64/python3.6/socket.py", line 586, in readinto
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     return self._sock.recv_into(b)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib64/python3.6/ssl.py", line 968, in recv_into
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     return self.read(nbytes, buffer)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib64/python3.6/ssl.py", line 830, in read
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     return self._sslobj.read(len, buffer)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib64/python3.6/ssl.py", line 587, in read
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     v = self._sslobj.read(len, buffer)
Mar 27 11:33:52 hug cloudflare-ddns[9827]: socket.timeout: The read operation timed out
Mar 27 11:33:52 hug cloudflare-ddns[9827]: During handling of the above exception, another exception occurred:
Mar 27 11:33:52 hug cloudflare-ddns[9827]: Traceback (most recent call last):
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/requests/adapters.py", line 449, in send
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     timeout=timeout
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/connectionpool.py", line 638, in urlopen
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     _stacktrace=sys.exc_info()[2])
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/util/retry.py", line 368, in increment
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     raise six.reraise(type(error), error, _stacktrace)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/packages/six.py", line 693, in reraise
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     raise value
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/connectionpool.py", line 600, in urlopen
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     chunked=chunked)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/connectionpool.py", line 386, in _make_request
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/urllib3/connectionpool.py", line 306, in _raise_timeout
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
Mar 27 11:33:52 hug cloudflare-ddns[9827]: urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='api.ipify.org', port=443): Read timed out. (read timeout=6)
Mar 27 11:33:52 hug cloudflare-ddns[9827]: During handling of the above exception, another exception occurred:
Mar 27 11:33:52 hug cloudflare-ddns[9827]: Traceback (most recent call last):
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/local/bin/cloudflare-ddns", line 298, in <module>
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     main()
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/local/bin/cloudflare-ddns", line 279, in main
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     external_ip = get_external_ip()
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/local/bin/cloudflare-ddns", line 115, in get_external_ip
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     r = requests.get(EXTERNAL_IP_QUERY_API, timeout=6)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/requests/api.py", line 75, in get
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     return request('get', url, params=params, **kwargs)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/requests/api.py", line 60, in request
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     return session.request(method=method, url=url, **kwargs)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/requests/sessions.py", line 533, in request
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     resp = self.send(prep, **send_kwargs)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/requests/sessions.py", line 646, in send
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     r = adapter.send(request, **kwargs)
Mar 27 11:33:52 hug cloudflare-ddns[9827]:   File "/usr/lib/python3.6/site-packages/requests/adapters.py", line 529, in send
Mar 27 11:33:52 hug cloudflare-ddns[9827]:     raise ReadTimeout(e, request=request)
Mar 27 11:33:52 hug cloudflare-ddns[9827]: requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='api.ipify.org', port=443): Read timed out. (read timeout=6)
Mar 27 11:33:52 hug systemd[1]: ddns.service: Main process exited, code=exited, status=1/FAILURE
Mar 27 11:33:52 hug systemd[1]: ddns.service: Failed with result 'exit-code'.
Mar 27 11:33:52 hug systemd[1]: Failed to start Automatically updates the CloudFlare DNS record upon IP address change..
Mar 27 11:35:19 hug systemd[1]: Starting Automatically updates the CloudFlare DNS record upon IP address change....
Mar 27 11:35:21 hug cloudflare-ddns[9853]: Found external IPv4: "123.123.123.123"

request.get() results in 403

I have a problem with the token authentification.
Everytime I run the script I got the message that I should check my login data. But they are correct. So I started to search where the authentification fails.

I found that

zone_resp = requests.get(CLOUDFLARE_ZONE_QUERY_API, headers=auth, timeout=6, params={'per_page': 50, 'page': cur_page})
print(zone_resp)

is not working properly.
zone_resp: <Response [403]>

I have no clue why this is happening. If I find a fix to this problem I'll make a pull request.

Cloudflare protection turned off after --update-now

Hi there,

every time the --update-now command is ran manually or via crontab, the cloudflare protection is being turned off.

I mean the cloud icon in the DNS section of cloudflare.com.
This is a very useful feature i want to always be enabled. It protects/hides my IP.

Is this behaivor intentional?

Best regards
cryllical

Request: Select non-temporary (non-private) global ipv6 if available

I see the latest commit does address the issue of local addresses being assigned as permanent addresses on the interface, but I wouldn't consider all other addresses to be "permanent" or even suitable for inclusion in a DNS AAAA record.

Here's an example:

$ ip -6 addr show scope global
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2601:844:4000:750:XXXX:XXXX:XXXX:9431/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 86383sec preferred_lft 14383sec
3: eno2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2601:844:4000:750:ffff:74:8:10/128 scope global dynamic noprefixroute 
       valid_lft 4470sec preferred_lft 1770sec
    inet6 2601:844:4000:750:XXXX:XXXX:XXXX:9432/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 86383sec preferred_lft 14383sec

Each interface has a private, temporary address: marked by mngtmpaddr which means:

(IPv6 only) make the kernel manage temporary addresses created from this one as
template on behalf of Privacy Extensions (RFC3041). For this to become active, the
use_tempaddr sysctl setting has to be set to a value greater than zero. The given
address needs to have a prefix length of 64. This flag allows to use privacy
extensions in a manually configured network, just like if stateless auto-
configuration was active.

And I think that if an address is intended to be 'private' and 'temporary' we should instead prefer a non-private address, even if it might still be labeled as 'temporary' by the lifetime valid/preferred timers or 'dynamic'. ('dynamic' addresses can still be assigned to something unchanging by the router, like it is in my case, which also has SLAAC)

Improve feedback/error messages in cli

Hey,

I have recently started to use your tool.
However, there were a few things that bothered me.
If you do not mind, I would like to contribute and improve feed back messages in the CLI and various other stuff. Feel free to assign me to this issue.
Just posting this issue so noone else works on the same stuff at the same time 😄.

Thanks!

After hours of trying to master the Cloudflare API (and failing!), I've installed your script which works great.
There's obviously a lot of work gone into writing this, and wanted to thank you for sharing.

Paul

Subdomains

Will this update the subdomains as well?

Fails to find A or CNAME records when name parameter does not perfectly match subdomain

I have records which, when fetched by the api, report like so:

type: A
name: *.domain.com

type: CNAME
name: www.domain.com

type: MX
name: domain.com

type: TXT
name: domain.com

The docs for this tool suggest that when I provide my domain name, I provide it in the format domain.com. However, the code uses a filter parameter with the api which filters against the "name" attribute of each record. This means, in my example, I only get the MX and TXT records returned so the A and CNAME records do not get returned to the program and therefore not updated.

There are multiple workarounds for this including modifying my config to be domains=domain.com,*.domain.com,www.domain.com but this feels somewhat unintuitive unless mentioned specifically in the docs. Alternatively, I can modify the script to search for those alternative subdomains automatically or I could remove the name param entirely but this could possibly have adverse affects for some users whereby records are unintentionally updated. (not sure)

I am starting this issue to discuss the most viable course of action to fix this. Happy to do a PR if you like. Thanks!

SSL Error

Unable to update the IP, following error happens:

root@master:~# cloudflare-ddns --update-now
From cffi callback <function _verify_callback at 0x7600d430>:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/OpenSSL/SSL.py", line 309, in wrapper
    _lib.X509_up_ref(x509)
AttributeError: 'module' object has no attribute 'X509_up_ref'
Traceback (most recent call last):
  File "/usr/local/bin/cloudflare-ddns", line 193, in <module>
    main()
  File "/usr/local/bin/cloudflare-ddns", line 183, in main
    external_ip = get_external_ip()
  File "/usr/local/bin/cloudflare-ddns", line 91, in get_external_ip
    return requests.get(EXTERNAL_IP_QUERY_API, timeout=6).json()['ip']
  File "/usr/local/lib/python2.7/dist-packages/requests/api.py", line 75, in get
    return request('get', url, params=params, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/api.py", line 60, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 533, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/sessions.py", line 646, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/requests/adapters.py", line 514, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='api.ipify.org', port=443): Max retries exceeded with url: /?format=json (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))

I'm able to curl https://api.ipify.org with no errors, also the API SSL is fine

I'm able to fix this bt running sudo python -m easy_install --upgrade pyOpenSSL so it might be using incorrect versions by default

Add git updating to execution

As there has been some mistakes on the master branch, I would suggest to add something like

git fetch 
git pull origin master

To the execution when running cloudflare-ddns --update-now.
This update process can be made optional by asking the user during cloudflare-ddns --configure if he would like to receive updates.

What are your thoughts on that @LINKIWI ?

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.