Skip to content

πŸ“° .NET client for the Inoreader HTTP API

License

Notifications You must be signed in to change notification settings

Aldaviva/InoreaderCs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

πŸ“° InoreaderCs

NuGet package GitHub Workflow Status Testspace Coveralls

.NET client for the Inoreader HTTP API

Prerequisites

Installation

dotnet add package InoreaderCs

Features

Like IsaacSchemm/InoreaderFs, but not fucked up:

  • Correctly send the ot query parameter to stream/items/ids in microsecond format, not seconds, so it isn't ignored.
  • Has a built-in OAuth2 client with smart refresh logic as well as a password-based auth client, both of which make auth requests automatically and support pluggable persistence strategies.
  • Observe rate-limiting statistics.
  • Uses modern, interchangeable, customizable HttpClient instead of ancient, disgusting HttpWebRequest.
  • Allows you to set custom HTTP request headers, such as User-Agent.
  • Easily gets article's read and starred state, short ID, description, and original feed name and URL.
  • Instances are configurable because they are not static classes, so you don't need to supply authentication to literally every request, you can just set it up once, for example in an IoC context, and not have to pass it around your entire codebase.
  • Interfaces allow mocking and interchangeability, instead of everything being sealed static classes.
  • Hierarchical interface structure makes it easier to find the API method you want and understand what it applies to.
  • Facade pattern hides the complexity of the Inoreader API's very overloaded methods with lots of conditionally valid parameters.
  • Avoids the insane concept of stream IDs and all their complexity of parsing, translating, handling, and using them, because developers today shouldn't have to deal with weird Google decisions from 2001 that Inoreader bent over backwards to be compatible with for no real benefit because there is no Google Reader client that anyone pointed at Inoreader as a drop-in replacement backend.
  • Full documentation of methods and entities.
  • Exceptions have information about what went wrong.
  • Updated in the last 6 years by someone who uses Inoreader and this library heavily every day.
  • Automated tests.
  • Includes correct, strongly-typed arguments in API methods like subscription/edit, which are incorrect in the official documentation and unehlpful, weakly-typed strings in InoreaderFs.

Configuration

Authentication

Persisting auth tokens

Implement the IAuthTokenPersister interface so that auth tokens can be saved and loaded.

public class MyAuthTokenPersister: IAuthTokenPersister {

    public async Task<PersistedAuthTokens?> LoadAuthTokens() {
        // load auth tokens
    }

    public async Task SaveAuthTokens(PersistedAuthTokens authToken) {
        // save auth tokens
    }

}

Authentication client

If your app authenticates to Inoreader using OAuth2, subclass the OAuth2Client abstract class so that users can see and grant consent for OAuth2 app access to their account.

public class MyOauth2Client(Oauth2Parameters oauthParameters, IAuthTokenPersister authTokenPersister, IHttpClient? httpClient, ILoggerFactory? loggerFactory)
    : Oauth2Client(oauthParameters, authTokenPersister, httpClient, loggerFactory) {

    protected override Uri AuthorizationReceiverCallbackUrl => /* URL of your web server's OAuth2 callback resource */;

    protected override async Task<ConsentResult> ShowConsentPageToUser(Uri consentUri, Uri codeReceiverUri, Task authorizationSuccess) {
        // show consent page to user
    }

}

Otherwise, construct a PasswordAuthClient instance.

API Client

Create a new instance of InoreaderClient. It uses one instance of an IAuthClient, so if you have multiple auth strategies, construct additional InoreaderClient instances.

Oauth2Parameters oauthParameters = new(appId: 123, appKey: "abc");

using IInoreaderClient inoreader = new InoreaderClient(new InoreaderOptions {
    AuthClient = new MyOauth2Client(oauthParameters, new MyAuthTokenPersister())
});

Usage

List articles

DetailedArticles articleList = await inoreader.Newsfeed.ListArticlesDetailed();
foreach (Article article in articleList.Articles) {
    Console.WriteLine(article.Title);
}

Useful options:

  • To only fetch articles from a specific folder, tag, or subscription/feed, replace Newsfeed (which refers to all articles in the entire account) with Folders, Tags, or Subscriptions, respectively.
  • To change the page size, pass the maxArticles parameter.
  • To only return articles crawled after a certain time, pass the minTime parameter.
  • To remove or require articles to have a specific state like read or starred, pass the subtract or intersect parameters, respectively.
  • To fetch subsequent pages from a multi-page response, pass the previous page or its PaginationToken as the pagination parameter.

Caution

Even when the pagination parameter is sent properly, the Inoreader API servers sometimes ignore it and incorrectly return the first page again instead of the desired page. This makes it look like many of the articles are duplicates. After fetching multiple pages of articles, always filter by the unique Article.ShortId to remove the erroneous duplicates, for example, using IEnumerable<T>.Distinct.

Check if there are new articles

DateTimeOffset mostRecentArticleTime = default;
while (true) {
    BriefArticles mostRecentArticle = await inoreader.Newsfeed.ListArticlesBrief(maxArticles: 1, minTime: mostRecentArticleTime);

    if (mostRecentArticle.Articles[0].CrawlTime is var articleTime && articleTime != mostRecentArticleTime) {
        mostRecentArticleTime = articleTime;
        Console.WriteLine("New article received");
    }
    await Task.Delay(TimeSpan.FromMinutes(10));
}

Mark an article read or unread

await inoreader.Articles.MarkArticles(ArticleState.Read, [article]);
await inoreader.Articles.UnmarkArticles(ArticleState.Read, [article]);

Star or unstar an article

await inoreader.Articles.MarkArticles(ArticleState.Starred, [article]);
await inoreader.Articles.UnmarkArticles(ArticleState.Starred, [article]);

Tag or untag an article

await inoreader.Articles.TagArticles("my tag", [article]);
await inoreader.Articles.UntagArticles("my tag", [article]);

Subscribe to a feed

Uri feedUrl = new("https://arstechnica.com/science/feed/");
SubscriptionCreationResult subscription = await inoreader.Subscriptions.Subscribe(feedUrl, CancellationToken.None);

// alternative that immediately sets name or folder
await inoreader.Subscriptions.Subscribe(feed, title: "Science", folder: "Technology");

Show the number of unread articles

NewsfeedUnreadCounts unreadResponse = await inoreader.Newsfeed.GetUnreadCounts();
int unreadCount = unreadResponse.AllArticles.UnreadCount;
string unreadLabel = $"{unreadCount:N0}{(unreadCount == unreadResponse.MaxDisplayableCount ? "+" : "")}";

List subscriptions, folders, or tags

IEnumerable<Subscription> subscriptions = await inoreader.Subscriptions.List();
IEnumerable<FolderState> folders = await inoreader.Folders.List();
IEnumerable<TagState> tags = await inoreader.Tags.List();

Rename subscriptions, folders, or tags

Uri subscription = new("https://arstechnica.com/science/feed/");
await inoreader.Subscriptions.Rename(subscription, newName: "Science!");
await inoreader.Folders.Rename("Technology", newName: "New Technology");
await inoreader.Tags.Rename("My tag", newName: "My new tag");

Organize subscriptions into folders

Uri subscription = new("https://arstechnica.com/science/feed/");
await inoreader.Subscriptions.AddToFolder(subscription, "Technology");
await inoreader.Subscriptions.RemoveFromFolder(subscription, "Technology");

Delete subscriptions, folders, or tags

await inoreader.Subscriptions.Unsubscribe(new Uri("https://arstechnica.com/science/feed/"));
await inoreader.Folders.Delete("Technology");
await inoreader.Tags.Delete("My tag");

Mark all articles as read

Article latestArticle;
await inoreader.Newsfeed.MarkAllArticlesAsRead(latestArticle.CrawlTime);
await inoreader.Folders.MarkAllArticlesAsRead("Technology", latestArticle.CrawlTime);
await inoreader.Tags.MarkAllArticlesAsRead("My tag", latestArticle.CrawlTime);
await inoreader.Subscriptions.MarkAllArticlesAsRead(new Uri("https://arstechnica.com/science/feed/"), latestArticle.CrawlTime);

Get self user

User self = await inoreader.Users.GetSelf();

About

πŸ“° .NET client for the Inoreader HTTP API

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

Packages

 
 
 

Languages