diff --git a/doc/_ext/jsonlexer.py b/doc/_ext/jsonlexer.py new file mode 100644 index 000000000..1a76bd510 --- /dev/null +++ b/doc/_ext/jsonlexer.py @@ -0,0 +1,14 @@ +def setup(app): + # enable Pygments json lexer + try: + import pygments + if pygments.__version__ >= '1.5': + # use JSON lexer included in recent versions of Pygments + from pygments.lexers import JsonLexer + else: + # use JSON lexer from pygments-json if installed + from pygson.json_lexer import JSONLexer as JsonLexer + except ImportError: + pass # not fatal if we have old (or no) Pygments and no pygments-json + else: + app.add_lexer('json', JsonLexer()) diff --git a/doc/api/changes.rst b/doc/api/changes.rst new file mode 100644 index 000000000..81f135a63 --- /dev/null +++ b/doc/api/changes.rst @@ -0,0 +1,87 @@ +API Changes +=========== + +This page lists changes to the Advanced API. The current version is 2.11. This versioning scheme has been introduced in [1]. + +Version 2.11 +------------ + + added Download All subscriptions + +Version 2.10 +------------ + + added Authentication API [2] + added Device Synchronization API [3] + added Podcast Lists API [4] + added include_actions parameter to Device Update API [5] + + +Version 2.9 +----------- + + added XML format to some Simple API requests [6] [7] + + +Version 2.8 +----------- + + added JSONP as a format to Simple API requests [8] + + +Version 2.7 +----------- + + added API Parametrization + + +Version 2.6 +----------- + + added "released" to Retrieving Episode Data and Listing Favorite Episodes + + +Version 2.5 +----------- + added "Subscribers Last Week" to Retrieving Podcast Data [9] + + +Version 2.4 +----------- + + added Saving a Setting [10] + added Retrieving Settings + added Listing Favorite Episodes + + +Version 2.3 +----------- + + added Retrieving Updates for a Device + + +Version 2.2 +----------- + + added Retrieving Top Tags + added Retrieving Podcasts of a Tag + added Retrieving Podcast Data + added Retrieving Episode Data + + +Version 2.1 +----------- + + added aggregated=true to Retrieving episode actions [11] + + +Version 2.0 +----------- + + added Add/remove subscriptions + added Retrieving subscription changes + added Uploading episode actions + added Retrieving episode actions + added (Re)naming devices and setting the type + added Getting a list of devices + diff --git a/doc/api/deprecation.rst b/doc/api/deprecation.rst deleted file mode 100644 index 2313b9934..000000000 --- a/doc/api/deprecation.rst +++ /dev/null @@ -1,16 +0,0 @@ -Deprecation Information -======================= - - - - -Deprecation Policy ------------------- - -TODO: specify deprecation policy - - -Deprecated Functionality ------------------------- - -*currently none* diff --git a/doc/api/index.rst b/doc/api/index.rst index aa889d582..74bf8e73b 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -3,16 +3,27 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -API 3 Documentation (draft) -=========================== +API Documentation +================= -*This site contains drafts for the gpodder.net's future API -- version 3 -- -which is not yet implemented. The current API is documented at -http://wiki.gpodder.org/wiki/Web_Services/API_2.* +This is the specification of Version 2 of the public API for the gpodder.net +Web Services. -The gpodder.net API allows clients to interact with gpodder.net. The API is -provided via a REST interface, and is free of charge within certain quotas, and -completely free for open source clients (see :ref:`usage`). +Please consult the :doc:`integration` before integrating the gpodder.net API +in your application. + +There are two different APIs for different target audiences: + +* The **Simple API** targets developers who want to write quick integration + into their existing applications +* The **Advanced API** targets developers who want tight integration into their + applications (with more features) + +The API is versioned so that changes in the major version number indicate +backwards incompatible changes. All other changes preserve backwards +compatibility. See :doc:`changes` for a list of changes. The current +version is 2.11. This versioning scheme has been introduced in `bug 1273 +`_. Contents -------- @@ -20,8 +31,7 @@ Contents .. toctree:: :maxdepth: 2 - usage integration reference/index - deprecation - libraries + Libraries + changes diff --git a/doc/api/integration.rst b/doc/api/integration.rst index 529b14640..ac18d2f90 100644 --- a/doc/api/integration.rst +++ b/doc/api/integration.rst @@ -68,8 +68,8 @@ device Id like the following * a web based player (*mywebservice-myusername*) When a previously unknown device Id is used in some API request, a device is -automatically created. Refer to the :ref:`devices-api` on how to provide some -information about the device. Users can manage their devices `online +automatically created. Refer to the :doc:`reference/devices` on how to provide +some information about the device. Users can manage their devices `online `_. @@ -77,7 +77,7 @@ Podcast Directory ^^^^^^^^^^^^^^^^^ The most basic *passive* integration with gpodder.net is to access some of its -public data. Refer to the :ref:`directory-api` for available endpoints. +public data. Refer to the :doc:`reference/directory` for available endpoints. Subscription Management @@ -86,7 +86,7 @@ Subscription Management The most common form of *active* integration is subscription management. Clients can upload the podcast subscriptions using their device Id and receive subscription changes (for their device) that were made online. Refer to the -:ref:`subscriptions-api` for additional information. +:doc:`reference/subscriptions` for additional information. Episode Actions Synchronization @@ -95,5 +95,5 @@ Episode Actions Synchronization Clients can upload and download certain actions (episode downloaded, played, deleted) to/from gpodder.net. This gives the user a central overview of where and when he accessed certain podcast episodes, and allows clients to -synchronise states between applications. Refer to the :ref:`events-api` for -further information. +synchronise states between applications. Refer to the :doc:`reference/events` +for further information. diff --git a/doc/api/libraries.rst b/doc/api/libraries.rst deleted file mode 100644 index fce08a615..000000000 --- a/doc/api/libraries.rst +++ /dev/null @@ -1,2 +0,0 @@ -Client Libraries -================ diff --git a/doc/api/reference/auth.rst b/doc/api/reference/auth.rst index 31e6e6fad..609a62a1e 100644 --- a/doc/api/reference/auth.rst +++ b/doc/api/reference/auth.rst @@ -1,2 +1,36 @@ Authentication API ================== + +Login / Verify Login +-------------------- + +.. http:post:: /api/2/auth/(username)/login.json + :synopsis: verify the login status + + * since 2.10 + + Log in the given user for the given device via HTTP Basic Auth. + + :param username: the username which should be logged in + :status 401: If the URL is accessed without login credentials provided + :status 400: If the client provides a cookie, but for a different username than the one given + :status 200: the response headers have a ``sessionid`` cookie set. + + The client can use this URL with the cookie in the request header to check + if the cookie is still valid. + + +Logout +------ + +.. http:post:: /api/2/auth/(username)/logout.json + :synopsis: logout + + * since 2.10 + + Log out the given user. Removes the session ID from the database. + + :param username: the username which should be logged out + :status 200: if the client didn't send a cookie, or the user was + successfully logged out + :status 400: if the client provides a cookie, but for a different username than the one given diff --git a/doc/api/reference/clientconfig.rst b/doc/api/reference/clientconfig.rst new file mode 100644 index 000000000..bd76ee7fe --- /dev/null +++ b/doc/api/reference/clientconfig.rst @@ -0,0 +1,34 @@ +Client Parametrization +====================== + +The client configuration file is located at +http://gpodder.net/clientconfig.json and contains information that clients +should retrieve before making requests to the APIs. + +If a client cannot retrieve and process this file (either temporarily or +permanently), it can assume the default values provided below. However, +the URLs in the file might reflect changed URLs and/or mirror servers. If a +client decides to permanently ignore this file, it might hit an outdated URL +or an overloaded server. + + +Commented Example +----------------- + +.. code-block:: json + + { + "mygpo": { + "baseurl": "http://gpodder.net/" + } + + "mygpo-feedservice": { + "baseurl": "http://mygpo-feedservice.appspot.com/" + } + + "update_timeout": 604800, + } + +* ``mygpo/baseurl``: URL to which the gpodder.net API Endpoints should be appended +* ``mygpo-feedservice/baseurl``: Base URL of the gpodder.net feed service +* ``update_timeout``: Time in seconds for which the values in this file can be considered valid. diff --git a/doc/api/reference/devices.rst b/doc/api/reference/devices.rst index ceb9430e9..a29303bc4 100644 --- a/doc/api/reference/devices.rst +++ b/doc/api/reference/devices.rst @@ -1,93 +1,139 @@ -.. _devices-api: +Device API +========== -Devices API -=========== +Update Device Data +------------------ -Devices are used throughout the API to identify a device / a client -application. A device ID can be any string matching the regular expression -``[\w.-]+``. The client application MUST generate a string to be used as its -device ID, and SHOULD ensure that it is unique within the user account. A good -approach is to combine the application name and the name of the host it is -running on. +.. http:post:: /api/2/devices/(username)/(deviceid).json + :synopsis: Sets the name and type of the device. -The API maintains subscriptions per device ID. Two distinct devices using the -same ID might receive (from their point of view) incomplete information. While -it is possible to retrieve a list of devices and their IDs from the server, -this SHOULD NOT be used to let a user select an existing device ID. + * Requires HTTP authentication + * Since 2.0 -Each device has exactly one type, which can be either *desktop*, *laptop*, -*mobile*, *server* or *other*. When retrieving device information, clients -SHOULD map unknown device types to *other*. Clients MUST NOT send -different device types that those previously stated. + The device ID is generated by the client application to identify itself in + API requests. The name is used to display a human readable identifier to + the user on the webservice. Only the keys that are supplied will be + updated. + **Example request**: -Resources ---------- + .. sourcecode:: http -The Devices API defines the following resources :: + POST /api/2/devices/a-user/somedevice-123.json - /user/{username}/devices - /user/{username}/device/{device_id} + { + "caption": "gPodder on my Lappy", + "type": "laptop" + } + :param username: the username for which device data should be updated + :param deviceid: see :ref:`devices` + :"], + + "updates": [ + { + "title": "TWiT 245: No Hitler For You", + "url": "http://www.podtrac.com/pts/redirect.mp3/aolradio.podcast.aol.com/twit/twit0245.mp3", + "podcast_title": "this WEEK in TECH - MP3 Edition", + "podcast_url": "http://leo.am/podcasts/twit", + "description": "[...]", + "website": "http://www.podtrac.com/pts/redirect.mp3/aolradio.podcast.aol.com/twit/twit0245.mp3", + "mygpo_link": "http://gpodder.net/episode/1046492" + "released": """2009-12-12T09:00:00" + "status": "(new|play|download|delete)" + } + ], + + "timestamp": + } + + :query since: ``timestamp`` when updates have last been retrieved + :query bool include_actions: Default: false, since 2.10 + + The response will have the following form and will contain + + * a list of subscriptions to be added, with URL, title and descriptions + * a list of URLs to be unsubscribed + * a list of updated episodes + * the current timestamp; for retrieving changes since the last query + + If include_actions is set to true, each updated episode (with a state other + than new) will contain an additional property action which includes + the user's latest episode action reported for this episode. The actions + have the same format as in :ref:`episode-action-types`. -If no device with the specified Id exists, a new device is created. Default -values are used for missing fields. diff --git a/doc/api/reference/directory.rst b/doc/api/reference/directory.rst index 843af8551..f21987198 100644 --- a/doc/api/reference/directory.rst +++ b/doc/api/reference/directory.rst @@ -1,243 +1,238 @@ -.. _directory-api: - Directory API ============= -The Directory API can be used to discover podcasts. - -TODO: report problems with podcasts (eg duplicates, missing data) - - -Resources ---------- - -The Directory API defines the following resources :: - - /search/podcasts - /directory/toplist - /directory/tags/latest - /directory/tag/{tag} - /user/{username}/suggestions - - -Podcast Toplist ---------------- - -The podcast toplist ranks podcasts by their number of subscribers. - - -Parameters -^^^^^^^^^^ - -* **lang**: a ISO 639-1 (two-letter) language code. If given, only podcasts in - this language are returned. If omitted, no language restriction is made. - -Request -^^^^^^^ - -Get the toplist :: - - GET /directory/toplist{?lang:2} - Content-Tpe: application/json - - -Response -^^^^^^^^ - -The response contains a ``toplist`` member which has a list of -:ref:`podcast-type` objects. The first entry in the list represents the highest -ranking podcast in the toplist. If a ``lang`` parameter was included in the -request, it is also included in the response. :: - - 200 OK - Content-Tpe: application/json - - { - "toplist": [ - podcast1, - podcast2, - podcast3, - ... - ], - "lang": "en" - } - - -Podcast Search --------------- - -Parameters -^^^^^^^^^^ - -* **q**: query string (mandatory) - - -Request -^^^^^^^ - -The search query is provided as a GET parameter. :: - - GET /search/podcasts{?q} - Content-Tpe: application/json - - -Responses -^^^^^^^^^ - -If the search could be performed, the search results (if any) are returned in -the ``search`` member. The query is returned in the ``query`` member. :: - - 200 OK - Content-Type: application/json - - { - "search": [ - podcast1, - podcast2, - ... - ], - "query": "query text" - } - - -If the search could not be performed, for example because the query was -missing :: - - 400 Bad Request - Content-Type: application/json - - { - "message": "parameter q missing", - "errors": [ - { - field: "?q", - code: "parameter_missing" - } +Retrieve Top Tags +----------------- + +.. http:get:: /api/2/tags/(int:count).json + :synopsis: Returns a list of the count most used tags. + + * Does not require authentication + * Since 2.2 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + [ + { + "title": "Technology", + "tag": "technology", + "usage": 530 + }, + { + "title": "Society & Culture", + "tag": "society-culture", + "usage": 420 + }, + { + "title": "Arts", + "tag": "arts", + "usage": 400 + }, + { + "title": "News & Politics", + "tag": "News & Politics", + "usage": 320 + } ] - } - - -Example -^^^^^^^ -Example:: + :param count: number of tags to return - GET /search/podcasts?q=linux - Content-Tpe: application/json +Retrieve Podcasts for Tag +------------------------- - 200 OK - Content-Tpe: application/json +.. http:get:: /api/2/tag/(tag)/(int:count).json + :synopsis: Returns the most-subscribed podcasts that are tagged with tag. - { - "search": [ - { "url": "http://example.com/feed.rss", ...}, - { ... }, - ... - ], - "query": "linux" - } + * Does not require authentication + * Since 2.2 + **Example response**: + .. sourcecode:: http -Latest Tags ------------ + HTTP/1.1 200 OK -The "Latest Tags" endpoint returns *current* tags. Those are tags for which -podcasts have recently published a new episode. + [ + {"url": "http://leo.am/podcasts/floss", + "title": "FLOSS Weekly", + "description": "Each Thursday we talk about Free Libre and Open Source Software with the people who are writing it. Part of the TWiT Netcast Network.", + "subscribers": 1138, + "logo_url: "http://leoville.tv/podcasts/coverart/floss144audio.jpg", + "website": "http://twit.tv/", + "mygpo_link": "http://gpodder.net/podcast/12925"}, -Parameters -^^^^^^^^^^ - -* **num**: number of tags to return (optional, default: 10) - - -Request -^^^^^^^ - -The number of tags to return can be included in the request. :: - - GET /directory/tags/latest{?num} - Content-Tpe: application/json - - -Reponse -^^^^^^^ + {"url": "http://leo.am/podcasts/twit", + "title": "this WEEK in TECH - MP3 Edition", + "description": "Your first podcast of the week is the last word in tech. [...]", + "subscribers": 895, + "logo_url": "http://leoville.tv/podcasts/coverart/twit144audio.jpg", + "website": "http://thisweekintech.com/", + "mygpo_link": "http://thisweekintech.com/"} + ] -In the ``tags`` member a list of :ref:`tag-type` objects is provided. :: + :param tag: URL-encoded tag + :param count: maximum number of podcasts to return - 200 OK - Content-Tpe: application/json - Link: ; rel="https://api.gpodder.net/3/relation/tag-podcasts"; title="Podcasts for tag {label}" - { - "tags": [ - { "label": "Technology" }, - { ... }, - ... - ] - } +Retrieve Podcast Data +--------------------- -Clients can use the provided ``Link`` header and resolve the `URI template -`_ to obtain the URL for retrieving the -podcasts of a certain tag. +.. http:get:: /api/2/data/podcast.json + Returns information for the podcast with the given URL or 404 if there is + no podcast with this URL. -Podcasts for Tag ----------------- + * No authentication required + * Since 2.2 -Clients can retrieve podcasts for a given tag. + .. sourcecode:: http + HTTP/1.1 200 OK -Request -^^^^^^^ + { + "website": "http://coverville.com", + "mygpo_link": "http://www.gpodder.net/podcast/16124", + "description": "The best cover songs, delivered to your ears two to three times a week!", + "subscribers": 19, + "title": "Coverville", + "url": "http://feeds.feedburner.com/coverville", + "subscribers_last_week": 19, + "logo_url": "http://www.coverville.com/art/coverville_iTunes300.jpg" + } -Request. :: + ::query url: the feed URL of the podcast - GET /directory/tag/{tag} - Content-Tpe: application/json +Retrieve Episode Data +--------------------- -Response -^^^^^^^^ +.. http:get:: /api/2/data/episode.json -Response. :: + Returns information for the episode with the given {episode-url} that + belongs to the podcast with the {podcast-url} - 200 OK - Content-Tpe: application/json + * Does not require authentication + * Since 2.2 (added released in 2.6) - TODO: body + **Example response**: + .. sourcecode:: http -Podcast Suggestions -------------------- + HTTP/1.1 200 OK -Clients can retrieve suggested podcasts for the current user. + { + "title": "TWiT 245: No Hitler For You", + "url": "http://www.podtrac.com/pts/redirect.mp3/aolradio.podcast.aol.com/twit/twit0245.mp3", + "podcast_title": "this WEEK in TECH - MP3 Edition", + "podcast_url": "http://leo.am/podcasts/twit", + "description": "[...]", + "website": "http://www.podtrac.com/pts/redirect.mp3/aolradio.podcast.aol.com/twit/twit0245.mp3", + "released": "2010-12-25T00:30:00", + "mygpo_link": "http://gpodder.net/episode/1046492" + } + ::query podcast-url: feed URL of the podcast to which the episode belongs + ::query episode-url: media URL of the episode -Request -^^^^^^^ -Request. :: +Podcast Toplist +--------------- - GET /user/{username}/suggestions - Content-Tpe: application/json +.. http:get:: /toplist/(int:number).(format) + :synopsis: Get list of most popular podcasts + + * Does not require authentication (public content) + * Since 1.0 + + **Example request**: + + .. sourcecode:: http + + GET /toplist/50.json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + [ + { + "website": "http://linuxoutlaws.com/podcast", + "description": "Open source talk with a serious attitude", + "title": "Linux Outlaws", + "url": "http://feeds.feedburner.com/linuxoutlaws", + "position_last_week": 0, + "subscribers_last_week": 1736, + "subscribers": 1736, + "mygpo_link": "http://www.gpodder.net/podcast/11092", + "logo_url": "http://linuxoutlaws.com/files/albumart-itunes.jpg" + }, + { + "website": "http://syndication.mediafly.com/redirect/show/d581e9b773784df7a56f37e1138c037c", + "description": "We're not talking dentistry here; FLOSS all about Free Libre Open Source Software. Join hosts Randal Schwartz and Leo Laporte every Saturday as they talk with the most interesting and important people in the Open Source and Free Software community.", + "title": "FLOSS Weekly Video (large)", + "url": "http://feeds.twit.tv/floss_video_large", + "position_last_week": 0, + "subscribers_last_week": 50, + "subscribers": 50, + "mygpo_link": "http://www.gpodder.net/podcast/31991", + "logo_url": "http://static.mediafly.com/publisher/images/06cecab60c784f9d9866f5dcb73227c3/icon-150x150.png" + }] + + :query jsonp: a functionname on which the response is wrapped (only valid + for format ``jsonp``; since 2.8) + :query scale_logo: returns logo URLs to scaled images, see below. + :param number: maximum number of podcasts to return + :param format: see :ref:`formats` + + + The number field might be any value in the range 1..100 (inclusive both + boundaries). + + For the JSON and XML formats, an optional paramter scale_logo={size} can be + passed, which provides a link to a scaled logo (scaled_logo_url) for each + podcast. size has to be a positive number up to 256 and defaults to 64. + + The OPML and TXT formats do not add any information about the (absolute and + relative) popularity for each podcast, only the ordering can be + considered. The JSON format includes a more detailed list, usable for + clients that want to display a detailed toplist or post-process the + toplist: + + All shown keys must be provided by the server. The description field may be + set to the empty string in case a description is not available. The title + field may be set to the URL in case a title is not available. The + subscribers_last_week field may be set to zero if no data is available. The + client can use the subscribers_last_week counts to re-sort the list and get + a ranking for the last week. With this information, a relative "position + movement" can also be calculated if the developer of the client decides to + do so. +Podcast Search +-------------- -Response -^^^^^^^^ +.. http:get:: /search.(format) -The response contains a ``suggestions`` member which has a list of -:ref:`podcast-type` objects. :: + Carries out a service-wide search for podcasts that match the given query. + Returns a list of podcasts. See :ref:`formats` for details on the response + formats. - 200 OK - Content-Tpe: application/json + * Does not require authentication (public content) + * Since 2.0 - { - "suggestions": [ - { podcast1 }, - { podcast2 }, - ... - ] - } + :query q: search query + :query jsonp: used to wrap the JSON results in a function call (JSONP); the + value of this parameter is the name of the function; since + 2.8 + :query scale_logo: when set, the results (only JSON and XML formats) + include links to the podcast logos that are scaled to + the requested size. The links are provided in the + scaled_logo_url field; since 2.9 + :param format: see :ref:`formats` diff --git a/doc/api/reference/events.rst b/doc/api/reference/events.rst index e3ac1aa87..049cad50c 100644 --- a/doc/api/reference/events.rst +++ b/doc/api/reference/events.rst @@ -1,211 +1,156 @@ -.. _events-api: - -Events API -========== - -The **Events API** can be used by clients to exchange information about the -status of podcast episodes. Clients upload events with certain actions such as -*play* to inform other clients that an episode has already been played. Clients -can receive episode actions, for example on startup, to update the status of -episodes accordingly. - - -Resources ---------- - -The Events API defines the following resources :: - - /user/{username}/events - /user/{username}/events/response/{id} - /user/{username}/events/device/{device_id} - - -Event Data ----------- - -An event is a JSON object with the following attributes. - -+--------------+----------+---------------------------------------------------+ -| Attribute | Required | Values | -+==============+==========+===================================================+ -| action | yes | one of ``play``, ``download``, ``flattr``, | -| | | ``delete`` | -+--------------+----------+---------------------------------------------------+ -| timestamp | yes | The UTC timestamp at which the event occured as | -| | | `RFC 3339 `_ | -+--------------+----------+---------------------------------------------------+ -| podcast | yes | The feed URL of the podcast | -+--------------+----------+---------------------------------------------------+ -| episode | yes | The media URL of the episode | -+--------------+----------+---------------------------------------------------+ -| device | no | The device ID at which the event was triggered | -+--------------+----------+---------------------------------------------------+ -| started | no | The position (in seconds) at which a play event | -| | | started | -+--------------+----------+---------------------------------------------------+ -| positon | no | The position (in seconds) at which a play event | -| | | was stopped | -+--------------+----------+---------------------------------------------------+ -| total | no | The total duratin (in seconds) of the media file | -+--------------+----------+---------------------------------------------------+ - - -Event Types -^^^^^^^^^^^ - -The following types of events (*actions*) are currently defined. - -* *play*: the episode has been played -* *download*: the episode has been downloaded -* *flattr*: the episode has been flattered -* *delete*: the episode has been deleted - -Additional event types might be defined in the future. To ensure forward -compatability, clients should accept (in if necessary ignore) events of unknown -type. - - -Upload Events -------------- - -Clients can upload new events to gpodder.net to make them available online and -for other clients. Clients MUST NOT upload events that have not been triggered -through them (ie events that have been donwloaded from the API). - -Clients SHOULD aggregate events in upload them in batches. - - -Request -^^^^^^^ - -To initiate an upload, the following request should be issued. :: - - POST /user/{username}/events - Content-Tpe: application/json - - { - events: [ - { event }, - { event }, - ... +Episode Actions API +=================== + +The episode actions API is used to synchronize episode-related events between +individual devices. Clients can send and store events on the webservice which +makes it available to other clients. The following types of actions are +currently accepted by the API: download, play, delete, new. Additional types +can be requested on the Mailing List. + +Example use cases + +* Clients can send ``download`` and ``delete`` events so that other clients + know where a file has already been downloaded. +* Clients can send ``play`` events with ``position`` information so that other + clients know where to start playback. +* Clients can send ``new`` states to reset previous events. This state needs + to be interpreted by receiving clients and does not delete any information + on the webservice. + + +.. _episode-action-types: + +Episode Action Types +-------------------- + +* download +* delete +* play +* new +* flattr + + +Upload Episode Actions +---------------------- + +.. http:post:: /api/2/episodes/(username).json + :synopsis: Upload new episode actions + + * Requires HTTP authentication + * Since 2.0 + + Upload changed episode actions. As actions are saved on a per-user basis + (not per-device), the API endpoint is the same for every device. For + logging purposes, the client can send the device ID to the server, so it + appears in the episode action log on the website. + + **Example request**: + + .. sourcecode:: http + + POST /api/2/episodes/some-user.json + + [ + { + "podcast": "http://example.com/feed.rss", + "episode": "http://example.com/files/s01e20.mp3", + "device": "gpodder_abcdef123", + "action": "download", + "timestamp": "2009-12-12T09:00:00" + }, + { + "podcast": "http://example.org/podcast.php", + "episode": "http://ftp.example.org/foo.ogg", + "action": "play", + "started": 15, + "position": 120, + "total": 500 + } ] - } - -Clients MUST NOT upload an empty list of events. - -TODO: add ``default_data`` attribute to object, to avoid repeating the same -data over and over? - -Clients MUST NOT upload invalid event objects. - -If events reference devices that do not yet exist, they are automatically -created with default data. - -TODO: specify device-id at top-level? what about events that don't belong to -any device? move device-id to URL? - - -Response -^^^^^^^^ - -The following responses are possible. - -The uploaded list of events has been processed immediatelly :: - - 204 No Content - - -The uploaded list of events has been accepted for later processing :: - - 202 Accepted - - -TODO: Validation? - - -Data -^^^^ - -No payload data is returned as a response to uploading events. - - -Download Events ---------------- - -Clients can download events to retrieve those that have been uploaded by other -devices (or itself) since a certain timestamp. - -It is RECOMMENDED that clients do not persistently store events after they have -been uploaded. Instead they SHOULD download events after an upload, to retrieve -all events that they (and other clients) have uploaded. - - -Requests -^^^^^^^^ - -Download requests retrieve events that have been uploaded since a specific -timestamp. This timestamp can be given explicitly (if the corresponding -timestamp value is maintained) is implicitly by the device-id (the -server maintains a timestamp per device). - -**Explicit Timestamp:** retrieve all events after a specific timestamp. :: - - GET /user/{username}/events{?since} - -Parameters - -* since: numeric timestamp value (mandatory) - - -**Implicit Timestamp:** retrieve all events that the current device has not yet -seen. The timestamp of is stored on the server side. :: - - GET /user/{username}/device/{device_id}/events - -Parameters - -* reset: can be set to true to reset the server-side timestamp to 0 and - retrieve all events. - - -Responses -^^^^^^^^^ - -Response :: - - 200 OK - Content-Tpe: application/json - TODO ...? - - { - since: ..., - timestamp: ..., - events: [ - { event }, - { event }, - ... - ] - } - - -The server can also return a prepared response (see -:ref:`prepared-response-api`). - -A successful response indicates a timeframe (between ``since`` and -``timestamp``) for which events have been retrieved. When using -*explicit* queries, the client MUST use the value of the last respone's -``timestamp`` field as the value of the ``since`` parameter in the following -request. The ``since`` paramter can be set to ``0`` if previously retrieved -events have been lost (eg through a database reset). This MUST, however, be an -exceptional case. - - -Data -^^^^ -The client can expect the retrieved events to be well-formed but SHOULD be able -to at least safely ignore invalid events. This includes events with an -``action`` which is not listed above. While the API does perform input -validation on uploaded events, this should ensure that clients are able to -remain operational if for example new event types are introduced which have -different requirements to the provided attributes. + :`_ - ``YYYY-MM-DDTHH:MM:SSZ`` +All endpoints send the `Access-Control-Allow-Origin: * +`_ header which `allows web application to access +the API `_. -Podcast is identified by its feed URL, episode is identified by its media URL. - -TODO: see http://developer.github.com/v3/ for relevant information! - -TODO: see `URI Templates `_ - - -Status Codes ------------- - -The API uses HTTP status codes to inform clients about type of response. The -semantics are used according to `their specified semantics -`_. - -The specification of each API endpoint describes which status codes should be -expected. In addition the following status codes can be returned for any API -request. - -+----------------------------+-----------------------------------------------+ -| Status Code | Interpretation | -+============================+===============================================+ -| 200 OK | All OK | -+----------------------------+-----------------------------------------------+ -| 301 Moved Permanently | The resource has moved permanently to the | -| | location provided in the Location header. | -| | Subsequent requests should use the new | -| | location directly. | -+----------------------------+-----------------------------------------------+ -| 303 See Other | the response to the request is found at the | -| | location provided in the Location header. It | -| | should be retrieved using a GET request | -+----------------------------+-----------------------------------------------+ -| 400 Bad Request | invalid JSON, invalid types | -+----------------------------+-----------------------------------------------+ -| 503 Service Unavailable | The service and/or API are under maintenance | -+----------------------------+-----------------------------------------------+ - -* Request not allowed (eg quota, authentication, permissions, etc) - - -Responses ---------- - -All responses are valid JSON (unless otherwise stated). - - -Error messages --------------- - -TODO: review `Problem Details for HTTP APIs -`_ - -An error response looks like :: - - { message: "message", errors: [...] } - -The ``errors`` array contains objects with the following information :: - - { - field: "", - code: "" - } - -The ``field`` value indicates where the error occured. - -* If the value starts with a ``/``, it should be interpreted as a `JSON Pointer - `_ to the problematic field in the - request body. - -* If the value starts with a ``?``, it is followed by the name of the parameter - that was responsible for the error. - -* The value can be null, indicating that the error was not caused by a specific - field. - -The ``code`` describes the actual error. The following error codes are defined: -* ``ìnvalid_url``: The provided values is not a valid URL. -* ``parameter_missing``: A mandatory parameter was not provided. -* ``duplicate_list_name``: A podcast list with the same name already exists. -* ``user_does_not_exist``: the specified user does not exist. -* ``podcastlist_does_not_exist``: the specified podcast list does not exist. +Identifying Podcasts and Episodes +--------------------------------- -Error codes may be added on demand. Clients should therefore expect and accept -arbitrary string values. - - -Redirects ---------- - -permanent (301) vs temporary (302, 307) redirects. - - -Authentication --------------- - -See Authentication API - - - -Rate Limiting -------------- - -See usage quotas :: - - GET /rate_limit - - HTTP/1.1 200 OK - Status: 200 OK - X-RateLimit-Limit: 60 - X-RateLimit-Remaining: 56 - -What counts as request? conditional requests? +Podcast is identified by its feed URL, episode is identified by its media URL. +Date Format +----------- -Conditional Requests --------------------- +Date format: ISO 8601 / `RFC 3339 `_: +``YYYY-MM-DDTHH:MM:SSZ`` -Some responses return ``Last-Modified`` and ``ETag`` headers. Clients SHOULD -use the values of these headers to make subsequent requests to those resources -using the ``If-Modified-Since`` and ``If-None-Match`` headers, respectively. If -the resource has not changed, the server will return a ``304 Not Modified``. -Making a conditional request and receiving a 304 response does not count -against the rate limit. +.. _formats: Formats ------- @@ -151,50 +41,123 @@ expected to contain JSON objects. JSONP Callbacks ^^^^^^^^^^^^^^^ -You can pass a ``?callback=`` parameter to any GET call to have +You can pass a ``json=`` parameter to any GET call to have the results wrapped in a JSON function. This is typically used when browsers want to embed content received from the API in web pages by getting around cross domain issues. The response includes the same data output as the regular API, plus the relevant HTTP Header information. -Resource Types --------------- +API Parametrization +------------------- + +Since 2.7 + +Clients should retrieve and process clientconfig.json (see :doc:`clientconfig`) +before making requests to the webservice. If a client can not process the +configuration, it can assume the default configuration given in the +clientconfig.json documentation. -.. _podcast-type: -Podcast -^^^^^^^ +.. _devices: -A podcast is represented as a JSON object containing at least an ``url`` -member. :: +Devices +------- - { - url: "http://example.com/podcast.rss", - title: "Cool Podcast", - logo: "http://example.com/podcast-logo.png" - } +Devices are used throughout the API to identify a device / a client +application. A device ID can be any string matching the regular expression +``[\w.-]+``. The client application MUST generate a string to be used as its +device ID, and SHOULD ensure that it is unique within the user account. A good +approach is to combine the application name and the name of the host it is +running on. +If two applications share a device ID, this might cause subscriptions to be +overwritten on the server side. While it is possible to retrieve a list of +devices and their IDs from the server, this SHOULD NOT be used to let a user +select an existing device ID. -.. _tag-type: -Tag +Formats +------- +Most of the resources are offered in several different formats + +* `OPML `_ +* JSON +* `JSONP `_ with an option function name + that wraps the result (since 2.8) +* plain text with one URL per line +* XML a custom XML format (see `example `_, + since 2.9) + + +JSON +^^^^ + +.. code-block:: json + + [ + { + "website": "http://sixgun.org", + "description": "The hardest-hitting Linux podcast around", + "title": "Linux Outlaws", + "url": "http://feeds.feedburner.com/linuxoutlaws", + "position_last_week": 1, + "subscribers_last_week": 1943, + "subscribers": 1954, + "mygpo_link": "http://gpodder.net/podcast/11092", + "logo_url": "http://sixgun.org/files/linuxoutlaws.jpg", + "scaled_logo_url": "http://gpodder.net/logo/64/fa9fd87a4f9e488096e52839450afe0b120684b4.jpg" + }, + ] + + +XML ^^^ -A tag is represented as a JSON object containing at least a ``label`` -member. :: +.. code-block:: xml + + + + Linux Outlaws + http://feeds.feedburner.com/linuxoutlaws + http://sixgun.org + http://gpodder.net/podcast/11092 + The hardest-hitting Linux podcast around + 1954 + 1943 + http://sixgun.org/files/linuxoutlaws.jpg + http://gpodder.net/logo/64/fa9fd87a4f9e488096e52839450afe0b120684b4.jpg + + + + +API Variants +------------ + +Simple API +^^^^^^^^^^ + +The Simple API provides a way to upload and download subscription lists in +bulk. This allows developers of podcast-related applications to quickly +integrate support for the web service, as the only - { - "label": "Technology" - } +* Synchronization of episode status fields is not supported +* This API uses more bandwith than the advanced API +* The client can be stateless +* The client can be low-powered - subscribe/unsubscribe events are calculated + on the server-side -Relations ---------- +Advanced API +^^^^^^^^^^^^ -`Relation types `_ that are -used in the API: +The Advanced API provides more flexibility and enhanced functionality for +applications that want a tighter integration with the web service. A reference +implementation will be provided as part of the gPodder source code (and gPodder +will make use of that reference implementation). -* ``https://api.gpodder.net/3/relation/tag-podcasts``: podcasts for a given tag +* The client has to persist the synchronization state locally +* Only changes to subscriptions are uploaded and downloaded +* Synchronization of episode status fields is supported in this API +* Only JSON is used as the data format to ease development -TODO: should they be on domain api.gpodder.net, or just gpodder.net? diff --git a/doc/api/reference/index.rst b/doc/api/reference/index.rst index 951b3b94c..8c9f12131 100644 --- a/doc/api/reference/index.rst +++ b/doc/api/reference/index.rst @@ -3,15 +3,9 @@ API Reference ============= -This is the reference documentation for the gpodder.net API 3. +This is the reference documentation for the gpodder.net API 2. -The API can be accessed via http and https. https is preferable from a security -/ privacy point of view and should be used by all clients. gpodder.net also -seems to be blocked in China via plain http. - -All endpoints are offered at https://api.gpodder.net/3/. - -The :ref:`integration-guide` contains additional non-normative information for +The :doc:`../integration` contains additional non-normative information for integrating gpodder.net into podcasting applications. @@ -21,13 +15,14 @@ integrating gpodder.net into podcasting applications. :maxdepth: 2 general - registration auth - user directory + suggestions devices subscriptions events podcastlists settings - prepare + favorites + sync + clientconfig diff --git a/doc/api/reference/podcastlists.rst b/doc/api/reference/podcastlists.rst index c4f18051b..34e940ee1 100644 --- a/doc/api/reference/podcastlists.rst +++ b/doc/api/reference/podcastlists.rst @@ -1,225 +1,104 @@ Podcast Lists API ================= -**TODO: this has just been copied from API 2 -- this needs review** +Podcast Lists are used to collect podcasts about one topic. On the website, +podcast lists are available at https://gpodder.net/lists/ -**Podcast Lists** are used to collect podcasts about some topics. On the -website, podcast lists are available at https://gpodder.net/lists/ -Resources ---------- +Create Podcast List +------------------- -The Podcast Lists API defines the following resources :: +.. http:post:: /api/2/lists/(username)/create.(format) + :synopsis: create a new podcast list - /user/{username}/lists/create - /user/{username}/lists - /user/{username}/list/{listname} + * requires authenticaton + * since 2.10 + :query title: url-encoded title + :param username: username for which a new podcast list should be created + :param format: see :ref:`formats` -Creating a Podcast List ------------------------ + The list content is sent in the request body, in the format indicates by + the format extension -A podcast list can be created by submitting a list of podcasts to the API. + The server then generates a short name for the list from the title given in + the Request. For example, from the title "My Python Podcasts" the name + "my-python-podcasts" would be generated. -Requires authentication. + :status 409: if the the user already has a podcast list with the + (generated) name + :status 303: the podcast list has been created at the URL given in the + :http:header:`Location` header -Parameters -^^^^^^^^^^ -* **title**: The title of the list (mandatory) +Get User's Lists +---------------- +.. http:get:: /api/2/lists/(username).json + :synopsis: get a user's podcast lists -Request -^^^^^^^ + * since 2.10 -Create a Podcast List :: + **Example response**: - POST /user/{username}/lists/create - Content-Tpe: application/json + .. sourcecode:: http - { - "title": "My Tech News", - "podcasts": [ - { "url": "http://example.com/feed.xml" }, - ... - ] - } - -The server then generates a short name for the list from the title given in the -request. For example, from the title "My Python Podcasts" the name -"my-python-podcasts" would be generated. - -Responses -^^^^^^^^^ - -If the podcast list has been created successfully, ``303 See Other`` is -returned. :: - - 303 See Other - Location: /3/user/yourusername/list/yourlistname - -The list can then be retrieved using a ``GET`` request. - -If the list could not be created, an error code is returned. If another list -with the same (generated) name exists, ``409 Conflict`` is returned. :: - - { - "message": "list already exists", - "errors": [ - { - field: "/title", - code: "duplicate_list_name" - } - ] - } - - -List the Podcast Lists of a User --------------------------------- + HTTP/1.1 200 OK - -Request -^^^^^^^ - -List User's Lists :: - - GET /user/{username}/lists - Content-Tpe: application/json - - -Response -^^^^^^^^ - -If the user exists, his/her podcast lists are returned. :: - - 200 OK - Content-Tpe: application/json - - { - "podcastlists": [ + [ { "title": "My Python Podcasts", "name": "my-python-podcasts", - "web": "http://gpodder.net/user/username/lists/my-python-podcasts" - } - ], - "username": "username" - } - -If the user does not exist, ``404 Not Found`` is returned. :: - - 404 Not Found - Content-Tpe: application/json - - { - "message": "user does not exist", - "errors": [ - { - field: "?username", - code: "user_does_not_exist" + "web": "http://gpodder.net/user/a-user/lists/my-python-podcasts" } ] - } + :status 200: the list of lists is returned + :status 404: the user was not found -Retrieve Podcast List ---------------------- - -Request -^^^^^^^ - -Retrieve a Podcast List :: - - GET /user/{username}/list/{listname} - Content-Tpe: application/json - - -Response -^^^^^^^^ - -The podcast list is returned. :: - - 200 OK - Content-Tpe: application/json - - { - "title": "My Tech News", - "name": "my-tech-news", - "podcasts": [ - { "url": "http://example.com/feed.xml" }, - ... - ], - "username": "username", - } - - -If either the user or the podcast list could not be found ``404 Not Found`` is -returned. :: - - 404 Not Found - Content-Tpe: application/json - - { - "message": "podcast list does not exist", - "errors": [ - { - field: "?listname", - code: "podcastlist_does_not_exist" - } - ] - } +Get a Podcast List +------------------ -Update ------- +.. http:get:: /api/2/lists/(username)/list/(listname).(format) + :synopsis: get a podcast list -Request -^^^^^^^ + * since 2.10 -Update a Podcast List:: + :param username: username to which the list belongs + :param listname: name of the requested podcast list + :param format: see :ref:`formats` + :status 200: the podcast list is returned in in the requested format + :status 404: if the user or the list do not exist - PUT /user/{username}/list/{listname} - Content-Tpe: application/json - - { - "title": "My Tech News", - "name": "my-tech-news2", - "podcasts": [ - { "url": "http://example.org/feed-mp3.xml" }, - ... - ] - } - -requires authentication +Update a Podcast List +--------------------- -Response -^^^^^^^^ +.. http:put:: /api/2/lists/(username)/list/(listname).(format) + :synopsis: update a podcast list -Possible Responses + * requires authentication + * since 2.10 -* 404 Not Found if there is no list with the given name -* 204 No Content If the podcast list has been created / updated + :param username: username to which the list belongs + :param listname: name of the requested podcast list + :param format: see :ref:`formats` + :status 404: if the user or the list do not exist + :status 204: if the podcast list has been created / updated Delete a Podcast List --------------------- -Request -^^^^^^^ - -Delete a Podcast List :: - - DELETE /user/{username}/list/{listname} - -requires authentication - - -Response -^^^^^^^^ - -If the update was successful, ``204 No Content`` is returned. :: +.. http:delete:: /api/2/lists/(username)/list/(listname).(format) + :synopsis: delete a podcast list - 204 No Content + * requires authentication + * since 2.10 -* 404 Not Found if there is no podcast list with the given name + :param username: username to which the list belongs + :param listname: name of the requested podcast list + :param format: see :ref:`formats` + :status 404: if the user or the list do not exist + :status 204: if the podcast list has been deleted diff --git a/doc/api/reference/prepare.rst b/doc/api/reference/prepare.rst deleted file mode 100644 index b505a5568..000000000 --- a/doc/api/reference/prepare.rst +++ /dev/null @@ -1,59 +0,0 @@ -.. _prepared-response-api: - -Prepared Response API -===================== - -This API is used by other parts of the API. It is not supposed to be initiated -by clients directly. - -Some requests might require the server to prepare a response. This process can -take longer than common request timeouts. - -In such cases, the server will provide a URL from which the response can be -retrieved. - - -Resources ---------- - -The Prepared Response API defines the following resources :: - - /response/ - - -Prepared Responses ------------------- - -The server can indicate a prepared response in the following way. :: - - 303 See Other - Link: /response/ - -Please note that any URL might be used in the ``Link`` header. - -The server is preparing the result at the specified resource. The client should -try to fetch the data from the given URLs. :: - - GET /response/ - Content-Tpe: application/json - - -A status code 404 is returned before the data is ready. The client may retry -after the given number of seconds. :: - - 404 Not Found - Retry-After: 120 - - -When the data is ready, 200 will be returned :: - - 200 OK - Content-Tpe: application/json - - body - -When the data is no longer available, a 410 is returned. :: - - 410 Gone - -In this case the client SHOULD retry the previous request. diff --git a/doc/api/reference/registration.rst b/doc/api/reference/registration.rst deleted file mode 100644 index f39d87811..000000000 --- a/doc/api/reference/registration.rst +++ /dev/null @@ -1,5 +0,0 @@ -Registration API -================ - -see `bug 1757 `_ for -requirements. diff --git a/doc/api/reference/settings.rst b/doc/api/reference/settings.rst index d3645d8f0..daec03c0c 100644 --- a/doc/api/reference/settings.rst +++ b/doc/api/reference/settings.rst @@ -1,184 +1,117 @@ Settings API ============ -Clients can use the gpodder.net API to store and exchange settings. Clients can -chose to simply store their settings on gpodder.net for online backup, or -exchange settings with other clients. Some settings also trigger behaviour on -the gpodder.net website. +Clients can store settings and retrieve settings as key-value-pairs, which are +attached to either account, device, podcast or episode. -Settings can be stored in several scopes. Each user has one *account* scope, -and one *device* scope per device. Additionally settings can be stored -per *podcast* and *episode*. +Keys are the names of the settings and are supposed to be strings. Values can +be any valid JSON objects. -Resources ---------- - -The Settings API defines the following resources :: - - /user/{username}/settings/account - /user/{username}/settings/device - /user/{username}/settings/podcast - /user/{username}/settings/episode - - -Well-Known Settings -------------------- +Known Settings +-------------- Although settings are primarily used to exchange settings between clients, some of them also trigger some behavior on the website. +Account +^^^^^^^ -Account scope -^^^^^^^^^^^^^ - -The following settings are well-known in the account scope. - -**public_profile** - when set to False, sets all podcasts to private (as on - http://gpodder.net/account/privacy, currently deactivated via API) - -**store_user_agent** - allow gpodder.net to store the User-Agent for each - device (default: true) - -**public_subscriptions** - default "public" value for subscriptions (default: true) - -**flattr_token** - auth-token for a Flattr login; empty when not logged in (default: empty) - -**auto_flattr** - auto-flattr episodes, only relevant when logged into Flattr account - (default: false) - -**flattr_mygpo** - automatically flattr gpodder.net, only relevant when logged into Flattr - account (default: false) - -**flattr_username** - username under which own items (eg podcast lists) are published - (default: empty) - - -Episode scope -^^^^^^^^^^^^^ - -The following settings are well-known in the episode scope. - -**is_favorite** - flags the episode as favorite (can be done on any episode-page) +* ``public_profile``: when set to False, sets all podcasts to private + (as on http://gpodder.net/account/privacy, currently deactivated via API) +* ``store_user_agent``: allow gpodder.net to store the User-Agent for each + device (default: true) +* ``public_subscriptions``: default "public" value for subscriptions (default: + true) +* ``flattr_token``: auth-token for a Flattr login; empty when not logged in + (default: empty) +* ``auto_flattr``: auto-flattr episodes, only relevant when logged into Flattr + account (default: false) +* ``flattr_mygpo``: automatically flattr gpodder.net, only relevant when logged + into Flattr account (default: false) +* ``flattr_username``: username under which own items (eg podcast lists) are + published (default: empty) +Episode +^^^^^^^ -Podcast scope -^^^^^^^^^^^^^ +* ``is_favorite``: flags the episode as favorite (can be done on any + episode-page) -The following settings are well-known in the podcast scope. +Podcast +^^^^^^^ -**public_subscription** - when set to False, sets the subscription to this podcast to private - (as on http://gpodder.net/account/privacy or any podcast-page, currently - deactivated via API) +* ``public_subscription``: when set to False, sets the subscription to this + podcast to private (as on http://gpodder.net/account/privacy or any + podcast-page, currently deactivated via API) -Saving Settings ---------------- +Save Settings +------------- -Save Settings :: +.. http:post:: /api/2/settings/(username)/(scope).json + :synopsis: Update or save some settings - POST /user/{username}/settings/{scope}{?scope_specification} - PATCH /user/{username}/settings/{scope}{?scope_specification} + * Requires Authentication + * Since 2.4 + **Example request**: -* Requires authentication + .. sourcecode:: http + { + "set": {"setting1": "value1", "setting2": "value2"}, + "remove": ["setting3", "setting4"] + } -Parameters -^^^^^^^^^^ + :param scope: one of account, device, podcast, episode + :query string podcast: Feed URL of a podcast (required for scope podcast + and episode) + :query device: Device id (see :ref:`devices`, required for scope device) + :query episode: media URL of the episode (required for scope episode) -**scope** - can be either ``account``, ``device``, ``podcast`` or ``episode`` + set is a dictionary of settings to add or update; remove is a list of keys + that shall be removed from the scope. -**podcast** - should contain the URL-encoded feed URL when ``scope`` is ``podcast`` or ``episode`` + **Example response**: -**episode** - should contain the URL-encoded media URL when ``scope`` is ``episode`` + The response contains all settings that the scope has after the update has + been carried out. -**device** - should contain the device Id when ``scope`` is ``device`` + .. sourcecode: http + HTTP/1.1 200 OK -Request Body -^^^^^^^^^^^^ - -The request body consists basically of a `JSON Patch (RFC 6902) -`_. However there are two possible -representations for a patch. - -When the PATCH method is used, the body corresponds to the JSON Patch. :: - - [ - { "op": "test", "path": "/a/b/c", "value": "foo" }, - { "op": "remove", "path": "/a/b/c" }, - { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }, - { "op": "replace", "path": "/a/b/c", "value": 42 }, - { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }, - { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" } - ] - - -When a POST request is used, a JSON object is included in the body, where the -actual patch is provided in the *patch* attribute. :: - - { - patch: [ - { "op": "test", "path": "/a/b/c", "value": "foo" }, - { "op": "remove", "path": "/a/b/c" }, - { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }, - { "op": "replace", "path": "/a/b/c", "value": 42 }, - { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }, - { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" } - ] - } - -Please refer to `RFC 6902 `_ for the -allowed operations and exact semantics of JSON Patch. Previously unused -settings default to the empty JSON object (``{}``). - - -Response -^^^^^^^^ - -Status Codes: - -* 200 OK -* 409 Conflict if a test operation failed - -A positive response contains all settings that the scope has after the update -has been carried out. :: - - { - "setting1": "value1", - "setting2": "value" - } - + { + "setting1": "value1", + "setting2": "value" + } Get Settings ------------ -Get Settings :: +.. http:get:: /api/2/settings/(username)/(scope).json + :synopsis: retrieve current settings + + * Requires Authentication + * Since 2.4 + + :param scope: one of account, device, podcast, episode + :query string podcast: Feed URL of a podcast (required for scope podcast + and episode) + :query device: Device id (see :ref:`devices`, required for scope device) + :query episode: media URL of the episode (required for scope episode) - GET /user/{username}/settings/{scope}{?scope_specification} -Scope and specification as above. -Requires Authentication + **Example response**: + The response contains all settings that the scope currently has -Response -^^^^^^^^ + .. sourcecode:: http -The response contains all settings that the scope currently has :: + { + "setting1": "value1", + "setting2": "value2" + } - {"setting1": "value1", "setting2": "value2"} diff --git a/doc/api/reference/subscriptions.rst b/doc/api/reference/subscriptions.rst index 9bcd0feaa..a0546863b 100644 --- a/doc/api/reference/subscriptions.rst +++ b/doc/api/reference/subscriptions.rst @@ -1,255 +1,189 @@ -.. _subscriptions-api: - Subscriptions API ================= -The subscriptions API is used to manage the podcast subscriptions of a client -device. - - -Resources ---------- - -The Subscriptions API defines the following resources :: - - /user/{username}/subscriptions - /user/{username}/device/{deviceid}/subscriptions - -The first resource represents the summary of subscriptions over all devices. It -can not be modified directly. - -The second resource represents the subscriptions of a single -:ref:`device-integration`. - - -Subscription Upload -------------------- - -Clients can upload the podcast subscriptions for a :ref:`device-integration` to -replace its existing subscriptions. - - -Request -^^^^^^^ - -The client sends an object containing all current subscriptions for the -device. :: - - PUT /user/{username}/device/{deviceid}/subscriptions - Content-Type: application/json - - { - podcasts: [ - { url: "http://example.com/podcast.rss" }, - { url: "http://podcast.com/episodes.xml" } - ] - } - - -Response -^^^^^^^^ - -The server can respond with the following status codes. - -If a ``Link`` header with ``rel=changes`` is provided, this URL can be used to -retrieve changes to the subscriptions since the respective response (see -:ref:`subscription-change-download`) - -If a new device has been created :: - - 201 Created - Link: ; rel=changes - - no body - - -If the subscriptions have been processed immediatelly :: - - 204 No Content - Link: ; rel=changes - - no body - +Get Subscriptions of Device +--------------------------- -If the subscriptions have been accepted for later processing :: +.. http:get:: /subscriptions/(username)/(deviceid).(format) + :synopsis: Get a list of subscribed podcasts for the given user. - 202 Accepted + * Requires HTTP authentication + * Since 1.0 - no body + **Example request**: -No change download address is provided in this case, as is is not yet known at -the time of the response. If the client needs to know the current -subscriptions, it should follow up by a request to download the subscriptions. + .. sourcecode:: http -If invalid podcast information is provided (eg an invalid feed URL), the whole -request will be rejected. :: + GET /subscriptions/bob/asdf.opml - 400 Bad Request - Content-Type: application/json + :param username: username for which subscriptions should be returned + :param deviceid: see :ref:`devices` + :param format: see :ref:`formats` + :query jsonp: function name for the JSONP format (since 2.8) - { - "message": "Invalid podcast URL", - "errors": [ - { - "field": "/podcasts/1", - "code": "invalid_url" - } - ] - } + :status 200: the subscriptions are returned in the requested format + :status 401: Invalid user + :status 404: Invalid device ID + :status 400: Invalid format -Subscription Download +Get All Subscriptions --------------------- -Clients can download the current subscriptions either of a single -:ref:`device-integration` or over all of the user's devices. - - -Request -^^^^^^^ - -Download subscriptions of a device :: - - GET /user/{username}/device/{deviceid}/subscriptions - Content-Type: application/json +.. http:get:: /subscriptions/(username).(format) + :synopsis: Get a list of all subscribed podcasts for the given user. + * Requires HTTP authentication + * Since 2.11 -Download all of the user's subscriptions :: + **Example request**: - GET /user/{username}/subscriptions - Content-Type: application/json + .. sourcecode:: http + GET /subscriptions/bob.opml -Response -^^^^^^^^ + This can be used to present the user a list of podcasts when the + application starts for the first time. -The podcasts correspond to the :ref:`podcast-type` type. :: + :param username: username for which subscriptions should be returned + :param deviceid: see :ref:`devices` + :param format: see :ref:`formats` + :query jsonp: function name for the JSONP format (since 2.8) - 200 OK - Link: ; rel=changes - Content-Type: application/json + :status 200: the subscriptions are returned in the requested format + :status 401: Invalid user + :status 400: Invalid format - { - podcasts: [ - { podcast1 }, - { podcast2 } - ] - } -The changes link is not provided if all subscriptions of a user are requested. +Upload Subscriptions of Device +------------------------------ +.. http:put:: /subscriptions/(username)/(deviceid).(format) + :synopsis: Upload subscriptions -Subscription Change Upload --------------------------- + * Requires HTTP authentication + * Since 1.0 -Clients can update the current subscriptions of a :ref:`device-integration` by -reporting subscribed and unsubscribed podcasts. + Upload the current subscription list of the given user to the server. The + data should be provided either in OPML, JSON or plaintext (one URL per + line) format, and should be uploaded just like a normal PUT request + (i.e. in the body of the request). + For successful updates, the implementation always returns the status code + 200 and the empty string (i.e. an empty HTTP body) as a result, any other + string should be interpreted by the client as an (undefined) error. -Request -^^^^^^^ + **Example request**: -A client can send which podcasts have been subscribed and unsubscribed. :: + .. sourcecode:: http - POST /user/{username}/device/{deviceid}/subscriptions - Content-Tpe: application/json + PUT /subscriptions/john/e9c4ea4ae004efac40.txt - { - subscribe: [ - { url: "http://example.com/podcast.rss" } - ] - unsubscribe: [ - { url: "http://podcast.com/episodes.xml" } - ] - } + :param username: username for which subscriptions should be uploaded + :param deviceid: see :ref:`devices` + :param format: see :ref:`formats` -A client MUST NOT upload a change set where both ``subscribe`` and -``unsubscribe`` are empty, or where the same podcast is given in both -``subscribe`` and ``unsubscribe``. + :status 200: the subscriptions have been updated + :status 401: Invalid user + :status 400: Invalid format + In case the device does not exist for the given user, it is automatically + created. If clients want to determine if a device exists, you have to to a + GET request on the same URL first and check for a the 404 status code (see + above). -Response -^^^^^^^^ -The server responds with either of the following status codes. - -The changes are processed immediatelly. :: - - 200 OK - Content-Tpe: application/json - - body according to Subscription Download - - -The changes have been accepted for later processing. :: - - 204 Accepted - - no body - -No response body is provided in this case, as it is not yet known. +Upload Subscription Changes +--------------------------- +.. http:post:: /api/2/subscriptions/(username)/(deviceid).json + :synopsis: Update the subscription list for a given device. -.. _subscription-change-download: + * Requires HTTP authentication + * Since 2.0 -Subscription Change Download ----------------------------- + Only deltas are supported here. Timestamps are not supported, and are + issued by the server. -Download changes to the subscriptions of a :ref:`device-integration`. + **Example request**: + .. sourcecode:: http -Request -^^^^^^^ + { + "add": ["http://example.com/feed.rss", "http://example.org/podcast.php"], + "remove": ["http://example.net/foo.xml"] + } -The client makes the following request. :: + :param username: username for which subscriptions should be returned + :param deviceid: see :ref:`devices` + :status 400: the same feed has been added and removed in the same request + :status 200: the subscriptions have been updated - GET /user/{username}/device/{deviceid}/subscriptions{?since} - Content-Tpe: application/json + In positive responses the server returns a timestamp/ID that can be used + for requesting changes since this upload in a subsequent API call. In + addition, the server sends a list of URLs that have been rewritten + (sanitized, see bug:747) as a list of tuples with the key "update_urls". + The client SHOULD parse this list and update the local subscription list + accordingly (the server only sanitizes the URL, so the semantic "content" + should stay the same and therefore the client can simply update the + URL value locally and use it for future updates. + **Example response**: -Response -^^^^^^^^ + .. sourcecode:: http -The server can response with any of the following status codes. + { + "timestamp": 1337, + "update_urls": + [ + [ + "http://feeds2.feedburner.com/LinuxOutlaws?format=xml", + "http://feeds.feedburner.com/LinuxOutlaws" + ], + [ + "http://example.org/podcast.rss ", + "http://example.org/podcast.rss" + ] + ] + } -The changes are returned immediatelly. :: + URLs that are not allowed (currently all URLs that don't start with either + http or https) are rewritten to the empty string and are ignored by + the Webservice. - 200 OK - Link: ; rel=changes - Content-Type: application/json - { - subscribe: [ - { url: "http://example.com/podcast.rss" } - ] - unsubscribe: [ - { url: "http://podcast.com/episodes.xml" } - ] - } +Get Subscription Changes +------------------------ -The server can also return a prepared response (see -:ref:`prepared-response-api`). +.. http:get:: /api/2/subscriptions/(username)/(deviceid).json + :synopsis: retrieve subscription changes + * Requires HTTP authentication + * Since 2.0 -Integration Recommendations ---------------------------- + This API call retrieves the subscription changes since the timestamp + provided in the since parameter. Its value SHOULD be timestamp value from + the previous call to this API endpoint. If there has been no previous call, + the cliend SHOULD use 0. -This section describes how the API can be accessed for common use cases. + The response format is the same as the upload format: A dictionary with two + keys "add" and "remove" where the value for each key is a list of URLs that + should be added or removed. The timestamp SHOULD be stored by the client in + order to provide it in the since parameter in the next request. -* On first startup a client CAN retrieve the list of all the user's - subscriptions to offer as suggestions. + **Example response**: -* On first startup a client SHOULD generate a unique :ref:`device-integration` - Id for managing its own subscriptions in subsequent API calls. + In case nothing has changed, the server returns something like the + following JSON content. -* A client which has been somehow "reset" can re-use an existing device ID and - restore its subscriptions from there. It SHOULD NOT share the same device ID - with another installation which is still used. + .. sourcecode:: http -* A client SHOULD either use the combination of Subscription Upload and - Download endpoints, or the Subscription Change endpoints to keep its - subscriptions up to date. + { + "add": [], + "remove": [], + "timestamp": 12347 + } -* When retrieving subscriptions or subscription changes, a client SHOULD use - the URL in the ``Link`` header with ``rel=changes`` (if present) to retrieve - subsequent changes to the resource. + :param username: username for which subscriptions should be returned + :param deviceid: see :ref:`devices` + :query since: the ``timestamp`` value of the last response diff --git a/doc/api/reference/suggestions.rst b/doc/api/reference/suggestions.rst new file mode 100644 index 000000000..8765c08aa --- /dev/null +++ b/doc/api/reference/suggestions.rst @@ -0,0 +1,62 @@ +Suggestions API +=============== + +Retrieve Suggested Podcasts +--------------------------- + +.. http:get:: /suggestions/(int:number).(format) + :synopsis: retrieve suggested podcasts + + * Requires HTTP authentication + * Since 1.0 + + **Example request**: + + .. sourcecode:: http + + GET /suggestions/10.opml + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + [ + { + "website": "http://www.linuxgeekdom.com", + "mygpo_link": "http://gpodder.net/podcast/64439", + "description": "Linux Geekdom", + "subscribers": 0, + "title": "Linux Geekdom", + "url": "http://www.linuxgeekdom.com/rssmp3.xml", + "subscribers_last_week": 0, + "logo_url": null + }, + { + "website": "http://goinglinux.com", + "mygpo_link": "http://gpodder.net/podcast/11171", + "description": "Going Linux", + "subscribers": 571, + "title": "Going Linux", + "url": "http://goinglinux.com/mp3podcast.xml", + "subscribers_last_week": 571, + "logo_url": "http://goinglinux.com/images/GoingLinux80.png" + }] + + :param number: the maximum number of podcasts to return + :param format: see :ref:`formats` + :query jsonp: function name for the JSONP format (since 2.8) + + Download a list of podcasts that the user has not yet subscribed to (by + checking all server-side subscription lists) and that might be + interesting to the user based on existing subscriptions (again on all + server-side subscription lists). + + The TXT format is a simple URL list (one URL per line), and the OPML file + is a "standard" OPML feed. The JSON format looks as follows: + + The server does not specify the "relevance" for the podcast suggestion, and + the client application SHOULD filter out any podcasts that are already + added to the client application but that the server does not know about yet + (although this is just a suggestion for a good client-side UX). diff --git a/doc/api/reference/sync.rst b/doc/api/reference/sync.rst new file mode 100644 index 000000000..9e9abc4fb --- /dev/null +++ b/doc/api/reference/sync.rst @@ -0,0 +1,66 @@ +Device Synchronization API +========================== + +Get Sync Status +--------------- + +.. http:get:: /api/2/sync-devices/(username).json + :synopsis: get the sync status of a user + + * requires authentication + * since 2.10 + + **Example response**: + + .. sourcecode:: http + + { + "synchronized": [ + ["notebook", "n900"], + ["pc-home", "pc-work"], + ], + "not-synchronized": [ + "test-pc", "netbook" + ] + } + + :param username: username for which the sync status is requested + + +Start / Stop Sync +----------------- + +.. http:post:: /api/2/sync-devices/(username).json + :synopsis: update the sync status of a user's devices + + * requires authentication + * since 2.10 + + **Example request**: + + .. sourcecode:: http + + { + "synchronize": [ + ["notebook", "netbook"] + ], + "stop-synchronize": ["pc-work"] + } + + Sets up / stops synchronization between devices. The synchronization status + is sent as a response + + **Example status**: + + .. sourcecode:: http + + { + "synchronized": [ + ["notebook", "netbook", "n900"] + ], + "not-synchronized": [ + "test-pc", "pc-work", "pc-home" + ] + } + + :param username: username for which the sync status is requested diff --git a/doc/api/reference/user.rst b/doc/api/reference/user.rst deleted file mode 100644 index 0a637dbec..000000000 --- a/doc/api/reference/user.rst +++ /dev/null @@ -1,45 +0,0 @@ -User API -======== - -The User API can be used to retrieve public information about a user, and to -discover user-related resources. - -When initiating a session, a client SHOULD query the user information, to -discover URIs for further queries. - - -Resources ---------- - -The User API defines the following resources :: - - /user/{username} - - -Get User Info -------------- - -Request :: - - GET /user/{username} - Content-Tpe: application/json - - -Response:: - - 200 Found - Content-Tpe: application/json - - { - "username": "stefan", - "avatar": "http://....", - "twitter": "@skoegl", - "description": "hi...", - "flattr_username": "stefankoegl", - - "resources": { - "subscriptions": "http://api.gpodder.net/3/user/{username}/subscriptions", - ... - } - } - diff --git a/doc/api/usage.rst b/doc/api/usage.rst deleted file mode 100644 index d97ed5dfb..000000000 --- a/doc/api/usage.rst +++ /dev/null @@ -1,81 +0,0 @@ -.. _usage: - -API Usage -========= - -This page describes how the gpodder.net API can be used. - - -Client Registration -------------------- - -Most API endpoints can only be accessed by registered clients using a client -key. Clients can be registered for free at LINK. A registration needs to be -approved before it can be used. - -TODO: User-Agent? - -TODO: how is the API Key included in requests? - - -Allowed Usage -------------- - -**Open source clients** can issue an unlimited number of requests to the API. - -**Closed source Clients** (this includes free-of-charge closed source clients) -have a quota of requests per day (UTC). The quota depends on the features -they implement (and activate/enable by default). - -If there are open and closed sourced versions of a client, they need to have -two API keys. - -The following table shows how the client quota is increased by implementing a -certain feature. - -+------------------------+---------------+----------------+ -| Features | Open source | Closed source | -+========================+===============+================+ -| Base Quota | unlimited | 1000 | -+------------------------+---------------+----------------+ -| Podcast Search | unlimited | not counted | -+------------------------+---------------+----------------+ -| Podcast Toplist | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Top Tags | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Tag-Podcasts | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Subscriptions Download | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Subscriptions Upload | unlimited | +5000 | -+------------------------+---------------+----------------+ -| Device List and Config | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Episode Favorites | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Authentication | unlimited | not counted | -+------------------------+---------------+----------------+ -| Device Sync | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Events Upload | unlimited | +10000 | -+------------------------+---------------+----------------+ -| Events Download | unlimited | +10000 | -+------------------------+---------------+----------------+ -| Podcast Lists | unlimited | +1000 | -+------------------------+---------------+----------------+ -| Settings | unlimited | +1000 | -+------------------------+---------------+----------------+ - - -Misbehaving Clients -------------------- - -Violations of rules that are given as *MUST* or *MUST NOT* are recorded. Any -client application may accumulate 100 of such violations per day (to accomodate -for bug fixing and debugging). Client applications that regularly -exceed this limit will be blocked. The status (and reports) of a client -application's violations can be viewed online. TODO: where? - - -TODO: distinguish between (individual) clients and client applications. diff --git a/doc/conf.py b/doc/conf.py index ce4df6d66..ad7a9ef77 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -25,7 +25,8 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +sys.path.insert(0, os.path.abspath('_ext')) +extensions = ['jsonlexer', 'sphinxcontrib.httpdomain'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -91,7 +92,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/index.rst b/doc/index.rst index 2bc6fa7e1..3b750f539 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -33,4 +33,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/requirements-doc.txt b/requirements-doc.txt new file mode 100644 index 000000000..4df3b687d --- /dev/null +++ b/requirements-doc.txt @@ -0,0 +1 @@ +sphinxcontrib-httpdomain