using System.Collections.Generic; using System.ComponentModel; using Newtonsoft.Json; using System.Net.Http.Json; // https://tt-rss.org/wiki/ApiReference namespace ttrss_co_client.ttrss { public class ApiClient { public Uri BaseURI { get; private set; } private HttpClient httpClient { get; set; } private string SessionId { get; set; } = null; private int api_level { get; set; } public ApiClient(Uri baseUri) { BaseURI = baseUri; httpClient = new HttpClient() { BaseAddress = baseUri }; } public async Task Login(string username, string password) { var json = new { op = "login", user = username, password = password }; var content = JsonContent.Create(json); var response = await (await httpClient.PostAsync(BaseURI, content)).Content.ReadAsStringAsync(); var loginResult = JsonConvert.DeserializeObject(response); if (loginResult.status == 0) { SessionId = loginResult.content.session_id; api_level = loginResult.content.api_level ?? 1; Console.WriteLine(SessionId); } else { throw new NotImplementedException(); } } public async Task GetApiLevel() { assertInitialized(); return await oneValueGet("getApiLevel", "level"); } public async Task Logout() { assertInitialized(); return (await oneValueGet("logout", "status"))?.ToLower() == "ok"; } public async Task IsLoggedIn() { //assertInitialized(); return (await oneValueGet("isLoggedIn", "status")); } public async Task GetUnread() { assertInitialized(); return await oneValueGet("getUnread", "unread"); } public async Task GetFeeds(int cat_id, bool unread_only, int limit, int offset, bool include_nested) { assertInitialized(); throw new NotImplementedException(); } public async Task GetCategories(bool unread_only, bool enable_nested, bool include_empty) { assertInitialized(); throw new NotImplementedException(); } public enum VIEWMODE { All, Unread, Adaptive, Marked, Updated } public enum SORTORDER { Default, OldestFirst, NewestFirst } public async Task GetHeadlines(int feed_id, int limit, int skip, /*string filter, */ bool is_cat, bool show_excerpt, bool show_content, VIEWMODE view_mode, bool include_attachments, int since_id, bool include_nested, SORTORDER order_by, bool sanitize, bool force_update = false, bool has_sandbox = false, bool include_header = false) { await getHeadlines(feed_id, null, limit, skip, /*filter, */ is_cat, show_excerpt, show_content, view_mode, include_attachments, since_id, include_nested, order_by, sanitize, force_update, has_sandbox, include_header); } public async Task GetHeadlines(string feed_id, int limit, int skip, /*string filter, */ bool is_cat, bool show_excerpt, bool show_content, VIEWMODE view_mode, bool include_attachments, int since_id, bool include_nested, SORTORDER order_by, bool sanitize, bool force_update = false, bool has_sandbox = false, bool include_header = false) { await getHeadlines(null, feed_id, limit, skip, /*filter, */ is_cat, show_excerpt, show_content, view_mode, include_attachments, since_id, include_nested, order_by, sanitize, force_update, has_sandbox, include_header); } private async Task getHeadlines(int? int_feed_id, string string_feed_id, int limit, int skip, /*string filter, */ bool is_cat, bool show_excerpt, bool show_content, VIEWMODE view_mode, bool include_attachments, int since_id, bool include_nested, SORTORDER order_by, bool sanitize, bool force_update, bool has_sandbox, bool include_header) { assertInitialized(); throw new NotImplementedException(); } public enum UPDATEMODE { SetFalse, SetTrue, Toggle } public enum UPDATEFIELD { starred, published, unread } public async Task UpdateArticleField(UPDATEMODE mode, UPDATEFIELD field, params int[] ids) { //documentation: UpdateArticle - for note, we have a separate method UpdateArticleNote assertInitialized(); throw new NotImplementedException(); } public async Task UpdateArticleNote(string data, params int[] ids) { //documentation: UpdateArticle - for fields other than note, we have a separate method UpdateArticleField assertInitialized(); throw new NotImplementedException(); } public async Task> GetArticle(params int[] article_id) { if (!article_id.Any()) { throw new ArgumentException("need at least one article_id"); } assertInitialized(); throw new NotImplementedException(); } public async Task GetConfig() { assertInitialized(); throw new NotImplementedException(); } /// ///tell the feed to update. As opposed to updating our configuration of the feed. /// public async Task UpdateFeed(int feed_id) { assertInitialized(); throw new NotImplementedException(); } public async Task GetPref(string key) { assertInitialized(); throw new NotImplementedException(); } public enum CATCHUPMODE { All, OneDay, OneWeek, TwoWeeks } public async Task CatchupFeed(int feed_id, bool is_cat, CATCHUPMODE mode = CATCHUPMODE.All) { assertInitialized(); throw new NotImplementedException(); } public async Task GetCounters(bool actual_feeds = true, bool labels = true, bool categories = true, bool tags = false) { assertInitialized(); throw new NotImplementedException(); } public async Task GetLabels(int? article_id) { assertInitialized(); if (article_id != null) { assertApiLevel(5); } throw new NotImplementedException(); } public async Task SetArticleLabel(int label_id, bool assign, params int[] article_ids) { //there's a label "cache", i guess? assertInitialized(); throw new NotImplementedException(); } public async Task ShareToPublished(string title, Uri url, string content, bool sanitize = true) { assertInitialized(); assertApiLevel(4); if (!sanitize) assertApiLevel(20); throw new NotImplementedException(); } /// ///if the feed requires basic HTTP auth; the login. (decidedly not self-explanatory, gdi) ///if the feed requires basic HTTP auth; the password. (decidedly not self-explanatory, gdi) /// public async Task SubscribeToFeed(Uri feed_url, int category_id, string login, string password) { assertInitialized(); assertApiLevel(5); throw new NotImplementedException(); } public async Task UnsubscribeFeed() { assertInitialized(); assertApiLevel(5); throw new NotImplementedException(); } public async Task GetFeedTree() { assertInitialized(); assertApiLevel(5); throw new NotImplementedException(); } private void assertInitialized() { if (SessionId == null) { throw new InvalidOperationException("no session ID - call Login first!"); } } private void assertApiLevel(int ApiLevel) { if (ApiLevel > this.api_level) { throw new NotSupportedException($"method requires api level {ApiLevel}, have {this.api_level}"); } } private async Task oneValueGet(string op, string key) { //mostly you post {"op": "aThingToDo", "sid": "sessionId", "some other param": "some other value"} //and get back something like {"seq": 0, "status", "content": {"the value you asked for": 0}} var json = JsonContent.Create(new { op = op, sid = this.SessionId }); var response = await (await httpClient.PostAsync(BaseURI, json)).Content.ReadAsStringAsync(); var apiResult = JsonConvert.DeserializeObject(response); try { var converter = TypeDescriptor.GetConverter(typeof(T)); if (converter != null) { return (T)converter.ConvertFromString(apiResult.Content[key]); } return default(T); } catch (NotSupportedException) { return default(T); } } } }