PmWiki recipe that provides an easy-to-use interface to the Pinboard API.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

pm-pinboard-api.php 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php if (!defined('PmWiki')) exit();
  2. /* pm-pinboard-api.php
  3. * Pm Pinboard API (a PmWiki recipe for Pinboard (https://pinboard.in) integration)
  4. *
  5. * See http://www.pmwiki.org/wiki/Cookbook/PmPinboardAPI
  6. * for documentation and latest version.
  7. *
  8. * This program is free software; you can redistribute it
  9. * and/or modify it under the terms of the GNU General
  10. * Public License as published by the Free Software
  11. * Foundation; either version 3 of the License, or (at your
  12. * option) any later version. Available at
  13. * https://www.gnu.org/licenses/gpl.txt
  14. *
  15. * Copyright 2018 Said Achmiz.
  16. */
  17. $RecipeInfo['PmPinboardAPI']['Version'] = '2018-01-09';
  18. /*************/
  19. /* VARIABLES */
  20. /*************/
  21. SDV($PinboardAPIEndpoint, 'https://api.pinboard.in/v1/');
  22. SDV($PinboardAPIToken, 'YOUR_API_TOKEN_GOES_HERE');
  23. SDVA($PinboardAPIAllowedMethods, [
  24. 'posts/update',
  25. // 'posts/add',
  26. // 'posts/delete',
  27. 'posts/get',
  28. 'posts/recent',
  29. 'posts/dates',
  30. 'posts/all',
  31. 'posts/suggest',
  32. 'tags/get',
  33. // 'tags/delete',
  34. // 'tags/rename',
  35. 'user/secret',
  36. 'notes/list',
  37. 'notes/ID'
  38. ]);
  39. SDVA($PinboardAPIMethodCooldowns, [
  40. 'global' => 3,
  41. 'posts/recent' => 60,
  42. 'posts/all' => 300
  43. ]);
  44. SDV($PinboardAPICacheFolder, 'pub/cache/pinboard/');
  45. SDV($PinboardAPIResponseCacheDuration, 0);
  46. $PinboardAPIResponseCacheDuration = ($PinboardAPIResponseCacheDuration > 0) ?
  47. max($PinboardAPIResponseCacheDuration, max($PinboardAPIMethodCooldowns)) :
  48. 0;
  49. /*********/
  50. /* SETUP */
  51. /*********/
  52. if (!file_exists($PinboardAPICacheFolder))
  53. mkdir($PinboardAPICacheFolder);
  54. $response_cache_file = $PinboardAPICacheFolder."response_cache.json";
  55. $response_cache = PinboardAPIGetResponseCache();
  56. $request_log_file = $PinboardAPICacheFolder."request_log.json";
  57. $request_log = PinboardAPIGetRequestLog();
  58. /*************/
  59. /* FUNCTIONS */
  60. /*************/
  61. function PinboardAPIRequest($method, $params) {
  62. global $PinboardAPIToken, $PinboardAPIEndpoint, $PinboardAPIAllowedMethods,
  63. $PinboardAPIResponseCacheDuration, $PinboardAPIMethodCooldowns;
  64. global $request_log, $response_cache;
  65. ## Check if specified method is allowed. If not, return an error.
  66. if (!(in_array($method, $PinboardAPIAllowedMethods) ||
  67. (in_array('notes/ID', $PinboardAPIAllowedMethods) && preg_match("^notes\/", $method))))
  68. return [
  69. 'error-text' => "The method “$method” is not permitted.",
  70. 'error-html' => "<p style='color: red; font-weight: bold;'>The method “<code>$method</code>” is not permitted.</p>\n"
  71. ];
  72. ## Build the request.
  73. $request_URL = $PinboardAPIEndpoint . $method;
  74. $request_params = [
  75. 'format' => 'json',
  76. 'auth_token' => $PinboardAPIToken
  77. ];
  78. $request_params = array_merge($request_params, $params);
  79. $request_URL .= "?" . http_build_query($request_params);
  80. ## This is for logging/caching.
  81. $request_URL_hash = md5($request_URL);
  82. $cur_time = time();
  83. ## If...
  84. ## a) a response cache duration is specified, and...
  85. ## b) there is a cached response for this request, and...
  86. ## c) the cached response isn't too old...
  87. ## ... then return the cached response.
  88. if ($PinboardAPIResponseCacheDuration > 0 &&
  89. isset($response_cache[$request_URL_hash]) &&
  90. ($cur_time - $response_cache[$request_URL_hash]['request_time']) <= $PinboardAPIResponseCacheDuration)
  91. {
  92. return $response_cache[$request_URL_hash];
  93. }
  94. ## Check elapsed time since last request (or last request of this type, in the case of
  95. ## methods that have their own rate limits (i.e. posts/recent and posts/all)).
  96. ## If the new request comes too soon after the last one, return an error message.
  97. $cooldown_category = $PinboardAPIMethodCooldowns[$method] ? $method : 'global';
  98. $cooldown = $PinboardAPIMethodCooldowns[$cooldown_category];
  99. $elapsed = $cur_time - $request_log["last-{$cooldown_category}"];
  100. ## Alternatively, if the last request got an HTTP status code 429 (Too Many Requests),
  101. ## then make sure a good long while has elapsed since the last request of any kind;
  102. ## if it hasn't, then return an error.
  103. ## (What's a "good long while"? Well, "twice as long as the longest cooldown" seems
  104. ## like a reasonable value. (The longest cooldown should be the 5-minute cooldown for
  105. ## posts/all, so the cooldown after a 429 ought to be 10 minutes (600 seconds).))
  106. if (isset($request_log['last_request_hash']) &&
  107. $response_cache[$request_log['last_request_hash']]['http_code'] == 429) {
  108. $cooldown = 2 * max($PinboardAPIMethodCooldowns);
  109. $elapsed = $cur_time - $request_log["last-global"];
  110. }
  111. ## In either case, if we're still within the relevant cooldown, return an error.
  112. if ($elapsed < $cooldown)
  113. return [
  114. 'error-text' => "Too many requests. Wait a bit, then try again.",
  115. 'error-html' => "<p style='color: red; font-weight: bold;'>Too many requests. Wait a bit, then try again.</p>\n"
  116. ];
  117. ## Send the request.
  118. $curl = curl_init($request_URL);
  119. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  120. $curl_response = curl_exec($curl);
  121. ## Handle the response.
  122. $info = curl_getinfo($curl);
  123. $response = json_decode($curl_response, true);
  124. $response['http_code'] = $info['http_code'];
  125. $response['request_time'] = $cur_time;
  126. curl_close($curl);
  127. ## Cache the response.
  128. PinboardAPIUpdateResponseCache($request_URL_hash, $response);
  129. ## Update the last-request info.
  130. $request_log['last-global'] = $cur_time;
  131. if ($method == 'posts/recent') $request_log['last-posts/recent'] = $cur_time;
  132. else if ($method == 'posts/all') $request_log['last-posts/all'] = $cur_time;
  133. $request_log['last_request_hash'] = $request_URL_hash;
  134. PinboardAPISaveRequestLog($request_log);
  135. return $response;
  136. }
  137. /***************/
  138. /* REQUEST LOG */
  139. /***************/
  140. function PinboardAPIResetRequestLog() {
  141. global $request_log_file;
  142. file_put_contents($request_log_file, json_encode([
  143. 'last-global' => 0,
  144. 'last-posts/recent' => 0,
  145. 'last-posts/all' => 0
  146. ]));
  147. }
  148. function PinboardAPIGetRequestLog() {
  149. global $request_log_file;
  150. if (!file_exists($request_log_file))
  151. PinboardAPIResetRequestLog();
  152. $request_log = json_decode(file_get_contents($request_log_file), true);
  153. return $request_log;
  154. }
  155. function PinboardAPISaveRequestLog($request_log) {
  156. global $request_log_file;
  157. file_put_contents($request_log_file, json_encode($request_log));
  158. }
  159. /******************/
  160. /* RESPONSE CACHE */
  161. /******************/
  162. function PinboardAPIClearResponseCache() {
  163. global $response_cache_file;
  164. file_put_contents($response_cache_file, json_encode([ ]));
  165. }
  166. function PinboardAPIGetResponseCache() {
  167. global $response_cache_file;
  168. if (!file_exists($response_cache_file)) {
  169. PinboardAPIClearResponseCache();
  170. return [ ];
  171. } else {
  172. return json_decode(file_get_contents($response_cache_file), true);
  173. }
  174. }
  175. function PinboardAPIUpdateResponseCache($request_URL_hash, $response) {
  176. global $response_cache_file, $PinboardAPIResponseCacheDuration;
  177. $response_cache = ($PinboardAPIResponseCacheDuration > 0) ? PinboardAPIGetResponseCache() : [ ];
  178. $response_cache[$request_URL_hash] = $response;
  179. file_put_contents($response_cache_file, json_encode($response_cache));
  180. }