diff --git a/.gitignore b/.gitignore index ca1c7a3..a4fd64f 100644 --- a/.gitignore +++ b/.gitignore @@ -398,3 +398,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +appsettings.json diff --git a/Configuration.cs b/Configuration.cs new file mode 100644 index 0000000..9f6d768 --- /dev/null +++ b/Configuration.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace ttrss_co_client +{ + public class Configuration + { + public Uri BaseURI { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..43577ed --- /dev/null +++ b/Program.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; + +namespace ttrss_co_client +{ + class Program + { + static async Task Main(string[] args) + { + var conf = Configure(); + var ttrssClient = new ttrss.ApiClient(conf.BaseURI); + await ttrssClient.Login(conf.Username, conf.Password); + Console.WriteLine("Hello, World!"); + } + static Configuration Configure(string configurationPath = "appsettings.json") + { + if (!File.Exists(configurationPath)) + { + Console.Error.WriteLine($"could not find configuration at {configurationPath}! copying sample to that spot."); + File.Copy("sample-appsettings.json", configurationPath); //and you know what, if that explodes at the OS level, the OS should give you an error + return null; + } + var fileContents = File.ReadAllText(configurationPath); + if (string.IsNullOrWhiteSpace(fileContents)) + { + Console.Error.WriteLine($"configuration file at {configurationPath} was empty! overwriting with sample settings."); + File.Copy("sample-appsettings.json", configurationPath, true); + return null; + } + + var conf = JsonConvert.DeserializeObject(fileContents); + + if (conf == null) + { + Console.Error.WriteLine($"configuration file at {configurationPath} was empty! overwriting with sample settings."); + File.Copy("sample-appsettings.json", configurationPath, true); + return null; + } + + return conf; + } + + } +} \ No newline at end of file diff --git a/sample-appsettings.json b/sample-appsettings.json new file mode 100644 index 0000000..b150adb --- /dev/null +++ b/sample-appsettings.json @@ -0,0 +1,5 @@ +{ + "BaseUri": "https://ttrss.example.com/api/", + "username": "guy who didn't configure", + "password": "sordph1sh" +} \ No newline at end of file diff --git a/ttrss-co-client.csproj b/ttrss-co-client.csproj new file mode 100644 index 0000000..e487660 --- /dev/null +++ b/ttrss-co-client.csproj @@ -0,0 +1,15 @@ + + + + Exe + net7.0 + ttrss_co_client + enable + disable + + + + + + + diff --git a/ttrss/ApiClient.cs b/ttrss/ApiClient.cs new file mode 100644 index 0000000..756742a --- /dev/null +++ b/ttrss/ApiClient.cs @@ -0,0 +1,209 @@ +using System.Collections.Generic; +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(); + throw new NotImplementedException(); + } + + public async Task GetVersion() + { + assertInitialized(); + throw new NotImplementedException(); + } + + public async Task Logout() + { + assertInitialized(); + throw new NotImplementedException(); + } + public async Task IsLoggedIn() + { + assertInitialized(); + throw new NotImplementedException(); + } + public async Task GetUnread() + { + assertInitialized(); + throw new NotImplementedException(); + } + 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) + { + 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) + { + 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}"); + } + } + } +} \ No newline at end of file diff --git a/ttrss/Configuration.cs b/ttrss/Configuration.cs new file mode 100644 index 0000000..feffa34 --- /dev/null +++ b/ttrss/Configuration.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Net.Http.Json; + +namespace ttrss_co_client.ttrss +{ + public class Configuration + { + public string icons_dir { get; set; } + public string icons_url { get; set; } + public bool daemon_is_running { get; set; } + public int num_feeds { get; set; } + } +} \ No newline at end of file diff --git a/ttrss/messages/LoginResponse.cs b/ttrss/messages/LoginResponse.cs new file mode 100644 index 0000000..a2e0446 --- /dev/null +++ b/ttrss/messages/LoginResponse.cs @@ -0,0 +1,17 @@ +namespace ttrss_co_client.ttrss.messages +{ + public class LoginResponse + { + public int seq { get; set; } + public int status { get; set; } + public Content content { get; set; } + + public class Content + { + public string session_id { get; set; } + public Configuration config { get; set; } + public int? api_level { get; set; } + public string error { get; set; } + } + } +} \ No newline at end of file