214 lines
6.9 KiB
PHP
214 lines
6.9 KiB
PHP
<?php if (!defined('PmWiki')) exit();
|
|
|
|
/* pm-pinboard-api.php
|
|
* Pm Pinboard API (a PmWiki recipe for Pinboard (https://pinboard.in) integration)
|
|
*
|
|
* See http://www.pmwiki.org/wiki/Cookbook/PmPinboardAPI
|
|
* for documentation and latest version.
|
|
*
|
|
* This program is free software; you can redistribute it
|
|
* and/or modify it under the terms of the GNU General
|
|
* Public License as published by the Free Software
|
|
* Foundation; either version 3 of the License, or (at your
|
|
* option) any later version. Available at
|
|
* https://www.gnu.org/licenses/gpl.txt
|
|
*
|
|
* Copyright 2018 Said Achmiz.
|
|
*/
|
|
|
|
$RecipeInfo['PmPinboardAPI']['Version'] = '2018-01-09';
|
|
|
|
/*************/
|
|
/* 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));
|
|
}
|