jimhigson / ring-api

A promisified API for Ring Doorbell APIs
102 stars 15 forks source link

Ring API

An unofficial, friendly Javascript API for ring doorbells, cameras, etc.

Requires a JS runtime that supports ES6 async/await or else traspilation

usage

const RingApi = require( 'ring-api' );

// note that RingApi returns a promise - the promise resolves when you are authenticated/
// authorised and have a session ready to start interacting with your ring deviecs. This
// promise will reject if for some reason you are not able to log in
const ringApi = await RingApi( {

    // note - that the email and password can also be given by setting the RING_USER 
    // and RING_PASSWORD environment variables. For example if you want to keep
    // passwords out of your source code
    email: 'you@example.com',
    password: 'password you use on ring.com',

    // the user agent parameter has been removed since Ring have started rejecting
    // unrecognised agent strings
    //userAgent: 'any string',

    // OPTIONAL: if true, ring-api will poll behind the scenes.
    // Listening for events only works if this is on.
    // True by default.
    poll: true,

    // OPTIONAL
    // Set this if you need to run in a browser behind a proxy, for example
    // to get around x-origin request restrictions. Ring don't have CORS headers.
    // once set, all requests will be made relative to this value
    // default is 'https://api.ring.com/clients_api'
    // If running in node, you almost certainly want to leave this out
    serverRoot: 'http://example.com'
} );

Listening for activity on your ring devices

const logActivity = activity => console.log( 'there is a activity', activity );

ringApi.events.on('activity', logActivity);

The event will be fired on rings and motion detected. To distinguish between then, use the activity.kind property.

Where the activity object looks like:

{
   kind: 'motion',  // 'motion' or 'ring',
   // note - id will be a string - Javascript Number can't do large integers
   id: '6500907085284961754',
   id_str: '6500907085284961754', // same as id
   state: 'ringing',
   protocol: 'sip',
   doorbot_id: 3861978, // id of the device that is ringing
   doorbot_description: 'Back garden',
   device_kind: 'hp_cam_v1',
   motion: true,
   snapshot_url: '',  // seems to always be blank   
   expires_in: 175,
   now: Date, // js Date object
   optimization_level: 1,

   // various sip-related fields for the video:
   sip_server_ip: '...',
   sip_server_port: 15063,
   sip_server_tls: true,
   sip_session_id: '...',
   sip_from: '...',
   sip_to: '..',
   audio_jitter_buffer_ms: 300,
   video_jitter_buffer_ms: 300,
   sip_endpoints: null,
   sip_token: 'long hex token',
   sip_ding_id: '6500907085284961754', // seems to always be the same as the id
}

Getting a list of devices

// returns a promise
ringApi.devices();

Where the promise will resolve to an object like:

{
    all: [ /* all your devices in one array */ ],
    doorbells: [ /* array of doorbells */ ]
    authorizedDoorbells: [], // other people's doorbells you are authorised for
    chimes: [ /* array of chimes */ ],
    cameras: [ /* array of cameras, floodlight cams, spotlight cams etc */ ] ],
    baseStations: [] // presumably if you have a chime pro with the wifi hotspot built in
}

Turning lights on and off

const prompt = require('node-ask').prompt;

async function lightsOnAndOff() {

   const devices = await ringApi.devices();

   console.log( `turning on all lights` );

   // note that .lightOn() returns a promise
   // with the magic of promises we can turn them all on asynchronously
   await Promise.all( devices.cameras.map( c => c.lightOn() ) );

   await prompt( 'all your lights are now on, hit return to turn them off' ); 

   await Promise.all( devices.cameras.map( c => c.lightOff() ) );

   console.log( 'they\'re all off again!');
};

lightsOnAndOff();

Getting device history and videos

async function logMyRingHistory() {

   const history = await ringApi.history();
   const firstVideoUrl = await history[0].videoUrl();

   console.log( 'latest video is at', firstVideoUrl );

   const allRecentVideos = Promise.all( history.map( h => h.videoUrl() ) );

   console.log( 'list of all recent videos:', await allRecentVideos );   
};

starting a livestream

So far this only works so far as getting the SIP details of the stream. To get this, can do:

// returns a promise that will resolve to the livestream SIP information:
devices.doorbells[0].liveStream();

getting device health

async function printHealth( device ) {
   const strength = (await device.health()).latest_signal_strength;
   console.log( `${device.description} wifi strength is ${strength}` );
}

// asynchronously print the health of the first of each kind of device,
// without worrying about the order they are printed in:
const devices = await ringApi.devices();
printHealth( devices.doorbells[0] );
printHealth( devices.chimes[0] );
printHealth( devices.cameras[0] );

debugging

To get extended debugging info, since ring-api uses https://www.npmjs.com/package/debug you can set the DEBUG environment variable to ring-api (or add ring-api to it if it is already set)

For example, to run the example script from bash with debugging you might do:

env DEBUG="$DEBUG ring-api" ./examples/example-script

Thanks to