Giter Site home page Giter Site logo

tivo-commander's Introduction

DVR Commander for TiVo is an open source clone of the TiVo for iPad and iPhone app for Android.

It is capable of connecting to and controlling a TiVo Premiere device.

DVR Commander for TiVo is copyright (C) 2011, Anthony Lieuallen. Released under the terms of the GNU GPLv2 public license.

DVR Commander for TiVo is not affiliated with TiVo Inc.

This project depends on the DragSortListView project. Make sure to check it out into an Eclipse project called DragSortListView, and build it, for the dependency to resolve correctly.

tivo-commander's People

Contributors

arantius avatar jicama avatar vondruska 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tivo-commander's Issues

Rare data driven crash on missing date

Now showing screen on first load:

 MindRpc.init3(): start auth.
    1 CALL bodyAuthenticate
 {
   "type" : "bodyAuthenticate",
   "credential" : {
     "type" : "makCredential",
     "key" : "..."
   }
 }
    1 RECV bodyAuthenticateResponse
 {
   "message" : "Authentication successful",
   "status" : "success",
   "type" : "bodyAuthenticateResponse"
 }
 Activity:Pause:Connect
 Activity:Resume:NowShowing
 MindRpc.init(): already connected.
    2 CALL bodyConfigSearch
 {
   "bodyId" : "tsn:...",
   "type" : "bodyConfigSearch"
 }
    3 CALL whatsOnSearch
 {
   "type" : "whatsOnSearch"
 }
    4 CALL videoPlaybackInfoEventRegister
    2 RECV bodyConfigList
 {
   "type" : "videoPlaybackInfoEventRegister",
   "throttleDelay" : 1001
 }
 {
   "bodyConfig" : [ {
     "bodyId" : "tsn:...",
     "hasScheduler" : true,
     "observesDaylightSaving" : true,
     "secondsFromGmt" : -18000,
     "softwareVersion" : "20.2.2.1-01-2-746",
     "userDiskSize" : "950185680",
     "state" : "active",
     "parentalControlsState" : "off",
     "userDiskUsed" : "494315520",
     "type" : "bodyConfig",
     "imageFetchingInfo" : {
       "type" : "imageFetchingInfo",
       "baseImageUrl" : "http://i.tivo.com/images-production/"
     }
   } ],
   "type" : "bodyConfigList"
 }
    4 RECV videoPlaybackInfoEvent
 {
   "begin" : "0",
   "end" : "8290282",
   "position" : "7647673",
   "speed" : 0,
   "virtualPosition" : "1367788947217",
   "type" : "videoPlaybackInfoEvent"
 }
    4 RECV videoPlaybackInfoEvent
 {
   "begin" : "0",
   "end" : "8291283",
   "position" : "7647673",
   "speed" : 0,
   "virtualPosition" : "1367788947218",
   "type" : "videoPlaybackInfoEvent"
 }
    3 RECV whatsOnList
 {
   "type" : "whatsOnList",
   "whatsOn" : [ {
     "collectionId" : "tivo:cl.63973179",
     "contentId" : "tivo:ct.63972663",
     "channelIdentifier" : {
       "channelNumber" : "734",
       "sourceType" : "cable",
       "stationId" : "tivo:st.119114326",
       "type" : "channelIdentifier"
     },
     "offerId" : "tivo:of.ctd.119114326.734.cable.2013-05-05-19-15-00.9000",
     "recordingId" : "tivo:rc.135369859",
     "activeAudioStream" : {
       "audioStreamFormat" : "ac3",
       "audioStreamId" : "tivo:aus.6362",
       "type" : "audioStream"
     },
     "availableAudioStream" : [ {
       "audioStreamFormat" : "ac3",
       "audioStreamId" : "tivo:aus.6362",
       "type" : "audioStream"
     } ],
     "type" : "whatsOn",
     "playbackType" : "liveCache"
   } ]
 }
    5 CALL offerSearch
 {
   "levelOfDetail" : "low",
   "bodyId" : "tsn:...",
   "type" : "offerSearch",
   "searchable" : true,
   "offerId" : [ "tivo:of.ctd.119114326.734.cable.2013-05-05-19-15-00.9000" ],
   "note" : [ "recordingForContentId" ],
   "namespace" : "refserver"
 }
    5 RECV offerList
 {
   "isBottom" : true,
   "isTop" : true,
   "type" : "offerList"
 }
 Failed to parse start time 
 java.text.ParseException: Unparseable date: "" (at offset 0)
    at java.text.DateFormat.parse(DateFormat.java:622)
    at com.arantius.tivocommander.NowShowing$2.onResponse(NowShowing.java:104)
    at com.arantius.tivocommander.rpc.MindRpc$3.run(MindRpc.java:296)
    at android.os.Handler.handleCallback(Handler.java:615)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4931)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558)
    at dalvik.system.NativeStart.main(Native Method)
 Adjusting end forward becase 9990700 > -1980000000
    4 RECV videoPlaybackInfoEvent
 {
   "begin" : "0",
   "end" : "8292284",
   "position" : "7647673",
   "speed" : 0,
   "virtualPosition" : "1367788947317",
   "type" : "videoPlaybackInfoEvent"
 }
 Adjusting end forward becase 9991800 > -1980000000
    4 RECV videoPlaybackInfoEvent
 {
   "begin" : "0",
   "end" : "8293285",
   "position" : "7647673",
   "speed" : 0,
   "virtualPosition" : "1367788947318",
   "type" : "videoPlaybackInfoEvent"
 }
 Adjusting end forward becase 9992802 > -1980000000
    4 RECV videoPlaybackInfoEvent
 {
   "begin" : "0",
   "end" : "8294286",
   "position" : "7647673",
   "speed" : 0,
   "virtualPosition" : "1367788947417",
   "type" : "videoPlaybackInfoEvent"
 }
 Adjusting end forward becase 9993902 > -1980000000
    4 RECV videoPlaybackInfoEvent
 {
   "begin" : "0",
   "end" : "8295787",
   "position" : "7647673",
   "speed" : 0,
   "virtualPosition" : "1367788947015",
   "type" : "videoPlaybackInfoEvent"
 }
 Adjusting end forward becase 9995001 > -1980000000

Title was empty, seek bar screwed up. The whatsOn response said liveCache but the offer gave no content. Was being recorded.

Enhance PC vs. TiVo detection

Via email from wmcbrine:

I posted a little about this on TCF the other day, but anyway, I just noticed this bit of code in Discover.java:

} else if (platform.startsWith("pc/")) {
  Utils.log("Ignoring event for PC platform.");
  // This is a e.g. a TiVo Desktop or pyTivo share. Exclude it.
  return;

I should tell you that I've only used a platform string that matches this ("pc/pyTivo") since November 2012 (and then, only for video shares); before that it was variously "pc" or "pyTivo". Hence, I guess, why some people (running older pyTivo versions) are still complaining about their pyTivo shares appearing in DVR Commander.

Maybe you could check for a TSN? pyTivo has never provided that.

So this detection logic could be a bit more intelligent about selecting away PyTivo vs. TiVo Desktop shares.

Add a "Now Viewing" screen

Which would have some basic info/options like the individual episode view that My Shows offers, and a play time readout and scrub bar and play/pause/seek buttons.

Fix the multicast error string

Device search failed. (Read below and) Try custom settings.

This isn't a great message to display when multicast detection fails.

NullPointerException in SimpleDateFormat.parse()

java.lang.NullPointerException
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1001)
at com.arantius.tivocommander.Utils.parseDateTimeStr(Utils.java:185)
at com.arantius.tivocommander.Utils.parseDateTimeStr(Utils.java:148)
at com.arantius.tivocommander.MyShows$ShowsAdapter.getView(MyShows.java:126)
at android.widget.AbsListView.obtainView(AbsListView.java:1435)
at android.widget.ListView.makeAndAddView(ListView.java:1824)
at android.widget.ListView.fillSpecific(ListView.java:1365)
at android.widget.ListView.layoutChildren(ListView.java:1651)
at android.widget.AbsListView.onLayout(AbsListView.java:1286)
at android.view.View.layout(View.java:7184)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1254)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1130)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1047)
at android.view.View.layout(View.java:7184)
at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
at android.view.View.layout(View.java:7184)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1254)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1130)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1047)
at android.view.View.layout(View.java:7184)
at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
at android.view.View.layout(View.java:7184)
at android.view.ViewRoot.performTraversals(ViewRoot.java:1180)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1914)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3859)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)

At MyShows line 126.

Error reports: custom dialog

Require that the user types something before allowing the report to be sent.
Request (require?) a picture of the TiVo device itself. (To catch non-branded devices.)
Remind users that I'm just one guy giving away a free app.

Consider moving work off of the main thread

MindRpc.dispatchRespose() runs all its callbacks on the UI thread, because that's easy -- most all of them end up constructing UI objects, and have to. But not all of them and not the whole of the work of each one. Running them on the network / a dedicated thread, and having each delegate to the UI thread only at the appropriate time might speed up the app / make it appear more responsive.

But it would be a lot of work and can't be done partially. So consider this.

A lot more logging

Log all details when trying to connect.
Log interaction with custom device pane.
Read the system log into error reports.

Can't display downloaded programs

When I try to select a show that was downloaded or pushed from pyTivo, I just get a "Response missing content" error that drops me back on the My Shows screen, making it impossible to, say, delete it.

I tried to debug this myself, but it seems that building from source requires a client certificate/password that isn't in the git repository.

Refresh the To Do list when appropriate.

  1. Navigate to To Do list.
  2. Click on an entry.
  3. Click Record...
  4. Click Don't record.
  5. Press back

The to do list will now still list the item you just set not to record. Go back and to To Do List again (to make it load data again) and now it won't. It should reload the moment you come back to it, if recordings were changed.

Don't show the error report dialog for common errors.

Today common and/or permanent errors (wrong brand device, multicast doesn't work, etc.) show the Help dialog which unconditionally includes the error report button. So I get lots of unactionable error reports.

When there are warnings/errors, trend towards showing a custom dialog, where the Help dialog is another click away.

Support platform "tcd/XG1"

User reported inability to connect to the device named "family room" which was described as a tivo premiere device (while "master bedroom" is a mini).

2013-12-18 19:59:11.487 I Start discovery query ...
2013-12-18 19:59:11.906 I Discovery serviceResolved(): [ServiceEventImpl@1119501048
name: 'Master Bedroom' type: '_tivo-mindrpc._tcp.local.' info: '[ServiceInfoImpl@1119478024 name: 'Master Bedroom._tivo-mindrpc._tcp.local.' address: '/192.199.1.100:1413 ' status: 'DNS: localhost [/192.199.1.104] state: probing 1 task: null' is persistent, has data
platform: tcd/Series4
protocol: tivo-mindrpc
swversion: 20.3.7.1b-01-6-A92
TSN: ...
mindversion: 7/11
path: /
]']
2013-12-18 19:59:11.928 I Discovery serviceResolved(): [ServiceEventImpl@1119694552
name: 'Family Room' type: '_tivo-mindrpc._tcp.local.' info: '[ServiceInfoImpl@1119632008 name: 'Family Room._tivo-mindrpc._tcp.local.' address: '/192.199.1.114:1413 ' status: 'DNS: localhost [/192.199.1.104] state: probing 1 task: null' is persistent, has data
platform: tcd/XG1
protocol: tivo-mindrpc
swversion: 20.3.7.1b-01-6-D18
TSN: ...
mindversion: 7/11
path: /
]']
2013-12-18 19:59:23.449 I Stop discovery query ...

Because it says platform: tcd/XG1 which is currently not allowed. I don't know what XG1 means, but all the premieres I've seen have said Series4.

The Now Showing screen "breaks" with video display off

TiVo. HD UI. Turn off the video in the corner (e.g. press the "slow" button on the remote to toggle it). Open the Now Showing screen. Spinner spins forever. As soon as video starts playing, then the info and scrub bar load. In this case, the whatsOnList RPC returns "playbackType" : "idle" with no content/collection/offer IDs.
The official app crashes; the official "for tablets" app says "To Be Announced" on the relevant screen.

If the SD UI is in use, the whatsOnList says "playbackType" : "loopset" with no content/collection/offer IDs. The official apps display the same behavior.

Unable to connect to Virgin Media TiVo (even with v15)

Virgin Media (in the UK) updated their firmware in January 2012 and made it difficult to connect to MindRPC. I was hopeful that your changes for #15 would make TiVo Commander work again, but apparently it doesn't.

I can confirm that the latest version (v0p8t) of kmttg DOES work.

I also used the cert & password from kmttg in my prototype Windows Phone 7 app, and got it working, too. That's using the C# version of BouncyCastle, and I did have to make a change to the sources to ignore a failure to validate the KeyUsage settings on the server certificate.

Scrub Bar wrong for old "live" show

Start a live recording. Pause the TiVo. Wait until well after the recording ends without touching the TiVo (i.e. overnight). Open DVR Commander. The scrub bar is wrong.

Rare java.lang.NullPointerException on MindRpc.addRequest

Version: v9

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.arantius.tivocommander/com.arantius.tivocommander.MyShows}: java.lang.NullPointerException
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1647)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
    at android.app.ActivityThread.access$1500(ActivityThread.java:117)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:123)
    at android.app.ActivityThread.main(ActivityThread.java:3683)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:864)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:622)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
    at com.arantius.tivocommander.rpc.MindRpc.addRequest(MindRpc.java:105)
    at com.arantius.tivocommander.MyShows.startRequest(MyShows.java:316)
    at com.arantius.tivocommander.MyShows.onCreate(MyShows.java:373)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
    ... 11 more

Or:

Caused by: java.lang.NullPointerException
at com.arantius.tivocommander.rpc.MindRpc.addRequest(MindRpc.java:105)
at com.arantius.tivocommander.Remote$1.onTextChanged(Remote.java:50)        

Inexplicable connect failure

Discovery says:

10-26 17:46:32.920: I/tivo_commander(2292): Discovery serviceResolved(): [ServiceEventImpl@1110655936 
10-26 17:46:32.920: I/tivo_commander(2292):     name: '...' type: '_tivo-mindrpc._tcp.local.' info: '[ServiceInfoImpl@1110010696 name: '...._tivo-mindrpc._tcp.local.' address: '/...:1413 ' status: 'DNS: localhost [/...] state: probing 1 task: null' is persistent, has data
10-26 17:46:32.920: I/tivo_commander(2292):     platform: tcd/Series4
10-26 17:46:32.920: I/tivo_commander(2292):     protocol: tivo-mindrpc
10-26 17:46:32.920: I/tivo_commander(2292):     swversion: 20.3.1-01-2-746
10-26 17:46:32.920: I/tivo_commander(2292):     TSN: ...
10-26 17:46:32.920: I/tivo_commander(2292):     mindversion: 7/11
10-26 17:46:32.920: I/tivo_commander(2292):     path: /
10-26 17:46:32.920: I/tivo_commander(2292): ]']

Try to connect and:

10-26 17:45:58.881: I/tivo_commander(2292): Activity:Resume:Connect
10-26 17:45:58.881: I/tivo_commander(2292): MindRpc.init3() com.arantius.tivocommander.NowShowing@421e74c0
10-26 17:46:24.100: E/tivo_commander(2292): connect: io exception!
10-26 17:46:24.100: E/tivo_commander(2292): java.net.SocketTimeoutException: failed to connect to /... (port 1413) after 25000ms
10-26 17:46:24.100: E/tivo_commander(2292):     at libcore.io.IoBridge.connectErrno(IoBridge.java:159)
10-26 17:46:24.100: E/tivo_commander(2292):     at libcore.io.IoBridge.connect(IoBridge.java:112)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:192)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:459)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.net.Socket.connect(Socket.java:842)
10-26 17:46:24.100: E/tivo_commander(2292):     at com.arantius.tivocommander.rpc.MindRpc$1.call(MindRpc.java:173)
10-26 17:46:24.100: E/tivo_commander(2292):     at com.arantius.tivocommander.rpc.MindRpc$1.call(MindRpc.java:1)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.util.concurrent.FutureTask.run(FutureTask.java:234)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:153)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:267)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
10-26 17:46:24.100: E/tivo_commander(2292):     at java.lang.Thread.run(Thread.java:841)
10-26 17:46:24.108: I/tivo_commander(2292): Settings: Could not connect. Are you connected to the right WiFi network?
10-26 17:46:24.264: I/tivo_commander(2292): Activity:Pause:Connect

Even though:

$ nmap ... -p 1413
PORT     STATE SERVICE
1413/tcp open  unknown

And most surprising, my one-off python script that I run overnight to check for interesting movies DID run and connect successfully last night! So the TiVo itself is definitely running and working correctly. Just connections from Android devices (all three tried) fail.

Run the official app and:

10-27 09:46:33.508: I/ActivityManager(372): START u0 {flg=0x4000000 cmp=com.tivophone.android/.setup.SelectDVRActivity (has extras)} from pid 7768
10-27 09:46:34.367: I/ActivityManager(372): Displayed com.tivophone.android/.setup.SelectDVRActivity: +658ms
10-27 09:47:04.922: I/ActivityManager(372): START u0 {cmp=com.tivophone.android/.setup.local.SetupActivity (has extras)} from pid 7768
10-27 09:47:05.406: I/ActivityManager(372): Displayed com.tivophone.android/.setup.local.SetupActivity: +224ms

It says to enable network control, but I have totally done that (years ago!). If I say I have done it:

10-27 09:47:52.391: I/ActivityManager(372): Displayed com.tivophone.android/.setup.ConnectToDVRActivity: +346ms

And 60 seconds later it shows a popup that says "the box is not responding". On three different devices tested, both my app and the official app. On one of those, the official app was not installed. It gave the network control prompt, but never asked for my MAK.

Rotating the phone deletes a show

Conditions are not exactly clear, but some user reports claim that rotating the phone will cause a show to be deleted.

I was in the my shows section and noticed that rotating my phone causes whatever show is selected to be deleted.

and

When using the remote function in the my shows menu, it will delete the selected show if the phone is rotated sideways

NPE in To Do list

java.lang.NullPointerException
    at com.arantius.tivocommander.ToDo.getIconForItem(ToDo.java:38)
    at com.arantius.tivocommander.ShowList$ShowsAdapter.getView(ShowList.java:167)
    at android.widget.AbsListView.obtainView(AbsListView.java:2452)
    ...

10 reports so far. Also:

java.lang.NullPointerException
    at com.arantius.tivocommander.ToDo$2.onResponse(ToDo.java:123)
    at com.arantius.tivocommander.rpc.MindRpc$3.run(MindRpc.java:296)
    at android.os.Handler.handleCallback(Handler.java:725)
    ...

2 reports of that so far.

Discovery: Sometimes shows the same device twice

It's got to be a timing/race condition, because it's very transient. But sometimes the device discovery screen will show the same device twice, once OK and once with the warning icon. It should only show once, OK, if it is in fact an OK device.

Add info to show view

The show/explore view should display:

  • Length of recording (and partial status).
  • Badge and text about how the show will be kept.
  • Full channel, date, and time of recording.

Upcoming long press: record

It would be useful in the "upcoming" view to schedule recording a particular episode with a long press, rather than the multiple taps it takes to open it and then record from there.

Refresh the Season Pass Manager when appropriate.

  1. Navigate to Season Pass Manager.
  2. Click on an entry.
  3. Click Record...
  4. Click Modify season pass
  5. Complete the modification, press OK, wait for it to complete. Acknowledge conflicts if necessary and wait to complete again.
  6. You're back at explore now, press back to return to season pass manager.

The subscription will still list the old state. Go back and to Season Pass Manager again (to make it load data again) and now it will list the new state. It should reload the moment you come back to it, if recordings were changed.

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.