2023-04-02 23:32:03 -04:00
using System.Collections.Generic ;
2023-04-03 00:36:53 -04:00
using System.ComponentModel ;
2023-04-02 23:32:03 -04:00
using Newtonsoft.Json ;
using System.Net.Http.Json ;
2023-04-03 16:47:24 -04:00
using ttrss_co_client.ttrss.messages ;
2023-04-03 13:09:39 -04:00
using ttrss_co_client.ttrss.datastructures ;
2023-04-02 23:32:03 -04:00
// https://tt-rss.org/wiki/ApiReference
2023-04-03 16:37:59 -04:00
//TODO: a lot of these are a given at API level 1, so double check all necessary API levels
2023-04-02 23:32:03 -04:00
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 ;
2023-04-03 00:36:53 -04:00
private int api_level { get ; set ; }
2023-04-02 23:32:03 -04:00
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 ( ) ;
2023-04-03 16:47:24 -04:00
var loginResult = JsonConvert . DeserializeObject < ApiResponse < LoginResponseContent > > ( response ) ;
2023-04-02 23:32:03 -04:00
if ( loginResult . status = = 0 )
{
2023-04-03 16:47:24 -04:00
SessionId = loginResult . Content . session_id ;
2023-04-04 13:52:10 -04:00
if ( loginResult . Content . api_level = = null )
2023-04-03 16:37:59 -04:00
{
throw new NotImplementedException ( $"api doesn't report an api level - unsupported. api level 1 is version 1.5.8, so {BaseURI} might be extremely old. (or maybe this library is extremely old and ttrss changed again?)" ) ;
}
else
{
2023-04-03 16:47:24 -04:00
api_level = loginResult . Content . api_level . Value ;
2023-04-03 16:37:59 -04:00
}
2023-04-04 13:52:10 -04:00
2023-04-02 23:32:03 -04:00
Console . WriteLine ( SessionId ) ;
}
else
{
throw new NotImplementedException ( ) ;
}
}
public async Task < int > GetApiLevel ( )
{
2023-04-03 16:37:59 -04:00
//1.5.8 = api level 1, any lower than that, unsupported.
//???? = 2
//???? = 3
//1.6.0 = 4
//1.7.6 = 5
//1.8 = ????
//???? = 6
//???? = 7
//???? = 8
//1.14 = 9
2023-04-04 13:52:10 -04:00
//1.15 = 10
2023-04-02 23:32:03 -04:00
assertInitialized ( ) ;
2023-04-04 12:08:48 -04:00
return await getOneValue < int > ( "getApiLevel" , "level" ) ;
2023-04-02 23:32:03 -04:00
}
public async Task < bool > Logout ( )
{
assertInitialized ( ) ;
2023-04-03 00:36:53 -04:00
2023-04-04 12:08:48 -04:00
return ( await getOneValue < string > ( "logout" , "status" ) ) ? . ToLower ( ) = = "ok" ;
2023-04-02 23:32:03 -04:00
}
public async Task < bool > IsLoggedIn ( )
{
2023-04-03 00:36:53 -04:00
//assertInitialized();
2023-04-04 12:08:48 -04:00
return ( await getOneValue < bool > ( "isLoggedIn" , "status" ) ) ;
2023-04-02 23:32:03 -04:00
}
public async Task < int > GetUnread ( )
{
assertInitialized ( ) ;
2023-04-04 12:08:48 -04:00
return await getOneValue < int > ( "getUnread" , "unread" ) ;
2023-04-02 23:32:03 -04:00
}
2023-04-03 00:36:53 -04:00
2023-04-03 13:09:39 -04:00
///<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 ( ) ;
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 ( ) ;
2023-04-03 16:47:24 -04:00
var apiResult = JsonConvert . DeserializeObject < ApiResponse < IEnumerable < CounterInfo > > > ( response ) ;
return apiResult . Content ;
2023-04-03 13:09:39 -04:00
}
2023-04-03 13:50:33 -04:00
///<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>
2023-04-04 13:52:10 -04:00
public async Task < IEnumerable < Feed > > GetFeeds ( int cat_id = 0 , bool unread_only = false , uint limit = 0 , int offset = 0 /*, bool include_nested*/ )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
2023-04-04 13:52:10 -04:00
if ( cat_id < - 2 )
2023-04-03 13:50:33 -04:00
{
2023-04-04 13:52:10 -04:00
if ( cat_id = = - 3 | | cat_id = = - 4 )
2023-04-03 13:50:33 -04:00
{
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 ( ) ;
2023-04-03 16:47:24 -04:00
var apiResult = JsonConvert . DeserializeObject < ApiResponse < IEnumerable < Feed > > > ( response ) ;
return apiResult . Content ;
2023-04-02 23:32:03 -04:00
}
2023-04-03 14:03:31 -04:00
///<param name="enable_nested">nested mode (return only top level)</param>
public async Task < IEnumerable < Category > > GetCategories ( bool unread_only = false , bool enable_nested = false , bool include_empty = false )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
2023-04-04 13:52:10 -04:00
2023-04-03 14:03:31 -04:00
var json = JsonContent . Create ( new
{
op = "getCategories" ,
sid = this . SessionId ,
unread_only = unread_only ,
enable_nested = enable_nested ,
include_empty = include_empty
} ) ;
var response = await ( await httpClient . PostAsync ( BaseURI , json ) ) . Content . ReadAsStringAsync ( ) ;
2023-04-03 16:47:24 -04:00
var apiResult = JsonConvert . DeserializeObject < ApiResponse < IEnumerable < Category > > > ( response ) ;
return apiResult . Content ;
2023-04-02 23:32:03 -04:00
}
public enum VIEWMODE { All , Unread , Adaptive , Marked , Updated }
public enum SORTORDER { Default , OldestFirst , NewestFirst }
2023-04-03 16:37:59 -04:00
public async Task < IEnumerable < Headline > > GetHeadlines (
int feed_id ,
bool is_cat ,
2023-04-04 13:52:10 -04:00
int limit = 60 ,
int skip = 0 ,
2023-04-03 16:37:59 -04:00
/*string filter,*/
bool show_excerpt = false ,
2023-04-04 13:52:10 -04:00
bool show_content = false ,
2023-04-03 16:37:59 -04:00
VIEWMODE view_mode = VIEWMODE . All ,
bool include_attachments = false ,
int? since_id = null ,
bool include_nested = false ,
SORTORDER order_by = SORTORDER . Default ,
bool sanitize = true ,
bool force_update = false ,
bool has_sandbox = false )
2023-04-02 23:32:03 -04:00
{
2023-04-04 13:52:10 -04:00
if ( limit > 60 )
2023-04-03 16:37:59 -04:00
{
assertApiLevel ( 6 ) ;
}
2023-04-04 13:52:10 -04:00
if ( limit > 200 )
2023-04-03 16:37:59 -04:00
{
throw new ArgumentOutOfRangeException ( "limit" , limit , "capped at 200" ) ;
}
2023-04-04 13:52:10 -04:00
if ( include_nested )
2023-04-03 16:37:59 -04:00
{
assertApiLevel ( 4 ) ;
}
string sortOrderString = "" ;
if ( order_by ! = SORTORDER . Default )
{
assertApiLevel ( 5 ) ;
switch ( order_by )
{
case SORTORDER . OldestFirst :
sortOrderString = "date_reverse" ;
break ;
case SORTORDER . NewestFirst :
sortOrderString = "feed_dates" ;
break ;
}
}
2023-04-04 13:52:10 -04:00
if ( sanitize = = false )
2023-04-03 16:37:59 -04:00
{
//TODO: it's version 1.8.0, but no idea what version that is. I can narrow it down to 6, 7, or 8.
assertApiLevel ( 6 ) ;
}
2023-04-04 13:52:10 -04:00
if ( force_update )
2023-04-03 16:37:59 -04:00
{
assertApiLevel ( 9 ) ;
}
var json = JsonContent . Create ( new
{
op = "getHeadlines" ,
sid = this . SessionId ,
feed_id = feed_id ,
is_cat = is_cat ,
limit = limit ,
skip = skip ,
show_excerpt = show_excerpt ,
show_content = show_content ,
view_mode = view_mode . ToString ( "D" ) ,
include_attachments = include_attachments ,
since_id = since_id ,
include_nested = include_nested ,
order_by = sortOrderString ,
sanitize = sanitize ,
force_update = force_update ,
has_sandbox = has_sandbox
} ) ;
return await getHeadlines ( json ) ;
2023-04-02 23:32:03 -04:00
}
2023-04-03 16:37:59 -04:00
public async Task < IEnumerable < Headline > > GetHeadlinesTag (
string tag ,
2023-04-04 13:52:10 -04:00
int limit = 200 ,
int skip = 0 ,
2023-04-03 16:37:59 -04:00
/*string filter,*/
bool show_excerpt = false ,
2023-04-04 13:52:10 -04:00
bool show_content = false ,
2023-04-03 16:37:59 -04:00
VIEWMODE view_mode = VIEWMODE . All ,
bool include_attachments = false ,
int? since_id = null ,
bool include_nested = false ,
SORTORDER order_by = SORTORDER . Default ,
bool sanitize = true ,
bool force_update = false ,
bool has_sandbox = false
/*bool include_header = false*/ )
2023-04-02 23:32:03 -04:00
{
2023-04-03 16:37:59 -04:00
assertApiLevel ( 18 ) ;
2023-04-04 13:52:10 -04:00
if ( limit > 200 )
2023-04-03 16:37:59 -04:00
{
throw new ArgumentOutOfRangeException ( "limit" , limit , "capped at 200" ) ;
}
string sortOrderString ;
2023-04-04 13:52:10 -04:00
switch ( order_by )
2023-04-03 16:37:59 -04:00
{
case SORTORDER . OldestFirst :
sortOrderString = "date_reverse" ;
break ;
case SORTORDER . NewestFirst :
sortOrderString = "feed_dates" ;
break ;
default :
sortOrderString = "" ;
break ;
}
var json = JsonContent . Create ( new
{
op = "getHeadlines" ,
sid = this . SessionId ,
feed_id = tag ,
limit = limit ,
skip = skip ,
show_excerpt = show_excerpt ,
show_content = show_content ,
view_mode = view_mode . ToString ( "D" ) ,
include_attachments = include_attachments ,
since_id = since_id ,
include_nested = include_nested ,
order_by = sortOrderString ,
sanitize = sanitize ,
force_update = force_update ,
has_sandbox = has_sandbox
} ) ;
return await getHeadlines ( json ) ;
2023-04-02 23:32:03 -04:00
}
2023-04-03 16:37:59 -04:00
private async Task < IEnumerable < Headline > > getHeadlines ( JsonContent parameters )
2023-04-02 23:32:03 -04:00
{
2023-04-03 16:37:59 -04:00
var response = await ( await httpClient . PostAsync ( BaseURI , parameters ) ) . Content . ReadAsStringAsync ( ) ;
2023-04-03 16:47:24 -04:00
var apiResult = JsonConvert . DeserializeObject < ApiResponse < IEnumerable < Headline > > > ( response ) ;
return apiResult . Content ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 02:16:56 -04:00
#region Get headlines include_header = true
// public async Task<Tuple<ttrss.messages.HeadlinesHeaderResponse, IEnumerable<Headline>>> GetHeadlinesAndHeader(
2023-04-03 16:37:59 -04:00
// int feed_id,
// bool is_cat,
// int limit=60,
// int skip=0,
// /*string filter,*/
// bool show_excerpt = false,
// bool show_content=false,
// VIEWMODE view_mode = VIEWMODE.All,
// bool include_attachments = false,
// int? since_id = null,
// bool include_nested = false,
// SORTORDER order_by = SORTORDER.Default,
// bool sanitize = true,
// bool force_update = false,
// bool has_sandbox = false)
// {
// assertApiLevel(12);
// if(limit > 200)
// {
// throw new ArgumentOutOfRangeException("limit", limit, "capped at 200");
// }
// string sortOrderString = "";
// if (order_by != SORTORDER.Default)
// {
// switch (order_by)
// {
// case SORTORDER.OldestFirst:
// sortOrderString = "date_reverse";
// break;
// case SORTORDER.NewestFirst:
// sortOrderString = "feed_dates";
// break;
// }
// }
// var json = JsonContent.Create(new
// {
// op = "getHeadlines",
// sid = this.SessionId,
// feed_id = feed_id,
// is_cat = is_cat,
// limit = limit,
// skip = skip,
// show_excerpt = show_excerpt,
// show_content = show_content,
// view_mode = view_mode.ToString("D"),
// include_attachments = include_attachments,
// since_id = since_id,
// include_nested = include_nested,
// order_by = sortOrderString,
// sanitize = sanitize,
// force_update = force_update,
// has_sandbox = has_sandbox,
// include_header = true
// });
// return await getHeadlinesAndHeader(json);
// }
// public async Task<HeadlinesHeaderResponse> GetHeadlinesTagAndHeader(
// string tag,
// int limit=200,
// int skip=0,
// /*string filter,*/
// bool show_excerpt = false,
// bool show_content=false,
// VIEWMODE view_mode = VIEWMODE.All,
// bool include_attachments = false,
// int? since_id = null,
// bool include_nested = false,
// SORTORDER order_by = SORTORDER.Default,
// bool sanitize = true,
// bool force_update = false,
// bool has_sandbox = false
// /*bool include_header = false*/)
// {
// assertApiLevel(18);
// if(limit > 200)
// {
// throw new ArgumentOutOfRangeException("limit", limit, "capped at 200");
// }
// string sortOrderString;
// switch(order_by)
// {
// case SORTORDER.OldestFirst:
// sortOrderString = "date_reverse";
// break;
// case SORTORDER.NewestFirst:
// sortOrderString = "feed_dates";
// break;
// default:
// sortOrderString = "";
// break;
// }
// var json = JsonContent.Create(new
// {
// op = "getHeadlines",
// sid = this.SessionId,
// feed_id = tag,
// limit = limit,
// skip = skip,
// show_excerpt = show_excerpt,
// show_content = show_content,
// view_mode = view_mode.ToString("D"),
// include_attachments = include_attachments,
// since_id = since_id,
// include_nested = include_nested,
// order_by = sortOrderString,
// sanitize = sanitize,
// force_update = force_update,
// has_sandbox = has_sandbox,
// include_header = true
// });
// return await getHeadlinesAndHeader(json);
// }
2023-04-04 02:16:56 -04:00
//todo: deserialize array of disparate types. can that be done?
// private async Task<Tuple<ttrss.messages.HeadlinesHeaderResponse, IEnumerable<Headline>>> getHeadlinesAndHeader(JsonContent parameters)
2023-04-03 16:37:59 -04:00
// {
// var response = await (await httpClient.PostAsync(BaseURI, parameters)).Content.ReadAsStringAsync();
2023-04-04 02:16:56 -04:00
// var immediateApiResult = JsonConvert.DeserializeObject<ApiResponse<IEnumerable<string>>>(response).Content.ToList();
// var apiResult = JsonConvert.DeserializeObject<Tuple<ttrss.messages.HeadlinesHeaderResponse, IEnumerable<Headline>>>(immediateApiResult[1]);
2023-04-03 16:37:59 -04:00
// return apiResult;
// }
2023-04-04 02:16:56 -04:00
#endregion
///<summary>to update note, see <see cref="UpdateArticleNote"/></summary>
2023-04-04 13:52:10 -04:00
public enum UPDATEFIELD { starred = 0 , published = 1 , unread = 2 }
public enum UPDATEMODE { SetFalse = 0 , SetTrue = 1 , Toggle = 2 }
public async Task < int > UpdateArticleField ( UPDATEFIELD field , UPDATEMODE mode , params int [ ] ids )
2023-04-02 23:32:03 -04:00
{
2023-04-04 13:52:10 -04:00
if ( ids = = null | | ids . Length = = 0 )
2023-04-04 02:16:56 -04:00
{
throw new System . ArgumentNullException ( "ids" , "need to specify at least one id" ) ;
}
2023-04-02 23:32:03 -04:00
//documentation: UpdateArticle - for note, we have a separate method UpdateArticleNote
assertInitialized ( ) ;
2023-04-04 02:16:56 -04:00
var json = JsonContent . Create ( new
{
op = "updateArticle" ,
sid = this . SessionId ,
article_ids = string . Join ( ',' , ids ) ,
mode = ( int ) mode ,
field = ( int ) field
} ) ;
var response = await ( await httpClient . PostAsync ( BaseURI , json ) ) . Content . ReadAsStringAsync ( ) ;
var apiResult = JsonConvert . DeserializeObject < ApiResponse < ArticleUpdateFieldResponse > > ( response ) ;
return apiResult . Content . updated ;
2023-04-02 23:32:03 -04:00
throw new NotImplementedException ( ) ;
}
2023-04-04 02:16:56 -04:00
///<summary>for fields other than note, we have a separate method UpdateArticleField</summary>
2023-04-02 23:32:03 -04:00
public async Task < int > UpdateArticleNote ( string data , params int [ ] ids )
{
2023-04-04 13:52:10 -04:00
if ( ids = = null | | ids . Length = = 0 )
2023-04-04 02:16:56 -04:00
{
throw new System . ArgumentNullException ( "ids" , "need to specify at least one id" ) ;
}
2023-04-04 12:08:48 -04:00
2023-04-02 23:32:03 -04:00
assertInitialized ( ) ;
2023-04-04 02:16:56 -04:00
var json = JsonContent . Create ( new
{
op = "updateArticle" ,
sid = this . SessionId ,
article_ids = string . Join ( ',' , ids ) ,
field = 3 ,
data = data ,
} ) ;
var response = await ( await httpClient . PostAsync ( BaseURI , json ) ) . Content . ReadAsStringAsync ( ) ;
var apiResult = JsonConvert . DeserializeObject < ApiResponse < ArticleUpdateFieldResponse > > ( response ) ;
return apiResult . Content . updated ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 12:08:48 -04:00
public async Task < IEnumerable < Article > > GetArticles ( params int [ ] article_id )
2023-04-02 23:32:03 -04:00
{
if ( ! article_id . Any ( ) )
{
throw new ArgumentException ( "need at least one article_id" ) ;
}
assertInitialized ( ) ;
2023-04-04 12:08:48 -04:00
var json = JsonContent . Create ( new
{
op = "getArticle" ,
sid = this . SessionId ,
article_id = string . Join ( ',' , article_id )
} ) ;
return await get < IEnumerable < Article > > ( json ) ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 12:13:12 -04:00
public async Task < datastructures . Configuration > GetConfig ( )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
2023-04-04 12:13:12 -04:00
var json = JsonContent . Create ( new
{
op = "getConfig" ,
sid = this . SessionId
} ) ;
return await get < datastructures . Configuration > ( json ) ;
2023-04-02 23:32:03 -04:00
}
///<summary>
///tell the feed to update. As opposed to updating our configuration of the feed.
///</summary>
2023-04-04 12:46:30 -04:00
public async Task < bool > UpdateFeed ( int feed_id )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
2023-04-04 12:46:30 -04:00
var json = JsonContent . Create ( new
{
op = "updateFeed" ,
sid = this . SessionId ,
feed_id = feed_id
} ) ;
var apiResponse = await get < Dictionary < string , string > > ( json ) ;
2023-04-04 13:52:10 -04:00
2023-04-04 12:46:30 -04:00
return apiResponse . ContainsKey ( "status" ) & & apiResponse [ "status" ] ? . ToLower ( ) = = "ok" ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 12:46:30 -04:00
public async Task < T > GetPref < T > ( string pref )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
2023-04-04 12:46:30 -04:00
var json = JsonContent . Create ( new
{
op = "getPref" ,
sid = this . SessionId ,
pref_name = pref
} ) ;
var apiResponse = await get < Dictionary < string , string > > ( json ) ;
try
{
var converter = TypeDescriptor . GetConverter ( typeof ( T ) ) ;
if ( converter ! = null )
{
return ( T ) converter . ConvertFromString ( apiResponse [ "value" ] ) ;
}
return default ( T ) ;
}
catch ( NotSupportedException )
{
return default ( T ) ;
}
2023-04-02 23:32:03 -04:00
}
public enum CATCHUPMODE { All , OneDay , OneWeek , TwoWeeks }
2023-04-04 13:12:08 -04:00
///<summary>Tries to catchup (e.g. mark as read) specified feed.</summary>
public async Task < bool > CatchupFeed ( int feed_id , bool is_cat , CATCHUPMODE mode = CATCHUPMODE . All )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
2023-04-04 13:12:08 -04:00
var modestring = "all" ;
2023-04-04 13:52:10 -04:00
if ( mode ! = CATCHUPMODE . All )
2023-04-04 13:12:08 -04:00
{
assertApiLevel ( 15 ) ;
switch ( mode )
{
case CATCHUPMODE . OneDay :
modestring = "1day" ;
break ;
case CATCHUPMODE . OneWeek :
modestring = "1week" ;
break ;
case CATCHUPMODE . TwoWeeks :
modestring = "2week" ;
break ;
}
}
var json = JsonContent . Create ( new
{
op = "catchupFeed" ,
sid = this . SessionId ,
feed_id = feed_id ,
is_cat = is_cat ,
mode = modestring
} ) ;
var apiResponse = await get < Dictionary < string , string > > ( json ) ;
2023-04-04 13:52:10 -04:00
2023-04-04 13:12:08 -04:00
return apiResponse . ContainsKey ( "status" ) & & apiResponse [ "status" ] ? . ToLower ( ) = = "ok" ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 13:52:10 -04:00
public async Task < IEnumerable < Label > > GetLabels ( int? article_id = null )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
2023-04-04 13:36:08 -04:00
var json = JsonContent . Create ( new
2023-04-02 23:32:03 -04:00
{
2023-04-04 13:36:08 -04:00
op = "getLabels" ,
sid = this . SessionId ,
article_id = article_id
} ) ;
var labels = await get < IEnumerable < Label > > ( json ) ;
2023-04-04 13:52:10 -04:00
if ( this . api_level < 5 )
2023-04-04 13:36:08 -04:00
{
2023-04-04 13:52:10 -04:00
foreach ( var l in labels )
2023-04-04 13:36:08 -04:00
{
l . id = - 11 - l . id ;
}
2023-04-02 23:32:03 -04:00
}
2023-04-04 13:36:08 -04:00
return labels ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 13:52:10 -04:00
public async Task < int > SetArticleLabel ( int label_id , bool assign , params int [ ] article_ids )
2023-04-02 23:32:03 -04:00
{
2023-04-04 13:52:10 -04:00
if ( article_ids = = null | | article_ids . Length = = 0 )
{
throw new System . ArgumentNullException ( "ids" , "need to specify at least one id" ) ;
}
2023-04-02 23:32:03 -04:00
assertInitialized ( ) ;
2023-04-04 13:52:10 -04:00
var json = JsonContent . Create ( new
{
op = "setArticleLabel" ,
sid = this . SessionId ,
article_ids = string . Join ( ',' , article_ids ) ,
label_id = label_id ,
assign = assign
} ) ;
var apiResponse = await get < Dictionary < string , string > > ( json ) ;
if ( ! apiResponse . ContainsKey ( "status" ) & & apiResponse [ "status" ] ? . ToLower ( ) = = "ok" )
{
return 0 ;
}
if ( this . api_level < = 10 )
{
//update label cache
var tasks = new List < Task > ( ) ;
foreach ( var id in article_ids )
{
tasks . Add ( GetLabels ( id ) ) ;
}
Task . WaitAll ( tasks . ToArray ( ) ) ;
}
int toReturn ;
if ( int . TryParse ( apiResponse [ "updated" ] , out toReturn ) )
{
return toReturn ;
}
else
{
throw new Exception ( "update ostensibly ok, but couldn't parse" ) ;
}
2023-04-02 23:32:03 -04:00
}
2023-04-04 14:50:30 -04:00
public async Task < bool > ShareToPublished ( string title , Uri url , string content , bool sanitize = true )
2023-04-02 23:32:03 -04:00
{
2023-04-04 14:50:30 -04:00
if ( url = = null )
{
throw new ArgumentNullException ( "url" ) ;
}
2023-04-02 23:32:03 -04:00
assertInitialized ( ) ;
assertApiLevel ( 4 ) ;
2023-04-03 00:36:53 -04:00
if ( ! sanitize )
2023-04-02 23:32:03 -04:00
assertApiLevel ( 20 ) ;
2023-04-04 14:50:30 -04:00
var json = JsonContent . Create ( new
{
op = "shareToPublished" ,
sid = this . SessionId ,
title = title ,
url = url . ToString ( ) ,
content = content ,
sanitize = sanitize
} ) ;
var apiResponse = await get < Dictionary < string , string > > ( json ) ;
return apiResponse . ContainsKey ( "status" ) & & apiResponse [ "status" ] ? . ToLower ( ) = = "ok" ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 14:50:30 -04:00
///<param name="category_id">0 = uncategorized</param>
///<param name="login">probably if the feed requires basic HTTP auth; the login? (decidedly not self-explanatory, gdi)</param>
///<param name="password">probably if the feed requires basic HTTP auth; the password? (decidedly not self-explanatory, gdi)</param>
///<returns>¯\_(ツ)_/¯ - code 1 = success, I think? - the documentation says "See subscribe_to_feed() in functions.php for details" - that function doesn't exist (as of 2023-04-04).</returns>
public async Task < int > SubscribeToFeed ( Uri feed_url , int category_id = 0 , string login = null , string password = null )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
assertApiLevel ( 5 ) ;
2023-04-04 14:50:30 -04:00
var json = JsonContent . Create ( new
{
op = "subscribeToFeed" ,
sid = this . SessionId ,
feed_url = feed_url . ToString ( ) ,
category_id = category_id ,
login = login ,
password = password
} ) ;
var apiResponse = await get < Dictionary < string , Dictionary < string , int > > > ( json ) ;
return apiResponse [ "status" ] [ "code" ] ;
2023-04-02 23:32:03 -04:00
}
2023-04-04 15:01:50 -04:00
public async Task < bool > UnsubscribeFeed ( int feed_id )
2023-04-02 23:32:03 -04:00
{
assertInitialized ( ) ;
assertApiLevel ( 5 ) ;
2023-04-04 15:01:50 -04:00
var json = JsonContent . Create ( new
{
op = "unsubscribeFeed" ,
sid = this . SessionId ,
feed_id = feed_id . ToString ( )
} ) ;
var apiResponse = await get < Dictionary < string , string > > ( json ) ;
return apiResponse . ContainsKey ( "status" ) & & apiResponse [ "status" ] ? . ToLower ( ) = = "ok" ;
2023-04-02 23:32:03 -04:00
}
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 )
{
2023-04-03 00:36:53 -04:00
if ( ApiLevel > this . api_level )
2023-04-02 23:32:03 -04:00
{
throw new NotSupportedException ( $"method requires api level {ApiLevel}, have {this.api_level}" ) ;
}
}
2023-04-03 00:36:53 -04:00
2023-04-04 12:08:48 -04:00
private async Task < T > getOneValue < T > ( string op , string key )
2023-04-03 00:36:53 -04:00
{
2023-04-03 13:09:39 -04:00
//mostly you post {"op": "getAThing", "sid": "sessionId"}
//and get back something like {"seq": 0, "status": 0, "content": {"the value you asked for": 0}}
2023-04-03 00:36:53 -04:00
var json = JsonContent . Create ( new
{
op = op ,
sid = this . SessionId
} ) ;
var response = await ( await httpClient . PostAsync ( BaseURI , json ) ) . Content . ReadAsStringAsync ( ) ;
2023-04-03 16:47:24 -04:00
var apiResult = JsonConvert . DeserializeObject < ttrss . messages . ApiResponse < Dictionary < string , string > > > ( response ) ;
2023-04-03 00:36:53 -04:00
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 ) ;
}
}
2023-04-04 12:08:48 -04:00
private async Task < T > get < T > ( JsonContent json )
{
var response = await ( await httpClient . PostAsync ( BaseURI , json ) ) . Content . ReadAsStringAsync ( ) ;
var apiResult = JsonConvert . DeserializeObject < ttrss . messages . ApiResponse < T > > ( response ) ;
return apiResult . Content ;
}
2023-04-02 23:32:03 -04:00
}
}