Plugin Store API

With the release of the Plugin Store, we soft-launched an API that would allow an external source to get the details of the latest release for the specified plugin. We really hadn't intended to publish it because it is our intention to deliver a more fully developed API later (with API keys, etc), but we've had a lot of developers ask for the ability to get the current release from the store, so we decided to go ahead and publish this one.

General Rules

Here are a few general rules for using the API:

  • Version check failures of any kind should never be fatal or treated as a blocking/interrupting event. If it doesn't work, your script/plugin/application should log the error and continue its normal operation.
  • Never loop a call to the API quickly (especially under failure cases). You could unintentionally cause a DoS attack on our hosted systems if calls are made too frequently.
  • If you do automatic timed calls, do them at most once a day.
  • Always catch exceptions and bad HTTP return codes (400-599 basically) returned from the API call. Again, don't loop if you get a bad return code - just log the error and continue your normal operation.

We don't currently have the backend infrastructure of larger organizations, so please respect the above rules to help us avoid any systemic issues.

API v2

Despite the version number, this is in fact just the first API that can be used to get the latest version from the plugin store. It's a very simple HTTP call:

https://api.indigodomo.com/api/v2/pluginstore/plugin-version-info.json?pluginId=com.indigodomo.opensource.rachio

Assuming the call completes successfully, it will return the following JSON:

{
  "plugins": [
    {
      "docUrl": "https://github.com/IndigoDomotics/rachio-indigo/wiki", 
      "githubRepoUrl": "https://github.com/IndigoDomotics/rachio-indigo", 
      "helpUrl": "https://github.com/IndigoDomotics/rachio-indigo", 
      "iconUrl": "http://downloads.indigodomo.com/pluginstore/com.perceptiveautomation/com.indigodomo.opensource.rachio/icon_EmjXrFy.png", 
      "id": 12, 
      "latestRelease": {
        "downloadCount": 18, 
        "downloadUrl": "https://github.com/IndigoDomotics/rachio-indigo/releases/download/1.0.3/Rachio.Sprinkler.indigoPlugin.zip", 
        "number": "1.0.3", 
        "releaseDate": "2017-11-16", 
        "requirements": "", 
        "summary": "Documentation reorganization", 
        "whatsNew": "Moved the main part of the docs to [SNIPPED FOR THIS EXAMPLE]"
      }, 
      "name": "Rachio Sprinklers", 
      "pluginId": "com.indigodomo.opensource.rachio", 
      "summary": "This plugin integrates the Rachio sprinkler controllers with Indigo."
    }
  ]
}

The return is a dictionary with one key, “plugins” which is a list. In this version of the release, there will always just be one item in the “plugins” list. The item that's in that list is a dictionary with the following components:

KeyValue
docUrlThe URL to the docs or help for the plugin as specified in the plugin store.
githubRepoUrlThe URL to the GitHub repo where the plugin is hosted (blank if it's not hosted on GitHub).
helpUrlURL to get help as configured for the plugin in the store.
iconUrlURL to get the icon for the plugin.
idInternal ID for the plugin in the store. Can be used to construct a URL to the plugin's details page: https://www.indigodomo.com/pluginstore/ID_HERE/
latestReleaseDictionary with release specific information (see next table for details) OR a string if the plugin is one of the built-in plugins in Indigo (you won't process those since the version is tied to the Indigo release).
nameName of the plugin.
pluginIdString ID of the plugin (same as what you passed into the URL above).
summaryShort summary that's shown when you mouse over a plugin in the plugin list.

The dictionary returned in the latestRelease element above contains:

KeyValue
downloadCountNumber of times the plugin has been downloaded (currently relatively inaccurate).
downloadUrlURL to download the release.
numberRelease number (i.e. 1.6.9, etc.).
releaseDateDate the release was created (string of the form YYYY-MM-DD).
requirementsRequirements specified for this release (if any, otherwise empty string).
summarySummary of changes in this release.
whatsNewWhats new in this release.

Example

We want to provide an example of how you might call this from within a plugin. Eventually we will have version checking and update notifications available as a built-in feature of plugins. In the meantime, however, you can follow this pattern.

First, here's a method you can implement in your Plugin subclass. You'll need to add this import at the top: from distutils.version import LooseVersion

  def version_check(pluginId=self.pluginId):
      # Create some URLs we'll use later on
      current_version_url = "https://api.indigodomo.com/api/v2/pluginstore/plugin-version-info.json?pluginId={}".format(pluginId)
      store_detail_url = "https://www.indigodomo.com/pluginstore/{}/"
      try:
          # GET the url from the servers with a short timeout (avoids hanging the plugin)
          reply = requests.get(current_version_url, timeout=5)
          # This will raise an exception if the server returned an error
          reply.raise_for_status()
          # We now have a good reply so we get the json
          reply_dict = reply.json()
          plugin_dict = reply_dict["plugins"][0]
          # Make sure that the 'latestRelease' element is a dict (could be a string for built-in plugins).
          latest_release = plugin_dict["latestRelease"]
          if isinstance(latest_release, dict):
              # Compare the current version with the one returned in the reply dict
              if LooseVersion(latest_release["number"]) > LooseVersion(self.pluginVersion):
                  # The release in the store is newer than the current version.
                  # We'll do a couple of things: first, we'll just log it
                  self.logger.info(
                      "A new version of the plugin (v{}) is available at: {}".format(
                          latest_release["number"],
                          store_detail_url.format(plugin_dict["id"])
                      )
                  )
                  # We'll change the value of a variable named "Plugin_Name_Current_Version" to the new version number
                  # which the user can then build a trigger on (or whatever). You could also insert the download URL,
                  # the store URL, whatever.
                  try:
                      variable_name = "{}_Current_Version".format(self.pluginDisplayName.replace(" ", "_"))
                      indigo.variable.updateValue(variable_name, latest_release["number"])
                  except:
                      pass
                  # We'll execute an action group named "New Version for Plugin Name". The action group could
                  # then get the value of the variable above to do some kind of notification.
                  try:
                      action_group_name = "New Version for {}".format(self.pluginDisplayName)
                      indigo.actionGroup.execute(action_group_name)
                  except:
                      pass
                  # There are lots of other things you could do here. The final thing we're going to do is send
                  # an email to self.version_check_email which I'll assume that you've set from the plugin
                  # config.
                  if hasattr(self, 'version_check_email') and self.version_check_email:
                      indigo.server.sendEmailTo(
                          self.version_check_email, 
                          subject="New version of Indigo Plugin '{}' is available".format(self.pluginDisplayName),
                          body="It can be downloaded here: {}".format(store_detail_url)
                      )
      except Exception as exc:
          self.logger.error(unicode(exc))

This function checks the store for the latest version, and if it exists it performs several actions:

  1. It writes the to the log
  2. It updates a variable with a specific name (Plugin_Name_Current_Version) with the new release number
  3. It calls an action group with a specific name (New Version for Plugin Name)
  4. It sends an email to self.version_check_email (assumes that you set it somehow, likely from the plugin prefs)

Notice how safe this method is: it doesn't loop in any way (it shouldn't). It always catches errors and logs them but it never hangs up. It also specifies a 5 second timeout on the request. You don't want to hang your plugin up waiting for an update check call so make sure you timeout relatively quickly. 5 seconds should be plenty but you can play with that number.

So, how do you call this method? You can call it in the startup(self) method. You can add a menu item and/or action in your plugin that calls it. If your plugin doesn't implement a runConcurrentThread method, then a simple way of doing daily checks is to implement it:

  def runConcurrentThread(self):
      self.logger.debug("Starting concurrent tread")
      try:
          while True:
              self.version_check()
              self.sleep(60*60*24) # Sleep for 24 hours
      except self.StopThread:
          self.logger.debug("Received StopThread")

If your plugin does implement one, then you'll need to add the 24 hour wait logic depending on your implementation. As mentioned above, you should not check more than daily.

developer_apis/plugin_store_api.txt · Last modified: 2019/01/25 22:53 (external edit)
 

© Perceptive Automation, LLC. · Privacy