From 3a5d346b9cbb07cf9b0da8e6174db5bdce47d578 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Sun, 24 Sep 2017 23:32:21 -0400 Subject: [PATCH] Preliminary TTRSS handler --- lib/REST.php | 6 +- lib/REST/TinyTinyRSS/API.php | 128 +++++++++++++++++++++++++++++ lib/REST/TinyTinyRSS/Exception.php | 17 ++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 lib/REST/TinyTinyRSS/API.php create mode 100644 lib/REST/TinyTinyRSS/Exception.php diff --git a/lib/REST.php b/lib/REST.php index 68f5e0b..28b9985 100644 --- a/lib/REST.php +++ b/lib/REST.php @@ -16,11 +16,15 @@ class REST { 'strip' => '/index.php/apps/news/api/v1-2', 'class' => REST\NextCloudNews\V1_2::class, ], + 'ttrss_api' => [ // Tiny Tiny RSS https://git.tt-rss.org/git/tt-rss/wiki/ApiReference + 'match' => '/tt-rss/api/', + 'strip' => '/tt-rss/api/', + 'class' => REST\TinyTinyRSS\API::class, + ], // Other candidates: // NextCloud News v2 https://github.com/nextcloud/news/blob/master/docs/externalapi/External-Api.md // Feedbin v1 https://github.com/feedbin/feedbin-api/commit/86da10aac5f1a57531a6e17b08744e5f9e7db8a9 // Feedbin v2 https://github.com/feedbin/feedbin-api - // Tiny Tiny RSS https://tt-rss.org/gitlab/fox/tt-rss/wikis/ApiReference // Fever https://feedafever.com/api // NewsBlur http://www.newsblur.com/api // Miniflux https://github.com/miniflux/miniflux/blob/master/docs/json-rpc-api.markdown diff --git a/lib/REST/TinyTinyRSS/API.php b/lib/REST/TinyTinyRSS/API.php new file mode 100644 index 0000000..a6cf363 --- /dev/null +++ b/lib/REST/TinyTinyRSS/API.php @@ -0,0 +1,128 @@ + ["login"], + ]; + + public function __construct() { + } + + public function dispatch(\JKingWeb\Arsse\REST\Request $req): Response { + if ($req->method != "POST") { + // only POST requests are allowed + return new Response(405, "", "", ["Allow: POST"]); + } + if ($req->body) { + // only JSON entities are allowed + if (!preg_match("<^application/json\b|^$>", $req->type)) { + return new Response(415, "", "", ['Accept: application/json']); + } + $data = @json_decode($req->body, true); + if (json_last_error() != \JSON_ERROR_NONE || !is_array($data)) { + // non-JSON input indicates an error + return new Response(400); + } + // layer input over defaults + $data = array_merge([ + 'seq' => 0, + 'op' => "", + 'sid' => null, + ], $data); + try { + if (!in_array($data['op'], self::OVERRIDE['auth'])) { + // unless otherwise specified, a session identifier is required + $this->resumeSession($data['sid']); + } + $method = "op".ucfirst($data['op']); + if (!method_exists($this, $method)) { + // because method names are supposed to be case insensitive, we need to try a bit harder to match + $method = strtolower($method); + $map = get_class_methods($this); + $map = array_combine(array_map("strtolower", $map), $map); + if(!array_key_exists($method, $map)) { + // if the method really doesn't exist, throw an exception + throw new Exception("UNKNWON_METHOD", ['method' => $data['op']]); + } + // otherwise retrieve the correct camelCase and continue + $method = $map[$method]; + } + return new Response(200, [ + 'seq' => $data['seq'], + 'status' => 0, + 'content' => $this->$method($data), + ]); + } catch (Exception $e) { + return new Response(200, [ + 'seq' => $data['seq'], + 'status' => 1, + 'content' => $e->getData(), + ]); + } catch (AbstractException $e) { + return new Response(500); + } + } else { + // absence of a request body indicates an error + return new Response(400); + } + } + + protected function resumeSession($id): bool { + try { + // verify the supplied session is valid + $s = Arsse::$db->sessionResume((string) $id); + } catch (\JKingWeb\Arsse\User\ExceptionSession $e) { + // if not throw an exception + throw new Exception("NOT_LOGGED_IN"); + } + // resume the session (currently only the user name) + Arsse::$user->id = $s['user']; + return true; + } + + public function opGetApiLevel(array $data): aray { + return ['level' => self::LEVEL]; + } + + public function opGetVersion(array $data): array { + return [ + 'version' => self::VERSION, + 'arsse_version' => \JKingWeb\Arsse\VERSION, + ]; + } + + public function opLogin(array $data): aray { + if (isset($data['user']) && isset($data['password']) && Arsse::$user->auth($data['user'], $data['password'])) { + $id = Arsse::$db->sessionCreate($data['user']); + return [ + 'session_id' => $id, + 'api_level' => self::LEVEL + ]; + } else { + throw new Exception("LOGIN_ERROR"); + } + } + + public function opLogout(array $data): array { + Arsse::$db->sessionDestroy(Arsse::$user->id, $data['sid']); + return ['status' => "OK"]; + } + + public function opIsLoggedIn(array $data): array { + // session validity is already checked by the dispatcher, so we need only return true + return ['status' => true]; + } +} \ No newline at end of file diff --git a/lib/REST/TinyTinyRSS/Exception.php b/lib/REST/TinyTinyRSS/Exception.php new file mode 100644 index 0000000..6674fda --- /dev/null +++ b/lib/REST/TinyTinyRSS/Exception.php @@ -0,0 +1,17 @@ +data = $data; + parent::__construct($msg, 0, $e); + } + + public function getData(): array { + $err = ['error' => $this->getMesssage()]; + return array_merge($err, $this->data, $err); + } +} \ No newline at end of file