Giter Club home page Giter Club logo

capture-website-cli's Introduction

capture-website-cli

Capture screenshots of websites from the command-line

It uses Puppeteer (Chrome) under the hood.

Install

npm install --global capture-website-cli

Note to Linux users: If you get a "No usable sandbox!" error, you need to enable system sandboxing.

Usage

$ capture-website --help

  Usage
    $ capture-website <url|file>
    $ echo "<h1>Unicorn</h1>" | capture-website

  Options
    --output                 Image file path (writes it to stdout if omitted)
    --width                  Page width  [default: 1280]
    --height                 Page height  [default: 800]
    --type                   Image type: png|jpeg|webp  [default: png]
    --quality                Image quality: 0...1 (Only for JPEG and WebP)  [default: 1]
    --scale-factor           Scale the webpage `n` times  [default: 2]
    --list-devices           Output a list of supported devices to emulate
    --emulate-device         Capture as if it were captured on the given device
    --full-page              Capture the full scrollable page, not just the viewport
    --no-default-background  Make the default background transparent
    --timeout                Seconds before giving up trying to load the page. Specify `0` to disable.  [default: 60]
    --delay                  Seconds to wait after the page finished loading before capturing the screenshot  [default: 0]
    --wait-for-element       Wait for a DOM element matching the CSS selector to appear in the page and to be visible before capturing the screenshot
    --element                Capture the DOM element matching the CSS selector. It will wait for the element to appear in the page and to be visible.
    --hide-elements          Hide DOM elements matching the CSS selector (Can be set multiple times)
    --remove-elements        Remove DOM elements matching the CSS selector (Can be set multiple times)
    --click-element          Click the DOM element matching the CSS selector
    --scroll-to-element      Scroll to the DOM element matching the CSS selector
    --disable-animations     Disable CSS animations and transitions  [default: false]
    --no-javascript          Disable JavaScript execution (does not affect --module/--script)
    --module                 Inject a JavaScript module into the page. Can be inline code, absolute URL, and local file path with `.js` extension. (Can be set multiple times)
    --script                 Same as `--module`, but instead injects the code as a classic script
    --style                  Inject CSS styles into the page. Can be inline code, absolute URL, and local file path with `.css` extension. (Can be set multiple times)
    --header                 Set a custom HTTP header (Can be set multiple times)
    --user-agent             Set the user agent
    --cookie                 Set a cookie (Can be set multiple times)
    --authentication         Credentials for HTTP authentication
    --debug                  Show the browser window to see what it's doing
    --dark-mode              Emulate preference of dark color scheme
    --launch-options         Puppeteer launch options as JSON
    --overwrite              Overwrite the destination file if it exists
    --inset                  Inset the screenshot relative to the viewport or \`--element\`. Accepts a number or four comma-separated numbers for top, right, left, and bottom.
    --clip                   Position and size in the website (clipping region). Accepts comma-separated numbers for x, y, width, and height.
    --no-block-ads           Disable ad blocking

  Examples
    $ capture-website https://sindresorhus.com --output=screenshot.png
    $ capture-website index.html --output=screenshot.png
    $ echo "<h1>Unicorn</h1>" | capture-website --output=screenshot.png
    $ capture-website https://sindresorhus.com | open -f -a Preview

  Flag examples
    --width=1000
    --height=600
    --type=jpeg
    --quality=0.5
    --scale-factor=3
    --emulate-device="iPhone X"
    --timeout=80
    --delay=10
    --wait-for-element="#header"
    --element=".main-content"
    --hide-elements=".sidebar"
    --remove-elements="img.ad"
    --click-element="button"
    --scroll-to-element="#map"
    --disable-animations
    --no-javascript
    --module=https://sindresorhus.com/remote-file.js
    --module=local-file.js
    --module="document.body.style.backgroundColor = 'red'"
    --header="x-powered-by: capture-website-cli"
    --user-agent="I love unicorns"
    --cookie="id=unicorn; Expires=Wed, 21 Oct 2018 07:28:00 GMT;"
    --authentication="username:password"
    --launch-options='{"headless": false}'
    --dark-mode
    --inset=10,15,-10,15
    --inset=30
    --clip=10,30,300,1024
    --no-block-ads

FAQ

More here.

How can I capture websites from a file with URLs?

Lets say you have a file named urls.txt with:

https://sindresorhus.com
https://github.com

You can run this:

filename='urls.txt'

while read url; do
  capture-website "$url" --output "screenshot-$(echo "$url" | sed -e 's/[^A-Za-z0-9._-]//g').png"
done < "$filename"

Related

capture-website-cli's People

Contributors

dirathea avatar fisker avatar hicom150 avatar jopemachine avatar olso avatar richienb avatar sindresorhus avatar timoschwarzer 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

capture-website-cli's Issues

height capped?

  • trying to capture a page with an estimate height of 7xxx px, using the following options --width=1440 --height=7000 --full-page --scale-factor=2
  • checking the output file the remaining space at the bottom beyond 4xxxpx+ looks weird, it's as if the process gets repeated and the same page header is appended at the bottom? https://i.imgur.com/Yeko0Dz.png same page at --height=4000 https://i.imgur.com/SphUVlZ.png

EACCESS on Linux Mint

Hey,

I tried to install the CLI globally, but encountered an issue where npm didn't had the permission to install the package in /usr/lib/node_modules. However, after adding the --unsafe-perm=true flag to the sudo npm install --global capture-website-cli --unsafe-perm=true I was able to install it successfully.

Add it to the docs? or wiki?

CLI v0.2.0 crash with no args

Issue:
If running the CLI with no arguments, it crashes.

Expected behavior:
Print the help message

Reproduce:

  1. Dockerfile
FROM node:8.16.0

USER node

# Global npm dependencies into node user
# https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#global-npm-dependencies
ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
ENV PATH=$PATH:/home/node/.npm-global/bin

# Install the v0.2.0 version directly from GitHub as it is missing from NPM
RUN npm install -g https://github.com/sindresorhus/capture-website-cli.git#f041897098b0af8421b36e008238934319a87e42

CMD ["capture-website"]
  1. Build the docker image docker build -t capture_test .
  2. Run the docker image
$ docker run -t capture_test

TypeError: Expected a string, got undefined
    at module.exports (/home/node/.npm-global/lib/node_modules/capture-website-cli/node_modules/file-url/index.js:6:9)
    at captureWebsite (/home/node/.npm-global/lib/node_modules/capture-website-cli/node_modules/capture-website/index.js:50:38)
    at Object.module.exports.buffer (/home/node/.npm-global/lib/node_modules/capture-website-cli/node_modules/capture-website/index.js:232:49)
    at /home/node/.npm-global/lib/node_modules/capture-website-cli/cli.js:190:45
    at Object.<anonymous> (/home/node/.npm-global/lib/node_modules/capture-website-cli/cli.js:192:3)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)

SyntaxError: Unexpected reserved word

Hi there,
I installed this package with sudo npm install --global capture-website-cli and getting the following error when running capture-website --help:

file:///usr/local/lib/node_modules/capture-website-cli/cli.js:295
await main();
^^^^^

SyntaxError: Unexpected reserved word
    at Loader.moduleStrategy (internal/modules/esm/translators.js:133:18)
    at async link (internal/modules/esm/module_job.js:42:21)

I'm running this on Debian 11 under WSL 2 in Windows 10 using node 12.22.12 and npm 7.5.2.

Thanks!
Devin

Overwrite CacheDirectory with --launch-options

How can I set the CacheDirectory for Puppeteer calling capture-website-cli with --launch-options? I tried --launch-options='{"cacheDirectory": "/my/path/.cache/puppeteer"}' but I still get Error: Could not find Chromium (rev. 1108766). and your cache path is incorrectly configured (which is: /Users/me/.cache/puppeteer). Any advice?

Option --wait-for-element seems to be ignored

In my workflow I heavily rely on the --wait-for-element option. In the latest release v.3.0.1 nothing happens and eventually capture-website times out.

I am on a Mac with Apple silicon and it seems that puppeteer is not yet fully supported on my architecture. Thus, I am not quite sure whether the problem is with capture-website or puppeteer.

As an aside, using the v.2.3.0 release works splendidly even though the installer reports the warning
WARN deprecated [email protected]: < 19.4.0 is no longer supported

Thanks for a great piece of software!

TimeoutError

Using latest version on Ubuntu 18.04

When trying to fetch a URL the script didn't finish running and I had to exit with Ctrl+C. When I did that, the following message was displayed:

TimeoutError: Navigation timeout of 30000 ms exceeded
    at Promise.then (/usr/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/LifecycleWatcher.js:142:21)
    at <anonymous>
  -- ASYNC --
    at Frame.<anonymous> (/usr/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/helper.js:111:15)
    at Page.goto (/usr/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/Page.js:675:49)
    at Page.<anonymous> (/usr/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/helper.js:112:23)
    at captureWebsite (/usr/lib/node_modules/capture-website-cli/node_modules/capture-website/index.js:239:51)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:189:7)

The URL in question does exist and isn't particularly large: 4.52 MB with 30 HTTP requests.

Could you advise on how to fix? Happy to help if you point me in the right direction.

`--style="red.css"` does not work

Style file will not be included?

using the command

. .\node_modules\.bin\capture-website.ps1 https://appjigger.com --output=appjigger.com.jpeg --full-page --style="red.css" --overwrite

red.css:

* {
  background: red !important;
}

ERR_UNSUPPORTED_ESM_URL_SCHEME error

Hi,
if I run capture-website "https://ondata.github.io/vaccinipertutti/?area=SIC" --output=screenshot.png I have

internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only file and data URLs are supported by the default ESM loader. Received protocol 'node:'
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:782:11)
    at Loader.resolve (internal/modules/esm/loader.js:85:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:229:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:51:40)
    at link (internal/modules/esm/module_job.js:50:36) {
  code: 'ERR_UNSUPPORTED_ESM_URL_SCHEME'
}

Thank you

Error: Failed to launch the browser process!

capture-website https://sindresorhus.com --output=screenshot.png
Error: Failed to launch the browser process!
/usr/local/lib/node_modules/capture-website-cli/node_modules/puppeteer/.local-chromium/linux-722234/chrome-linux/chrome: /usr/local/lib/node_modules/capture-website-cli/node_modules/puppeteer/.local-chromium/linux-722234/chrome-linux/chrome : ne peut exécuter le fichier binaire


TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md

    at onClose (/usr/local/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/Launcher.js:750:14)
    at Interface.<anonymous> (/usr/local/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/Launcher.js:739:50)
    at Interface.emit (events.js:317:22)
    at Interface.close (readline.js:410:8)
    at Socket.onend (readline.js:188:10)
    at Socket.emit (events.js:317:22)
    at endReadableNT (_stream_readable.js:1215:12)
    at processTicksAndRejections (internal/process/task_queues.js:84:21)

and this page not exist

https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md

in windows and laravel 8 + php8.1 error

php artisan queue:work
[2022-12-21 17:59:56][72] Processing: App\Jobs\LinkScreenshot
node:events:491
throw er; // Unhandled 'error' event
^

Error: EOF: end of file, write
at Socket._write (node:internal/net:61:25)
at writeOrBuffer (node:internal/streams/writable:392:12)
at _write (node:internal/streams/writable:333:10)
at Writable.write (node:internal/streams/writable:337:10)
at file:///C:/Users/f4soft/AppData/Roaming/npm/node_modules/capture-website-cli/cli.js:289:18
Emitted 'error' event on Socket instance at:
at emitErrorNT (node:internal/streams/destroy:151:8)
at emitErrorCloseNT (node:internal/streams/destroy:116:3)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
errno: -4095,
syscall: 'write',
code: 'EOF'
}

Node.js v18.12.1

Partially hot: not able to use it in github workflows

Hi,
I have this very basic script

folder="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

mkdir -p "$folder"/rawdata
mkdir -p "$folder"/processing

npm list -g --depth=0

rm "$folder"/rawdata/"$reg".png
capture-website --delay 5 --full-page --width 1280 --height  720 --output  "$folder"/rawdata/"$reg".png "https://ondata.github.io/vaccinipertutti/?area=SIC"

I run it using this github workflow (it's Ubuntu 20.04.2 LTS), in which I set capture-website-cli installation in this way

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH
source ~/.profile
NPM_CONFIG_PREFIX=~/.npm-global
npm install -g capture-website

But when the script runs I have this error:

./test.sh: line 13: capture-website: command not found

It seems that it has been not installed in ~/.npm-global.
If I run find /home/runner -executable -name capture-website I have

/home/runner/.npm-global/lib/node_modules/capture-website

Do you have some advice to solve my problem?

Thank you

ERR_CERT_AUTHORITY_INVALID

hi, when i want to connect on resource with self signer certificate, i receive error:

C:\Users\user>capture-website --overwrite -d 10 --format=png --filename=image123.png "https://127.0.0.1
file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/puppeteer-core/lib/esm/puppeteer/common/Frame.js:210
                    ? new Error(`${response.errorText} at ${url}`)
                      ^

Error: net::ERR_CERT_AUTHORITY_INVALID at https://127.0.0.1
    at navigate (file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/puppeteer-core/lib/esm/puppeteer/common/Frame.js:210:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Frame.goto (file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/puppeteer-core/lib/esm/puppeteer/common/Frame.js:180:21)
    at async CDPPage.goto (file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/puppeteer-core/lib/esm/puppeteer/common/Page.js:437:16)
    at async internalCaptureWebsiteCore (file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/capture-website/index.js:282:2)
    at async internalCaptureWebsite (file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/capture-website/index.js:162:10)
    at async Pageres.create (file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/pageres/dist/index.js:226:28)
    at async file:///C:/Users/user/AppData/Roaming/npm/node_modules/pageres-cli/node_modules/p-map/index.js:141:20

Node.js v18.12.1

how to fix it?

Error creating screenshot for specific website

When executing the following command, I get an error. I tested the command with different URLs and so far this URL is the only one that creates problems for me.

username@hostname:~$ capture-website "https://pp.ipd.kit.edu/lehre/SS2021/compiler/uebung" --output="TEST.png"
Error: Evaluation failed: Event
    at ExecutionContext._evaluateInternal (/home/username/.nvm/versions/node/v14.17.0/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/cjs/puppeteer/common/ExecutionContext.js:221:19)
    at processTicksAndRejections (internal/process/task_queues.js:95:5)
    at async DOMWorld.addStyleTag (/home/username/.nvm/versions/node/v14.17.0/lib/node_modules/capture-website-cli/node_modules/puppeteer/lib/cjs/puppeteer/common/DOMWorld.js:247:21)
    at async internalCaptureWebsite (file:///home/username/.nvm/versions/node/v14.17.0/lib/node_modules/capture-website-cli/node_modules/capture-website/index.js:244:3)
    at async Object.captureWebsite.file (file:///home/username/.nvm/versions/node/v14.17.0/lib/node_modules/capture-website-cli/node_modules/capture-website/index.js:408:21)
    at async file:///home/username/.nvm/versions/node/v14.17.0/lib/node_modules/capture-website-cli/cli.js:271:3

TypeError: options.removeElements.map is not a function

The error in exact terms was:
TypeError: options.removeElements.map is not a function at captureWebsite (/Volumes/Files/Users/leif/.config/yarn/global/node_modules/capture-website/index.js:137:44) at process._tickCallback (internal/process/next_tick.js:68:7)

with the command being:
capture-website --emulate-device="iPhone X" --remove-elements=".cookie-dialog" https://erlensee-aktuell.com ea.jpg

Same occurrence for --hide-elements with matching names.

Thanks for all your great work!

Multiple filenames

Hello,
Is there way to set multiple output filenames in one instance of pageres?

Someting like this:

pageres [ http://google.com --filename='goo' ] [ http://facebook.com --filename='face' ]

How to use the --cookie parameter? // Error: Protocol error (Network.setCookies): Invalid parameters cookies.0.expires: double value expected

Hello *,

how can I use the --cookie parameter? The examples section says --cookie="id=unicorn; Expires=Wed, 21 Oct 2018 07:28:00 GMT;".

Putting this into a CLI command will not work. Here's an example:

$ ~/node_modules/capture-website-cli/cli.js https://www.google.de --cookie="CONSENT=YES+DE.en+V9; Expires=Sat, 09 Jan 2038 07:28:00 GMT;" --output=test.png
^CError: Protocol error (Network.setCookies): Invalid parameters cookies.0.expires: double value expected
    at Promise (/home/knipser/node_modules/capture-website-cli/node_modules/puppeteer/lib/Connection.js:183:56)
    at new Promise (<anonymous>)
    at CDPSession.send (/home/knipser/node_modules/capture-website-cli/node_modules/puppeteer/lib/Connection.js:182:12)
    at Page.setCookie (/home/knipser/node_modules/capture-website-cli/node_modules/puppeteer/lib/Page.js:416:26)
    at process._tickCallback (internal/process/next_tick.js:68:7)
  -- ASYNC --
    at Page.<anonymous> (/home/knipser/node_modules/capture-website-cli/node_modules/puppeteer/lib/helper.js:111:15)
    at captureWebsite (/home/knipser/node_modules/capture-website-cli/node_modules/capture-website/index.js:213:14)
    at process._tickCallback (internal/process/next_tick.js:68:7)

After the CLI was entered the process hangs until I press CTRL-c (you can see the ^C in the output) and then the error messages appears.

Shouldn't the --cookie parameter be parsed so the underlying library got some dictionary instead of a simple string? In the cli.js the parameter will be just arrifyed.

Doesn't work on Ubuntu 22.04 with 2.3.0 + nodejs v12.22.9

Doesn't work on Ubuntu 22.04 with 2.3.0 + nodejs v12.22.9

capture-website --debug https://sindresorhus.com

Just opens a blank chrome window and does nothing. It says chrome is controlled by automated test software. And it keeps running until force closed. Without --debug it doesn't open browser window, just keeps running.

image

Sandboxing using

sudo sysctl -w kernel.unprivileged_userns_clone=1 doesn't work either.

However it does work with test.js

sudo npm install -g [email protected]
sudo npm install -g [email protected]

cd /home/<user>/Documents/Project-A/ && npm link capture-website

nano test.js
import captureWebsite from 'capture-website';

captureWebsite.file('https://sindresorhus.com', 'screenshot.png');

node test.js

It does produce screenshot.png just fine. Only cli doesn't work

Iny idea why ?

Set item in localStorage

Is it possible to set an item in local storage prior to the website rendering? The --script argument seems to be executing the code after the page loads.

capture-website https://site.com --output site.png --script='localStorage.setItem("showSomething",false);'

connect ECONNREFUSED 0.0.0.0:443

capture-website https://sindresorhus.com --output=screenshot.png

Puppeteer old Headless deprecation warning:
In the near future headless: true will default to the new Headless mode
for Chrome instead of the old Headless implementation. For more
information, please see https://developer.chrome.com/articles/new-headless/.
Consider opting in early by passing headless: "new" to puppeteer.launch()
If you encounter any bugs, please report them to https://github.com/puppeteer/puppeteer/issues/new/choose.

node:internal/deps/undici/undici:11457
Error.captureStackTrace(err, this);
^

TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11457:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Promise.all (index 0)
at async Promise.all (index 0)
at async internalCaptureWebsite (file:///Users/zhongyan/.nvm/versions/node/v18.16.0/lib/node_modules/capture-website-cli/node_modules/capture-website/index.js:161:20)
at async captureWebsite.file (file:///Users/zhongyan/.nvm/versions/node/v18.16.0/lib/node_modules/capture-website-cli/node_modules/capture-website/index.js:446:21)
at async main (file:///Users/zhongyan/.nvm/versions/node/v18.16.0/lib/node_modules/capture-website-cli/cli.js:289:3)
at async file:///Users/zhongyan/.nvm/versions/node/v18.16.0/lib/node_modules/capture-website-cli/cli.js:295:1 {
cause: Error: connect ECONNREFUSED 0.0.0.0:443
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16) {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '0.0.0.0',
port: 443
}
}

Node.js v18.16.0

image

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.