1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 13:12:41 +00:00

Prototype OPML handling

This commit is contained in:
J. King 2021-02-05 20:29:41 -05:00
parent 681654f249
commit b4ae988b79
4 changed files with 56 additions and 5 deletions

View file

@ -42,6 +42,7 @@ Miniflux version 2.0.28 is emulated, though not all features are implemented
- Creating a feed with the `scrape` property set to `true` might not return scraped content for the initial synchronization - Creating a feed with the `scrape` property set to `true` might not return scraped content for the initial synchronization
- Querying articles for both read/unread and removed statuses will not return all removed articles - Querying articles for both read/unread and removed statuses will not return all removed articles
- Search strings will match partial words - Search strings will match partial words
- OPML import either succeeds or fails atomically: if one feed fails, no feeds are imported
# Behaviour of filtering (block and keep) rules # Behaviour of filtering (block and keep) rules

View file

@ -104,7 +104,12 @@ abstract class AbstractException extends \Exception {
"Rule/Exception.invalidPattern" => 10701, "Rule/Exception.invalidPattern" => 10701,
]; ];
protected $symbol;
protected $params;
public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) { public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) {
$this->symbol = $msgID;
$this->params = $vars ?? [];
if ($msgID === "") { if ($msgID === "") {
$msg = "Exception.unknown"; $msg = "Exception.unknown";
$code = 10000; $code = 10000;
@ -121,4 +126,12 @@ abstract class AbstractException extends \Exception {
} }
parent::__construct($msg, $code, $e); parent::__construct($msg, $code, $e);
} }
public function getSymbol(): string {
return $this->symbol;
}
public function getParams(): array {
return $this->aparams;
}
} }

View file

@ -13,7 +13,8 @@ use JKingWeb\Arsse\Feed\Exception as FeedException;
use JKingWeb\Arsse\AbstractException; use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\Context\Context; use JKingWeb\Arsse\Context\Context;
use JKingWeb\Arsse\Db\ExceptionInput; use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Misc\HTTP; use JKingWeb\Arsse\ImportExport\OPML;
use JKingWeb\Arsse\ImportExport\Exception as ImportException;
use JKingWeb\Arsse\Misc\Date; use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\URL; use JKingWeb\Arsse\Misc\URL;
use JKingWeb\Arsse\Misc\ValueInfo as V; use JKingWeb\Arsse\Misc\ValueInfo as V;
@ -25,6 +26,7 @@ use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Laminas\Diactoros\Response\EmptyResponse; use Laminas\Diactoros\Response\EmptyResponse;
use Laminas\Diactoros\Response\JsonResponse as Response; use Laminas\Diactoros\Response\JsonResponse as Response;
use Laminas\Diactoros\Response\TextResponse as GenericResponse;
use Laminas\Diactoros\Uri; use Laminas\Diactoros\Uri;
class V1 extends \JKingWeb\Arsse\REST\AbstractHandler { class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
@ -141,8 +143,8 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
'GET' => ["getCategoryFeeds", false, true, false, false, []], 'GET' => ["getCategoryFeeds", false, true, false, false, []],
], ],
'/categories/1/mark-all-as-read' => [ '/categories/1/mark-all-as-read' => [
'PUT' => ["markCategory", false, true, false, false, []],
], ],
'PUT' => ["markCategory", false, true, false, false, []],
'/discover' => [ '/discover' => [
'POST' => ["discoverSubscriptions", false, false, true, false, ["url"]], 'POST' => ["discoverSubscriptions", false, false, true, false, ["url"]],
], ],
@ -212,6 +214,11 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
public function __construct() { public function __construct() {
} }
/** @codeCoverageIgnore */
protected function getInstance(string $class) {
return new $class;
}
protected function authenticate(ServerRequestInterface $req): bool { protected function authenticate(ServerRequestInterface $req): bool {
// first check any tokens; this is what Miniflux does // first check any tokens; this is what Miniflux does
if ($req->hasHeader("X-Auth-Token")) { if ($req->hasHeader("X-Auth-Token")) {
@ -261,9 +268,6 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
} }
if ($reqBody) { if ($reqBody) {
if ($func === "opmlImport") { if ($func === "opmlImport") {
if (!HTTP::matchType($req, "", ...[self::ACCEPTED_TYPES_OPML])) {
return new ErrorResponse("", 415, ['Accept' => implode(", ", self::ACCEPTED_TYPES_OPML)]);
}
$args[] = (string) $req->getBody(); $args[] = (string) $req->getBody();
} else { } else {
$data = (string) $req->getBody(); $data = (string) $req->getBody();
@ -1177,6 +1181,32 @@ class V1 extends \JKingWeb\Arsse\REST\AbstractHandler {
return new EmptyResponse(204); return new EmptyResponse(204);
} }
protected function opmlImport(string $data): ResponseInterface {
try {
$this->getInstance(OPML::class)->import(Arsse::$user->id, $data);
} catch (ImportException $e) {
switch ($e->getCode()) {
case 10611:
return new ErrorResponse("InvalidBodyXML", 400);
case 10612:
return new ErrorResponse("InvalidBodyOPML", 422);
case 10613:
return new ErrorResponse("InvalidImportCategory", 422);
case 10614:
return new ErrorResponse("DuplicateImportCatgory", 422);
case 10615:
return new ErrorResponse("InvalidImportLabel", 422);
}
} catch (FeedException $e) {
return new ErrorResponse(["FailedImportFeed", 'url' => $e->getParams()['url'], 'code' => $e->getCode()], 502);
}
return new Response(['message' => Arsse::$lang->msg("ImportSuccess")]);
}
protected function opmlExport(): ResponseInterface {
return new GenericResponse($this->getInstance(OPML::class)->export(Arsse::$user->id), 200, ['Content-Type' => "application/xml"]);
}
public static function tokenGenerate(string $user, string $label): string { public static function tokenGenerate(string $user, string $label): string {
// Miniflux produces tokenss in base64url alphabet // Miniflux produces tokenss in base64url alphabet
$t = str_replace(["+", "/"], ["-", "_"], base64_encode(random_bytes(self::TOKEN_LENGTH))); $t = str_replace(["+", "/"], ["-", "_"], base64_encode(random_bytes(self::TOKEN_LENGTH)));

View file

@ -8,12 +8,15 @@ return [
'CLI.Auth.Failure' => 'Authentication failed', 'CLI.Auth.Failure' => 'Authentication failed',
'API.Miniflux.DefaultCategoryName' => "All", 'API.Miniflux.DefaultCategoryName' => "All",
'API.Miniflux.ImportSuccess' => 'Feeds imported successfully',
'API.Miniflux.Error.401' => 'Access Unauthorized', 'API.Miniflux.Error.401' => 'Access Unauthorized',
'API.Miniflux.Error.403' => 'Access Forbidden', 'API.Miniflux.Error.403' => 'Access Forbidden',
'API.Miniflux.Error.404' => 'Resource Not Found', 'API.Miniflux.Error.404' => 'Resource Not Found',
'API.Miniflux.Error.MissingInputValue' => 'Required key "{field}" was not present in input', 'API.Miniflux.Error.MissingInputValue' => 'Required key "{field}" was not present in input',
'API.Miniflux.Error.DuplicateInputValue' => 'Key "{field}" accepts only one value', 'API.Miniflux.Error.DuplicateInputValue' => 'Key "{field}" accepts only one value',
'API.Miniflux.Error.InvalidBodyJSON' => 'Invalid JSON payload: {0}', 'API.Miniflux.Error.InvalidBodyJSON' => 'Invalid JSON payload: {0}',
'API.Miniflux.Error.InvalidBodyXML' => 'Invalid XML payload',
'API.Miniflux.Error.InvalidBodyOPML' => 'Payload is not a valid OPML document',
'API.Miniflux.Error.InvalidInputType' => 'Input key "{field}" of type {actual} was expected as {expected}', 'API.Miniflux.Error.InvalidInputType' => 'Input key "{field}" of type {actual} was expected as {expected}',
'API.Miniflux.Error.InvalidInputValue' => 'Supplied value is not valid for input key "{field}"', 'API.Miniflux.Error.InvalidInputValue' => 'Supplied value is not valid for input key "{field}"',
'API.Miniflux.Error.Fetch404' => 'Resource not found (404), this feed doesn\'t exists anymore, check the feed URL', 'API.Miniflux.Error.Fetch404' => 'Resource not found (404), this feed doesn\'t exists anymore, check the feed URL',
@ -28,6 +31,10 @@ return [
'API.Miniflux.Error.DuplicateUser' => 'The user name "{user}" already exists', 'API.Miniflux.Error.DuplicateUser' => 'The user name "{user}" already exists',
'API.Miniflux.Error.DuplicateFeed' => 'This feed already exists.', 'API.Miniflux.Error.DuplicateFeed' => 'This feed already exists.',
'API.Miniflux.Error.InvalidTitle' => 'Invalid feed title', 'API.Miniflux.Error.InvalidTitle' => 'Invalid feed title',
'API.Miniflux.Error.InvalidImportCategory' => 'Payload contains an invalid category name',
'API.Miniflux.Error.DuplicateImportCategory' => 'Payload contains the same category name twice',
'API.Miniflux.Error.FailedImportFeed' => 'Unable to import feed at URL "{url}" (code {code}',
'API.Miniflux.Error.InvalidImportLabel' => 'Payload contains an invalid label name',
'API.TTRSS.Category.Uncategorized' => 'Uncategorized', 'API.TTRSS.Category.Uncategorized' => 'Uncategorized',
'API.TTRSS.Category.Special' => 'Special', 'API.TTRSS.Category.Special' => 'Special',