"""Pydantic models for Nextcloud News app responses."""
from typing import List
from pydantic import BaseModel, ConfigDict, Field
from .base import BaseResponse
class NewsFolder(BaseModel):
"""Model for a News folder."""
model_config = ConfigDict(populate_by_name=True)
id: int = Field(description="Folder ID")
name: str = Field(description="Folder name")
class NewsFeed(BaseModel):
"""Model for a News feed (RSS/Atom subscription)."""
model_config = ConfigDict(populate_by_name=True)
id: int = Field(description="Feed ID")
url: str = Field(description="Feed URL")
title: str = Field(description="Feed title")
favicon_link: str | None = Field(
None, alias="faviconLink", description="Favicon URL"
)
link: str | None = Field(None, description="Website link")
added: int = Field(description="Unix timestamp when feed was added")
folder_id: int | None = Field(
None, alias="folderId", description="Parent folder ID"
)
unread_count: int = Field(
0, alias="unreadCount", description="Number of unread items"
)
ordering: int = Field(
0, description="Feed ordering (0=default, 1=oldest, 2=newest)"
)
pinned: bool = Field(False, description="Whether feed is pinned to top")
update_error_count: int = Field(
0, alias="updateErrorCount", description="Consecutive update failures"
)
last_update_error: str | None = Field(
None, alias="lastUpdateError", description="Last update error message"
)
@property
def has_errors(self) -> bool:
"""Check if feed has update errors."""
return self.update_error_count > 0
class NewsItem(BaseModel):
"""Model for a News item (article) with full content."""
model_config = ConfigDict(populate_by_name=True)
id: int = Field(description="Item ID")
guid: str = Field(description="Globally unique identifier")
guid_hash: str = Field(alias="guidHash", description="MD5 hash of GUID")
url: str | None = Field(None, description="Article URL")
title: str = Field(description="Article title")
author: str | None = Field(None, description="Article author")
pub_date: int | None = Field(
None, alias="pubDate", description="Publication timestamp"
)
body: str | None = Field(None, description="Article content (HTML)")
enclosure_mime: str | None = Field(
None, alias="enclosureMime", description="Enclosure MIME type"
)
enclosure_link: str | None = Field(
None, alias="enclosureLink", description="Enclosure URL"
)
media_thumbnail: str | None = Field(
None, alias="mediaThumbnail", description="Media thumbnail URL"
)
media_description: str | None = Field(
None, alias="mediaDescription", description="Media description"
)
feed_id: int = Field(alias="feedId", description="Parent feed ID")
unread: bool = Field(True, description="Whether item is unread")
starred: bool = Field(False, description="Whether item is starred")
rtl: bool = Field(False, description="Right-to-left text")
last_modified: int = Field(
alias="lastModified", description="Last modification timestamp"
)
fingerprint: str | None = Field(
None, description="Content fingerprint for deduplication"
)
content_hash: str | None = Field(
None, alias="contentHash", description="Content hash"
)
class NewsItemSummary(BaseModel):
"""Lightweight model for News item list responses."""
model_config = ConfigDict(populate_by_name=True)
id: int = Field(description="Item ID")
title: str = Field(description="Article title")
feed_id: int = Field(alias="feedId", description="Parent feed ID")
unread: bool = Field(True, description="Whether item is unread")
starred: bool = Field(False, description="Whether item is starred")
pub_date: int | None = Field(
None, alias="pubDate", description="Publication timestamp"
)
url: str | None = Field(None, description="Article URL")
author: str | None = Field(None, description="Article author")
class NewsStatus(BaseModel):
"""Model for News app status."""
version: str = Field(description="News app version")
warnings: dict = Field(default_factory=dict, description="Configuration warnings")
# --- Response Models ---
class ListFoldersResponse(BaseResponse):
"""Response model for listing folders."""
results: List[NewsFolder] = Field(description="List of folders")
total_count: int = Field(description="Total number of folders")
class ListFeedsResponse(BaseResponse):
"""Response model for listing feeds."""
results: List[NewsFeed] = Field(description="List of feeds")
starred_count: int = Field(0, description="Number of starred items")
newest_item_id: int | None = Field(None, description="ID of newest item")
total_count: int = Field(description="Total number of feeds")
class ListItemsResponse(BaseResponse):
"""Response model for listing items."""
results: List[NewsItemSummary] = Field(description="List of items")
total_count: int = Field(description="Number of items returned")
has_more: bool = Field(False, description="Whether more items exist")
oldest_id: int | None = Field(None, description="Oldest item ID (for pagination)")
class GetItemResponse(BaseResponse):
"""Response model for getting a single item."""
item: NewsItem = Field(description="Full item details")
class FeedHealthResponse(BaseResponse):
"""Response model for feed health status."""
feed_id: int = Field(description="Feed ID")
title: str = Field(description="Feed title")
url: str = Field(description="Feed URL")
has_errors: bool = Field(description="Whether feed has update errors")
error_count: int = Field(description="Number of consecutive errors")
last_error: str | None = Field(None, description="Last error message")
class GetStatusResponse(BaseResponse):
"""Response model for app status."""
version: str = Field(description="News app version")
warnings: dict = Field(default_factory=dict, description="Configuration warnings")