Source code for aioqbt.api.rss

import json
from typing import TYPE_CHECKING, Dict, List, Mapping, Optional, Union

from aioqbt._paramdict import ParamDict
from aioqbt.api.types import RSSArticle, RSSFeed, RSSFolder, RSSRule
from aioqbt.client import APIGroup

if TYPE_CHECKING:
    from aioqbt.mapper import ObjectMapper

__all__ = ("RSSAPI",)

_KEY_UID = "uid"
_KEY_URL = "url"
_KEY_ARTICLES = "articles"


def _process_item(
    mapper: "ObjectMapper",
    context: Mapping[str, object],
    data: Dict[str, object],
) -> Union[RSSFeed, RSSFolder]:
    # As we recursively walk through the data
    # tree structure is created

    uid = data.get(_KEY_UID)
    url = data.get(_KEY_URL)

    if isinstance(uid, str) and isinstance(url, str):
        feed = mapper.create_object(RSSFeed, data, context)

        articles = data.get(_KEY_ARTICLES)
        if isinstance(articles, list):
            feed.articles = mapper.create_list(RSSArticle, articles, context)
        return feed
    else:
        items = {}

        for key, value in data.items():
            assert isinstance(value, dict)
            items[key] = _process_item(mapper, context, value)

        return RSSFolder(
            _items=items,
        )


[docs] class RSSAPI(APIGroup): """ RSS APIs .. note:: RSS API is experimental. Methods and results may change without notice. """
[docs] async def add_folder(self, path: str) -> None: """ Add a new folder. Raise :exc:`~.exc.ConflictError` if error. """ data = ParamDict() data.required_str("path", path) await self._request_text( "POST", "rss/addFolder", data=data, )
[docs] async def add_feed(self, url: str, path: str) -> None: """ Add a new feed. Raise :exc:`~.exc.ConflictError` if error. """ data = ParamDict() data.required_str("url", url) data.required_str("path", path) await self._request_text( "POST", "rss/addFeed", data=data, )
[docs] async def remove_item(self, path: str) -> None: """ Add a feed/folder. Raise :exc:`~.exc.ConflictError` if error. """ data = ParamDict() data.required_str("path", path) await self._request_text( "POST", "rss/removeItem", data=data, )
[docs] async def move_item(self, item_path: str, dest_path: str) -> None: """ Move a feed/folder. Raise :exc:`~.exc.ConflictError` if error. """ data = ParamDict() data.required_str("itemPath", item_path) data.required_str("destPath", dest_path) await self._request_text( "POST", "rss/moveItem", data=data, )
[docs] async def items(self, with_data: Optional[bool] = None) -> RSSFolder: """ Get the root folder, which consists of all feeds and sub-folders. If ``with_data=True``, feed title and a list of articles will also be available. See :class:`~.RSSFeed`. """ params = ParamDict() params.optional_bool("withData", with_data) client = self._client() result = await client.request_json( "GET", "rss/items", params=params, ) mapper = client._mapper context = client._context root = _process_item(mapper, context, result) assert isinstance(root, RSSFolder) return root
[docs] async def mark_as_read(self, item_path: str, article_id: Optional[str] = None) -> None: """ Mark an article as read. """ # API 2.5.1 data = ParamDict() data.required_str("itemPath", item_path) data.optional_str("articleId", article_id) await self._request_text( "POST", "rss/markAsRead", data=data, )
[docs] async def refresh_item(self, item_path: str) -> None: """ Refresh a folder/feed. """ # API 2.2.1 data = ParamDict() data.required_str("itemPath", item_path) await self._request_text( "POST", "rss/refreshItem", data=data, )
[docs] async def set_rule( self, rule_name: str, rule_def: Union[str, RSSRule, Mapping[str, object]], ) -> None: """ Add/update a rule. """ if not isinstance(rule_def, str): rule_def = json.dumps(dict(rule_def), separators=(",", ":")) data = ParamDict() data.required_str("ruleName", rule_name) data.required_str("ruleDef", rule_def) await self._request_text( "POST", "rss/setRule", data=data, )
[docs] async def rename_rule(self, rule_name: str, new_rule_name: str) -> None: """ Rename a rule. .. note:: Before API 2.6.1, there was a bug that renaming rule would not change the result of :meth:`~.RSSAPI.rules`. """ data = ParamDict() data.required_str("ruleName", rule_name) data.required_str("newRuleName", new_rule_name) await self._request_text( "POST", "rss/renameRule", data=data, )
[docs] async def remove_rule(self, rule_name: str) -> None: """ Remove a rule. """ data = ParamDict() data.required_str("ruleName", rule_name) await self._request_text( "POST", "rss/removeRule", data=data, )
[docs] async def rules(self) -> Dict[str, RSSRule]: """ Get all rules. """ return await self._request_json( # type: ignore[no-any-return] "GET", "rss/rules", )
[docs] async def matching_articles(self, rule_name: str) -> Dict[str, List[str]]: """ Get articles matched by a rule. The result is a dict mapping feed names to lists of article titles. """ # API 2.5.1 params = ParamDict() params.required_str("ruleName", rule_name) return await self._request_json( # type: ignore[no-any-return] "GET", "rss/matchingArticles", params=params, )