PmWiki recipe that provides an easy-to-use interface to the Pinboard API.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

pm-pinboard-api.php 6.5KB

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