PmWiki recipe that provides an easy-to-use interface to the Pinboard API.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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. }