277 lines
11 KiB
C#
277 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using Newtonsoft.Json;
|
|
using System.Net.Http.Json;
|
|
using ttrss_co_client.ttrss.datastructures;
|
|
|
|
// 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<ttrss.messages.LoginResponse>(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<int> GetApiLevel()
|
|
{
|
|
assertInitialized();
|
|
return await oneValueGet<int>("getApiLevel", "level");
|
|
}
|
|
|
|
public async Task<bool> Logout()
|
|
{
|
|
assertInitialized();
|
|
|
|
return (await oneValueGet<string>("logout", "status"))?.ToLower() == "ok";
|
|
}
|
|
public async Task<bool> IsLoggedIn()
|
|
{
|
|
//assertInitialized();
|
|
return (await oneValueGet<bool>("isLoggedIn", "status"));
|
|
}
|
|
public async Task<int> GetUnread()
|
|
{
|
|
assertInitialized();
|
|
return await oneValueGet<int>("getUnread", "unread");
|
|
}
|
|
|
|
///<summary>at least in my installation, it doesn't seem to respect the parameters I give it, be it curl or here.</summary>
|
|
public async Task<IEnumerable<CounterInfo>> GetCounters(bool feeds = true, bool labels = true, bool categories = true, bool tags = false)
|
|
{
|
|
assertInitialized();
|
|
assertApiLevel(4);
|
|
|
|
var output_mode = "";
|
|
if (feeds) output_mode += "f";
|
|
if (labels) output_mode += "l";
|
|
if (categories) output_mode += "c";
|
|
if (tags) output_mode += "t";
|
|
|
|
var json = JsonContent.Create(new
|
|
{
|
|
op = "getCounters",
|
|
sid = this.SessionId,
|
|
output_mode = output_mode
|
|
});
|
|
var response = await (await httpClient.PostAsync(BaseURI, json)).Content.ReadAsStringAsync();
|
|
var apiResult = JsonConvert.DeserializeObject<ttrss.messages.CounterInfoResponse>(response);
|
|
return apiResult.content;
|
|
}
|
|
///<param name="limit">0 for all</param>
|
|
///<param name="offset">skip this amount first</param>
|
|
///<param name="include_nested">idk, doesn't affect what the documentation says it should</param>
|
|
public async Task<IEnumerable<Feed>> GetFeeds(int cat_id = 0, bool unread_only=false, uint limit = 0, int offset = 0/*, bool include_nested*/)
|
|
{
|
|
assertInitialized();
|
|
if(cat_id <-2)
|
|
{
|
|
if(cat_id == -3 || cat_id == -4)
|
|
{
|
|
assertApiLevel(4);
|
|
}
|
|
else
|
|
{
|
|
throw new IndexOutOfRangeException($"cat_id {cat_id} is out of range");
|
|
}
|
|
}
|
|
|
|
var json = JsonContent.Create(new
|
|
{
|
|
op = "getFeeds",
|
|
sid = this.SessionId,
|
|
cat_id = cat_id,
|
|
unread_only = unread_only,
|
|
limit = limit,
|
|
offset = offset
|
|
});
|
|
var response = await (await httpClient.PostAsync(BaseURI, json)).Content.ReadAsStringAsync();
|
|
var apiResult = JsonConvert.DeserializeObject<ttrss.messages.FeedsResponse>(response);
|
|
return apiResult.content;
|
|
}
|
|
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<int> 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<int> 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<IEnumerable<object>> 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<Configuration> GetConfig()
|
|
{
|
|
assertInitialized();
|
|
throw new NotImplementedException();
|
|
}
|
|
///<summary>
|
|
///tell the feed to update. As opposed to updating our configuration of the feed.
|
|
///</summary>
|
|
public async Task UpdateFeed(int feed_id)
|
|
{
|
|
assertInitialized();
|
|
throw new NotImplementedException();
|
|
}
|
|
public async Task<string> 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 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();
|
|
}
|
|
///<summary>
|
|
///<param name="login"/>if the feed requires basic HTTP auth; the login. (decidedly not self-explanatory, gdi)</param>
|
|
///<param name="password"/>if the feed requires basic HTTP auth; the password. (decidedly not self-explanatory, gdi)</param>
|
|
///</summary>
|
|
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<T> oneValueGet<T>(string op, string key)
|
|
{
|
|
//mostly you post {"op": "getAThing", "sid": "sessionId"}
|
|
//and get back something like {"seq": 0, "status": 0, "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<ttrss.messages.GenericApiResponse>(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);
|
|
}
|
|
}
|
|
}
|
|
} |