@ -16,6 +17,7 @@ use JKingWeb\Arsse\Misc\ValueInfo;
use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\AbstractException;
use JKingWeb\Arsse\ExceptionType;
use JKingWeb\Arsse\ExceptionType;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ResultEmpty;
use JKingWeb\Arsse\Feed\Exception as FeedException;
use JKingWeb\Arsse\Feed\Exception as FeedException;
use JKingWeb\Arsse\REST\Response;
use JKingWeb\Arsse\REST\Response;
@ -32,10 +34,13 @@ Protocol difference so far:
- The "Published" virtual feed is non-functional (this will not be implemented in the near term)
- The "Published" virtual feed is non-functional (this will not be implemented in the near term)
- setArticleLabel responds with errors for invalid labels where TT-RSS simply returns a zero result
- setArticleLabel responds with errors for invalid labels where TT-RSS simply returns a zero result
- The result of setArticleLabel counts only records which actually changed rather than all entries attempted
- The result of setArticleLabel counts only records which actually changed rather than all entries attempted
- Using both limit/skip and unread_only in getFeeds produces reliable results, unlike in TT-RSS
- Top-level categories in getFeedTree have a 'parent_id' property (set to null); in TT-RSS the property is absent
- Top-level categories in getFeedTree have a 'parent_id' property (set to null); in TT-RSS the property is absent
- Article hashes are SHA-256 rather than SHA-1.
- Article hashes are SHA-256 rather than SHA-1.
- Articles have at most one attachment (enclosure), whereas TTRSS allows for several; there is also significantly less detail. These are limitations of picoFeed which should be addressed
- Articles have at most one attachment (enclosure), whereas TTRSS allows for several; there is also significantly less detail. These are limitations of picoFeed which should be addressed
- IDs for enclosures are ommitted as we don't give them IDs
- IDs for enclosures are ommitted as we don't give them IDs
- Searching in getHeadlines is not yet implemented
- Category -3 (all non-special feeds) is handled correctly in getHeadlines; TT-RSS returns results for feed -3 (Fresh)
*/
*/
@ -59,35 +64,34 @@ class API extends \JKingWeb\Arsse\REST\AbstractHandler {
const CAT_ALL = -4;
const CAT_ALL = -4;
// valid input
// valid input
const VALID_INPUT = [
const VALID_INPUT = [
'op' => ValueInfo::T_STRING,
'op' => ValueInfo::T_STRING, // the function ("operation") to perform
'sid' => ValueInfo::T_STRING,
'sid' => ValueInfo::T_STRING, // session ID
'seq' => ValueInfo::T_INT,
'seq' => ValueInfo::T_INT, // request number from client
'include_nested' => ValueInfo::T_BOOL | ValueInfo::M_DROP, // whether to include subcategories in `getFeeds` and the articles thereof in `getHeadlines`
'parent_id' => ValueInfo::T_INT,
'caption' => ValueInfo::T_STRING | ValueInfo::M_STRICT, // name for categories, feed, and labels
'category_id' => ValueInfo::T_INT,
'parent_id' => ValueInfo::T_INT, // parent category for `addCategory` and `moveCategory`
$c->markedSince(Date::sub("PT24H"))->unread(false); // FIXME: this selects any recently touched article which is read, not necessarily a recently read one
break;
default:
// any actual feed
$c->subscription($id);
break;
}
}
}
// next handle the view mode
switch ($viewMode) {
case "all_articles":
// no context needed here
break;
case "adaptive":
// adaptive means "return only unread unless there are none, in which case return all articles"
if ($c->unread !== false && Arsse::$db->articleCount(Arsse::$user->id, (clone $c)->unread(true))) {
$c->unread(true);
}
break;
case "unread":
if ($c->unread !== false) {
$c->unread(true);
} else {
// unread mode in the "Recently Read" feed is a no-op
return new ResultEmpty;
}
break;
case "marked":
$c->starred(true);
break;
case "has_note":
$c->annotated(true);
break;
case "published":
// not implemented
// TODO: if the Published feed is implemented, the headline function needs to be modified accordingly
return new ResultEmpty;
default:
throw new \JKingWeb\Arsse\Exception("constantUnknown", $viewMode); // @codeCoverageIgnore
Phake::when(Arsse::$db)->articleCount($this->anything(), (new Context)->unread(true))->thenReturn(1);
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->subscription(2112), Database::LIST_MINIMAL)->thenThrow(new ExceptionInput("subjectMissing"));
Phake::when(Arsse::$db)->articleList($this->anything(), new Context, Database::LIST_MINIMAL)->thenReturn(new Result($this->articles));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 1]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->label(1088), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 2]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->unread(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 3]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->label(1088)->unread(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 4]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->subscription(42)->starred(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 5]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->subscription(42)->annotated(true), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 6]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->limit(5), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 7]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->offset(2), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 8]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->limit(5)->offset(2), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 9]]));
Phake::when(Arsse::$db)->articleList($this->anything(), (new Context)->oldestArticle(48), Database::LIST_MINIMAL)->thenReturn(new Result([['id' => 10]]));
$out1 = [
$this->respErr("INCORRECT_USAGE"),
$this->respGood([]),
$this->respGood([]),
$this->respGood([]),
$this->respGood([]),
$this->respGood([]),
$this->respGood([]),
$this->respGood([['id' => 101],['id' => 102]]),
$this->respGood([['id' => 1]]),
$this->respGood([['id' => 2]]),
$this->respGood([['id' => 3]]),
$this->respGood([['id' => 2]]), // the result is 2 rather than 4 because there are no unread, so the unread context is not used