| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- <?php if (!defined('PmWiki')) exit();
- /** \pm-pinboard-api.php
- * \Copyright 2018-2021 Said Achmiz
- * \Licensed under the MIT License
- * \brief Provides an easy-to-use PmWiki-compatible interface to the Pinboard API.
- */
- $RecipeInfo['PmPinboardAPI']['Version'] = '2021-12-12';
-
- /*************/
- /* VARIABLES */
- /*************/
-
- SDV($PinboardAPIEndpoint, 'https://api.pinboard.in/v1/');
- SDV($PinboardAPIToken, 'YOUR_API_TOKEN_GOES_HERE');
- SDVA($PinboardAPIAllowedMethods, [
- 'posts/update',
- // 'posts/add',
- // 'posts/delete',
- 'posts/get',
- 'posts/recent',
- 'posts/dates',
- 'posts/all',
- 'posts/suggest',
-
- 'tags/get',
- // 'tags/delete',
- // 'tags/rename',
-
- 'user/secret',
-
- 'notes/list',
- 'notes/ID'
- ]);
- SDVA($PinboardAPIMethodCooldowns, [
- 'global' => 3,
- 'posts/recent' => 60,
- 'posts/all' => 300
- ]);
- SDV($PinboardAPICacheFolder, 'pub/cache/pinboard/');
- SDV($PinboardAPIResponseCacheDuration, 0);
- $PinboardAPIResponseCacheDuration = ($PinboardAPIResponseCacheDuration > 0) ?
- max($PinboardAPIResponseCacheDuration, max($PinboardAPIMethodCooldowns)) :
- 0;
-
- /*********/
- /* SETUP */
- /*********/
-
- if (!file_exists($PinboardAPICacheFolder))
- mkdir($PinboardAPICacheFolder);
-
- $response_cache_file = $PinboardAPICacheFolder."response_cache.json";
- $response_cache = PinboardAPIGetResponseCache();
-
- $request_log_file = $PinboardAPICacheFolder."request_log.json";
- $request_log = PinboardAPIGetRequestLog();
-
- /*************/
- /* FUNCTIONS */
- /*************/
-
- function PinboardAPIRequest($method, $params) {
- global $PinboardAPIToken, $PinboardAPIEndpoint, $PinboardAPIAllowedMethods,
- $PinboardAPIResponseCacheDuration, $PinboardAPIMethodCooldowns;
- global $request_log, $response_cache;
-
- ## Check if specified method is allowed. If not, return an error.
- if (!(in_array($method, $PinboardAPIAllowedMethods) ||
- (in_array('notes/ID', $PinboardAPIAllowedMethods) && preg_match("^notes\/", $method))))
- return [
- 'error-text' => "The method “$method” is not permitted.",
- 'error-html' => "<p style='color: red; font-weight: bold;'>The method “<code>$method</code>” is not permitted.</p>\n"
- ];
-
- ## Build the request.
- $request_URL = $PinboardAPIEndpoint . $method;
- $request_params = [
- 'format' => 'json',
- 'auth_token' => $PinboardAPIToken
- ];
- $request_params = array_merge($request_params, $params);
- $request_URL .= "?" . http_build_query($request_params);
-
- ## This is for logging/caching.
- $request_URL_hash = md5($request_URL);
- $cur_time = time();
-
- ## If...
- ## a) a response cache duration is specified, and...
- ## b) there is a cached response for this request, and...
- ## c) the cached response isn't too old...
- ## ... then return the cached response.
- if ($PinboardAPIResponseCacheDuration > 0 &&
- isset($response_cache[$request_URL_hash]) &&
- ($cur_time - $response_cache[$request_URL_hash]['request_time']) <= $PinboardAPIResponseCacheDuration)
- {
- return $response_cache[$request_URL_hash];
- }
-
- ## Check elapsed time since last request (or last request of this type, in the case of
- ## methods that have their own rate limits (i.e. posts/recent and posts/all)).
- ## If the new request comes too soon after the last one, return an error message.
- $cooldown_category = $PinboardAPIMethodCooldowns[$method] ? $method : 'global';
- $cooldown = $PinboardAPIMethodCooldowns[$cooldown_category];
- $elapsed = $cur_time - $request_log["last-{$cooldown_category}"];
- ## Alternatively, if the last request got an HTTP status code 429 (Too Many Requests),
- ## then make sure a good long while has elapsed since the last request of any kind;
- ## if it hasn't, then return an error.
- ## (What's a "good long while"? Well, "twice as long as the longest cooldown" seems
- ## like a reasonable value. (The longest cooldown should be the 5-minute cooldown for
- ## posts/all, so the cooldown after a 429 ought to be 10 minutes (600 seconds).))
- if (isset($request_log['last_request_hash']) &&
- $response_cache[$request_log['last_request_hash']]['http_code'] == 429) {
- $cooldown = 2 * max($PinboardAPIMethodCooldowns);
- $elapsed = $cur_time - $request_log["last-global"];
- }
- ## In either case, if we're still within the relevant cooldown, return an error.
- if ($elapsed < $cooldown)
- return [
- 'error-text' => "Too many requests. Wait a bit, then try again.",
- 'error-html' => "<p style='color: red; font-weight: bold;'>Too many requests. Wait a bit, then try again.</p>\n"
- ];
-
- ## Send the request.
- $curl = curl_init($request_URL);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- $curl_response = curl_exec($curl);
-
- ## Handle the response.
- $info = curl_getinfo($curl);
- $response = json_decode($curl_response, true);
- $response['http_code'] = $info['http_code'];
- $response['request_time'] = $cur_time;
- curl_close($curl);
-
- ## Cache the response.
- PinboardAPIUpdateResponseCache($request_URL_hash, $response);
-
- ## Update the last-request info.
- $request_log['last-global'] = $cur_time;
- if ($method == 'posts/recent') $request_log['last-posts/recent'] = $cur_time;
- else if ($method == 'posts/all') $request_log['last-posts/all'] = $cur_time;
- $request_log['last_request_hash'] = $request_URL_hash;
- PinboardAPISaveRequestLog($request_log);
-
- return $response;
- }
-
- /***************/
- /* REQUEST LOG */
- /***************/
-
- function PinboardAPIResetRequestLog() {
- global $request_log_file;
- file_put_contents($request_log_file, json_encode([
- 'last-global' => 0,
- 'last-posts/recent' => 0,
- 'last-posts/all' => 0
- ]));
- }
-
- function PinboardAPIGetRequestLog() {
- global $request_log_file;
- if (!file_exists($request_log_file))
- PinboardAPIResetRequestLog();
-
- $request_log = json_decode(file_get_contents($request_log_file), true);
- return $request_log;
- }
-
- function PinboardAPISaveRequestLog($request_log) {
- global $request_log_file;
- file_put_contents($request_log_file, json_encode($request_log));
- }
-
- /******************/
- /* RESPONSE CACHE */
- /******************/
-
- function PinboardAPIClearResponseCache() {
- global $response_cache_file;
- file_put_contents($response_cache_file, json_encode([ ]));
- }
-
- function PinboardAPIGetResponseCache() {
- global $response_cache_file;
- if (!file_exists($response_cache_file)) {
- PinboardAPIClearResponseCache();
- return [ ];
- } else {
- return json_decode(file_get_contents($response_cache_file), true);
- }
- }
-
- function PinboardAPIUpdateResponseCache($request_URL_hash, $response) {
- global $response_cache_file, $PinboardAPIResponseCacheDuration;
-
- $response_cache = ($PinboardAPIResponseCacheDuration > 0) ? PinboardAPIGetResponseCache() : [ ];
- $response_cache[$request_URL_hash] = $response;
- file_put_contents($response_cache_file, json_encode($response_cache));
- }
|