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:
parent
681654f249
commit
b4ae988b79
4 changed files with 56 additions and 5 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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',
|
||||||
|
|
Loading…
Reference in a new issue