Giter Club home page Giter Club logo

hltv-livescore's Introduction

Due to a lack of resources, this project has been mostly abandoned. Please see gigobyte/HLTV for a popular and more up-to-date HLTV API. If anyone would like to take over maintenance for this project, please contact me.


HLTV Livescore

A simple Node.js library to interface with HLTV's live scorebot.

NPM Version Downloads Build Codacy Steam Donate

Contents

Introduction

This module is based on @Nols1000's original version, created back in May of 2015. It is packed full of features, but was never updated to the newest version of HLTV's scorebot. The purpose of this version is to be more of a wrapper for HLTV, and to incorporate all of the features available with the new scorebot.

Getting Started

Install with npm:

$ npm install hltv-livescore

Usage:

var Livescore = require('hltv-livescore');
var live = new Livescore({
    listid: 2299033
});

live.on('kill', function(data) {
    console.log(data.killer.name, 'killed', data.victim.name, 'with', data.weapon, data.headshot ? '(headshot)' : '');
});

Methods

Constructor([options])

  • options - An optional object containing some of the following options
    • listid - The game's listid
    • url - The URL to listen on. Defaults to http://scorebot2.hltv.org
    • port - The port to listen on. Defaults to 10022

Constructs a new Livescore. You will be automatically connected to the HLTV scorebot server. The game with the specified listid will be automatically started if provided. If not provided, you must specify them using them using the start() method.

start([options][, callback])

  • options - An optional object containing some of the following options
    • listid - The game's listid
  • callback - An optional callback.

Start the game with the specified listid. If provided in the Constructor, the listid is not required. An error will be thrown if you are not connected to the HLTV scorebot server before calling this method.

getPlayers(callback)

  • callback - Required. Called with an object of players
    • players - An object containing all the players connected to the server, with their name as the key

Retrieve all players connected to the server.

getTeams(callback)

  • callback - Required. Called with an object of players
    • teams - An object containing both teams connected to the server

Retrieve both teams connected to the server.

setTime(time)

  • time - Required. The time to set the scoreboard to (in seconds)

Set the scoreboard to a new time.

getTime(callback)

  • callback - Required. Called with the remaining time
    • time - The time remaining in seconds as displayed on the scoreboard

Retrieve the time remaining.

Events

Events emit an object containing the parameters listed under each event.

connected

Emitted when we successfully connect to the HLTV Socket.io server.

started

Emitted immediately before the first scoreboard event is emitted.

log

  • log - The log given to us by HLTV since the last log was emitted

Emitted whenever HLTV feels like giving us logs (after kills, round events, etc).

time

  • seconds - The time displayed on the timer in seconds

Emitted every time the timer on the scoreboard is updated.

scoreboard

  • teams - An object containing the two teams' objects
  • map - The current map
  • bombPlanted - true if the bomb is planted
  • currentRound - The current round number

Emitted whenever HLTV sends us a scoreboard update. The scoreboard may not be any different from the last update.

kill

  • killer - The player object of the killer
  • victim - The player object of the victim
  • weapon - The weapon used
  • headshot - true if the kill was a headshot

Emitted after every kill.

suicide

  • player - The player object of the suicider

Emitted after a player commits suicide.

bombPlanted

  • player - The player object of the bomb planter

Emitted when the bomb is planted.

bombDefused

  • player - The player object of the bomb defuser

Emitted when the bomb is defused.

roundStart

  • round - The round number.

Emitted at the start of every round.

roundEnd

  • teams - The list of teams
  • winner - The team that won
  • winType - How the team won
  • knifeRound - If we think the round was a knife round (>=5 knife kills)

Emitted at the end of every round.

playerJoin

  • playerName - The player's name

Emitted when a player joins the server.

playerQuit

  • player - The player object of the player who quit

Emitted when a player leaves the server.

mapChange

  • map - The new map

Emitted when the map is changed.

restart

Emitted when the score is restarted

Classes

Player

  • steamid - A SteamID object
  • hltvid - The player's HLTV id
  • name - The player's username
  • alive - true if the player is alive
  • money - The player's in-game money
  • rating - The player's HLTV rating for this game
  • kills - The player's total kills
  • assists - The player's total assists
  • deaths - The player's total deaths
  • team - The player's Team class

Example:

Player {
    steamid: [Object],
    hltvid: 11654,
    name: 'almazer1',
    alive: true,
    money: 12300,
    rating: 1.16,
    kills: 19,
    assists: 4,
    deaths: 17,
    team: [Object]
}

Team

  • id - The team's HLTV id
  • name - The team's name
  • score - The team's score
  • side - The team's side (ESide)
  • players - An array of the team's Player classes
  • history - The team's round history

Example:

Team {
    id: 6921,
    name: 'Vesuvius',
    score: 16,
    side: 1,
    players: [Array],
    history: [Object]
}

Round

  • type - How the round ended for this team (ERoundType)
  • round - The round number

Example:

Round {
    type: 6
    round: 12
}

Enums

There are numerous enums available for your use. All enums are located in the /resources/ directory.

EOption

Primarily for internal use. Specifies options about the module.

ERoundType

Specifies how a team ended the round.

ESide

Specifies team constants.

Examples

hltv-livescore's People

Contributors

andrewda avatar cymonbr avatar dtolstyi avatar gitter-badger avatar greenkeeper[bot] 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hltv-livescore's Issues

Newbie question

Hello,

I am using
sb.on('scoreboard', function(data) { console.log(data); });

but not sure how to get Player, Team and Round class data.

Thanks in advance.

Real round detection

We need to be able to detect if the current round is "real" or not. There are some key characteristics to "fake"/warmup rounds.

When the score is 0-0, pistols are not the only thing being used.

If this is the case, we can plainly say that all future rounds are fake until a reset.

All players have $16k

Not sure if this is always true, but seemed to be true in at least one ESEA game.

No team switch after 15 rounds

Again, not sure if this is always the case, but looks like it is at least in the ESL Pro League.

Round number is 31 (see @mrwhale's data below)

It seems that rounds numbered 31 are "warmups". We might want to check to see if the round that came before the current round was 30, and if it wasn't, add a fakeRound param.

Detect halftime resets and deal with them

Some tournaments (at least ESL and ESEA) reset the score at halftime which makes HLTV think the half is starting from 0-0. We should detect this and deal with it.

half 1 score + half 2 score = real score

A potential solution:

When the reset event is fired, check if the score is equal to 15. If it is, save it as the half 1 score and add that to the half 2 score whenever emitting the score.

A potential issue with this solution is if the warmups of the server end at 15 rounds, and so everything after that is considered the second half, even if it really is the first half.

To deal with this issue, we might need to also implement "real round" detection, but this could be very tricky. There are a few ways we could do this, noted in #19.

Reconnect

Sometimes instead of map change server closes connection. Is there any way to reconnect without relaunching node.js file manually?

Code Update

@andrewda I like this repository, but I created a new version of your code. I've created using ES6 and simpler for new features and fixes errors. Would you like to look at my code?

How does HLTV gather match info?

Hey @andrewda

Just having a quick glance at your library. It's working really well. I would like to build an application which is independent from HLTV though, so I am trying to figure out where HLTV gets their information from.

Do you have any idea or pointers? It's a subject I find super hard to Google.

Also, can it really be true that there are no official documentation of the scorebot from HLTV?

Hope you find the time to help me out.

Thanks!

Listening to multiple matches simultaneously

Hi,

I've started using your module a few days ago and it works fine, except the situation when I need to get information for multiple matches simultaneously.
I've been trying to create multiple instances of livescore and to pass an array of ids, but nothing of this works.
Do you know if it's possible to listen to multiple matches?

Not able to get score

Hi,

I am trying to get score->currentMap / mapScores data, but without any success. I am using

sb.on('score', function(data) {
    console.log('currentCtScore:', data.mapScores.currentCtScore);
});

Any ideas? thanks in advance.

HLTV has changed its websocket endpoint

Hey @andrewda
Been a while since I've used this library, but getting back into it it seems that hltv has upgraded its websocket endpoint and a few other bits and pieces during the connecting. I've played around to see how it works now and pretty sure I've got it - but will need some help integrating that back into this library

In this I was using the websocat command line client to test and connect
Endpoint - scorebot-secure.hltv.org

  1. websocat "wss://scorebot-secure.hltv.org/socket.io/?EIO=3&transport=websocket"
    • On connection you will get some information back such as an sid
  2. Echo to the server 61:42["readyForMatch","{\"token\":\"\",\"listId\":\"2328874\"}"]
  • You should immediately start getting live matchin information
  1. every so ofter you should echo back a "keep alive" of 2, the server will respond with 3. if you do not receive a response generally means the connection has closed
    Below is a print of it in action, and a look at the data structure of the json feed that it returs
mrwhale@mrwhale:~$ websocat "wss://scorebot-secure.hltv.org/socket.io/?EIO=3&transport=websocket"
0{"sid":"b347b8ad-86ab-4a67-9ef6-eb120dde1e63","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":60000}
40
>> 61:42["readyForMatch","{\"token\":\"\",\"listId\":\"2328874\"}"]
42["scoreboard",{"TERRORIST":[{"steamId":"1:1:13560020","dbId":7993,"name":"bvd. Detrony","score":26,"deaths":17,"assists":5,"alive":false,"money":100,"damagePrRound":103.48275862068965,"hp":0,"kevlar":false,"helmet":false,"nick":"Detrony","hasDefusekit":false},{"steamId":"1:0:90746920","dbId":10611,"name":"bvd. Fadey","score":26,"deaths":24,"assists":5,"alive":false,"money":950,"damagePrRound":94.44827586206897,"hp":0,"kevlar":false,"helmet":false,"nick":"Fadey","hasDefusekit":false},{"steamId":"1:0:30724686","dbId":11630,"name":"bvd. JT^","score":21,"deaths":20,"assists":9,"alive":true,"money":300,"damagePrRound":83.20689655172414,"hp":73,"primaryWeapon":"ak47","kevlar":true,"helmet":true,"nick":"JT","hasDefusekit":false},{"steamId":"1:1:8352209","dbId":8446,"name":"bvd. ELUS1VE","score":15,"deaths":17,"assists":1,"alive":true,"money":200,"damagePrRound":49.827586206896555,"hp":100,"primaryWeapon":"galilar","kevlar":true,"helmet":true,"nick":"ELUSIVE","hasDefusekit":false},{"steamId":"1:1:7432061","dbId":8711,"name":"bvd. Sonic^","score":14,"deaths":21,"assists":3,"alive":true,"money":0,"damagePrRound":48.793103448275865,"hp":70,"primaryWeapon":"ak47","kevlar":true,"helmet":true,"nick":"Sonic","hasDefusekit":false}],"CT":[{"steamId":"1:1:59654408","dbId":16647,"name":"INF_bwills","score":25,"deaths":18,"assists":3,"alive":true,"money":3550,"damagePrRound":77.10344827586206,"hp":100,"primaryWeapon":"ak47","kevlar":true,"helmet":true,"nick":"Bwills","hasDefusekit":true},{"steamId":"1:0:11946443","dbId":9342,"name":"INF_JoshRT","score":24,"deaths":20,"assists":5,"alive":false,"money":7850,"damagePrRound":89.48275862068965,"hp":0,"kevlar":false,"helmet":false,"nick":"JoshRT","hasDefusekit":false},{"steamId":"1:0:28414483","dbId":13261,"name":"INF_tweiss","score":20,"deaths":21,"assists":4,"alive":false,"money":2700,"damagePrRound":74.82758620689656,"hp":0,"kevlar":false,"helmet":false,"nick":"tweiss","hasDefusekit":false},{"steamId":"1:1:60979592","dbId":13307,"name":"INF_Strings","score":17,"deaths":23,"assists":4,"alive":true,"money":3600,"damagePrRound":68.03448275862068,"hp":100,"primaryWeapon":"m4a1","kevlar":true,"helmet":true,"nick":"Strings","hasDefusekit":true},{"steamId":"1:1:458376","dbId":9301,"name":"INF_real_bM","score":12,"deaths":20,"assists":4,"alive":true,"money":6450,"damagePrRound":64.72413793103448,"hp":65,"primaryWeapon":"m4a1","kevlar":true,"helmet":true,"nick":"real_bM","hasDefusekit":true}],"ctMatchHistory":{"firstHalf":[{"type":"Terrorists_Win","roundOrdinal":1,"survivingPlayers":5},{"type":"Terrorists_Win","roundOrdinal":2,"survivingPlayers":3},{"type":"Target_Bombed","roundOrdinal":3,"survivingPlayers":0},{"type":"Target_Bombed","roundOrdinal":4,"survivingPlayers":4},{"type":"lost","roundOrdinal":5,"survivingPlayers":0},{"type":"lost","roundOrdinal":6,"survivingPlayers":0},{"type":"lost","roundOrdinal":7,"survivingPlayers":0},{"type":"Terrorists_Win","roundOrdinal":8,"survivingPlayers":1},{"type":"Terrorists_Win","roundOrdinal":9,"survivingPlayers":2},{"type":"Terrorists_Win","roundOrdinal":10,"survivingPlayers":4},{"type":"Target_Bombed","roundOrdinal":11,"survivingPlayers":1},{"type":"lost","roundOrdinal":12,"survivingPlayers":0},{"type":"lost","roundOrdinal":13,"survivingPlayers":0},{"type":"lost","roundOrdinal":14,"survivingPlayers":0},{"type":"lost","roundOrdinal":15,"survivingPlayers":0}],"secondHalf":[{"type":"CTs_Win","roundOrdinal":16,"survivingPlayers":1},{"type":"CTs_Win","roundOrdinal":17,"survivingPlayers":1},{"type":"Bomb_Defused","roundOrdinal":18,"survivingPlayers":1},{"type":"lost","roundOrdinal":19,"survivingPlayers":0},{"type":"lost","roundOrdinal":20,"survivingPlayers":2},{"type":"lost","roundOrdinal":21,"survivingPlayers":1},{"type":"CTs_Win","roundOrdinal":22,"survivingPlayers":2},{"type":"lost","roundOrdinal":23,"survivingPlayers":1},{"type":"lost","roundOrdinal":24,"survivingPlayers":0},{"type":"lost","roundOrdinal":25,"survivingPlayers":0},{"type":"CTs_Win","roundOrdinal":26,"survivingPlayers":3},{"type":"CTs_Win","roundOrdinal":27,"survivingPlayers":3},{"type":"CTs_Win","roundOrdinal":28,"survivingPlayers":5}]},"terroristMatchHistory":{"firstHalf":[{"type":"lost","roundOrdinal":1,"survivingPlayers":0},{"type":"lost","roundOrdinal":2,"survivingPlayers":0},{"type":"lost","roundOrdinal":3,"survivingPlayers":1},{"type":"lost","roundOrdinal":4,"survivingPlayers":0},{"type":"CTs_Win","roundOrdinal":5,"survivingPlayers":4},{"type":"CTs_Win","roundOrdinal":6,"survivingPlayers":3},{"type":"CTs_Win","roundOrdinal":7,"survivingPlayers":3},{"type":"lost","roundOrdinal":8,"survivingPlayers":0},{"type":"lost","roundOrdinal":9,"survivingPlayers":0},{"type":"lost","roundOrdinal":10,"survivingPlayers":0},{"type":"lost","roundOrdinal":11,"survivingPlayers":2},{"type":"Bomb_Defused","roundOrdinal":12,"survivingPlayers":1},{"type":"CTs_Win","roundOrdinal":13,"survivingPlayers":3},{"type":"CTs_Win","roundOrdinal":14,"survivingPlayers":5},{"type":"CTs_Win","roundOrdinal":15,"survivingPlayers":5}],"secondHalf":[{"type":"lost","roundOrdinal":16,"survivingPlayers":0},{"type":"lost","roundOrdinal":17,"survivingPlayers":0},{"type":"lost","roundOrdinal":18,"survivingPlayers":0},{"type":"Terrorists_Win","roundOrdinal":19,"survivingPlayers":3},{"type":"Target_Bombed","roundOrdinal":20,"survivingPlayers":3},{"type":"Target_Bombed","roundOrdinal":21,"survivingPlayers":2},{"type":"lost","roundOrdinal":22,"survivingPlayers":0},{"type":"Target_Bombed","roundOrdinal":23,"survivingPlayers":3},{"type":"Terrorists_Win","roundOrdinal":24,"survivingPlayers":3},{"type":"Terrorists_Win","roundOrdinal":25,"survivingPlayers":2},{"type":"lost","roundOrdinal":26,"survivingPlayers":0},{"type":"lost","roundOrdinal":27,"survivingPlayers":0},{"type":"lost","roundOrdinal":28,"survivingPlayers":0}]},"bombPlanted":false,"mapName":"de_nuke","terroristTeamName":"Bravado","ctTeamName":"Infamous","currentRound":29,"counterTerroristScore":15,"terroristScore":13,"ctTeamId":9379,"tTeamId":5158,"frozen":false,"live":true,"ctTeamScore":15,"tTeamScore":13}]
42["log","{\"log\":[{\"Kill\":{\"killerName\":\"bvd. JT^\",\"killerNick\":\"JT\",\"killerSide\":\"TERRORIST\",\"victimNick\":\"tweiss\",\"victimName\":\"INF_tweiss\",\"victimSide\":\"CT\",\"weapon\":\"ak47\",\"headShot\":true}}]}"]

>> 2
3
.......

You also get some useful things during the round such as
42["log","{\"log\":[{\"MatchStarted\":{\"map\":\"de_nuke\"}}]}"]
42["log","{\"log\":[{\"MatchStarted\":{\"map\":\"de_nuke\"}}]}"]

You can also submit the string
61:42["readyForScores","{\"token\":\"\",\"listIds\":[2328874]}"] to the server to get a nice summary of the match
42["score",{"mapScores":{"1":{"firstHalf":{"ctTeamDbId":5158,"ctScore":8,"tTeamDbId":9379,"tScore":7},"secondHalf":{"ctTeamDbId":9379,"ctScore":8,"tTeamDbId":5158,"tScore":6},"overtime":{"ctTeamDbId":9379,"ctScore":0,"tTeamDbId":5158,"tScore":0},"mapOrdinal":1,"scores":{"9379":15,"5158":14},"currentCtId":9379,"currentTId":5158,"map":"de_nuke","mapOver":false}},"listId":2328874,"wins":{},"liveLog":{"":true,"IrregularTeamKillsRequirement":true,"PlayersRequirement":false,"NoSuspectEventsInFirstRoundRequirement":false,"NotKnifeRoundRequirement":true,"BombInPlayRequirement":false,"KillsInFirstRoundRequirement":false,"FiveKillsWhenEnemyElliminatedRequirement less than 5 five kills in round(s) []":true,"MatchStartRequirement":true,"MapNameRequirement":true,"NoDrawRoundsRequirement":true,"FirstRoundOverRequirement":false,"RoundOneMaxEquipmentValueRequirement":true},"forcedLive":false,"forcedDead":false}]

It seems with this method you can list a number of matchId's and get an entire summary back (this is how the main page of HLTV keeps updated for all live matches

Let me know if I can help testing. If I do get some time I may submit a PR for this

Move connect() to the constructor

Will clean up the code and be more conventional.

Instead of:

var sb = new Scorebot();
sb.connect(blah, blah);

Do:

var sb = new Scorebot({
    matchid: blah,
    listid: blah
});

Improve knife round checks

Instead of checking if there are 5 or more knife kills, check if 100% of the kills were knife kills. This will fix the issue of a knife round not being recognized if a player leaves or if the timer runs out before 5 players being eliminated.

TypeError: Cannot set property

/var/www/test/node_modules/hltv-scorebot/Scorebot.js:285
this.players[player.name] = {
^

TypeError: Cannot set property 'Rickeh intel' of undefined
at /var/www/test/node_modules/hltv-scorebot/Scorebot.js:285:35
at Array.forEach (native)
at updatePlayers (/var/www/test/node_modules/hltv-scorebot/Scorebot.js:284:7)
at Scorebot.onScoreboard (/var/www/test/node_modules/hltv-scorebot/Scorebot.js:170:5)
at Socket.Emitter.emit (/var/www/test/node_modules/component-emitter/index.js:134:20)
at Socket.onevent (/var/www/test/node_modules/socket.io-client/lib/socket.js:254:10)
at Socket.onpacket (/var/www/test/node_modules/socket.io-client/lib/socket.js:212:12)
at Manager. (/var/www/test/node_modules/component-bind/index.js:21:15)
at Manager.Emitter.emit (/var/www/test/node_modules/component-emitter/index.js:134:20)
at Manager.ondecoded (/var/www/test/node_modules/socket.io-client/lib/manager.js:301:8)

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml
  • The new Node.js version is in-range for the engines in 1 of your package.json files, so that was left alone

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Knife round recognition

In the roundend event, emit whether or not the round was recognized as a knife round. If there are at least five knife kills in a round, assume it is a knife round.

Add colour to player/team names for events to show which side they are currently on

Be a nice addition to add natively (maybe an optional library?)

In the thing I am writing using this library I have also added chalk to add colour to output. Similar to the scorebot on the hltv site

example during a kill callback

if(data.victim.team.side == 1){
        T -= 1;
        console.log(chalk.blue(data.killer.name), 'killed', chalk.red(data.victim.name), 'with', data.weapon, data.headshot ? '(headshot)' : '', '(' + chalk.blue(CT) + ' vs ' + chalk.red(T) + ')');
    }else{
        CT -= 1;
        console.log(chalk.red(data.killer.name), 'killed', chalk.blue(data.victim.name), 'with', data.weapon, data.headshot ? '(headshot)' : '', '(' + chalk.blue(CT) + ' vs ' + chalk.red(T) + ')');
    }

It will then look like this in a commandline window

screenshot from 2016-09-19 12-47-38

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.