2023-04-02 23:32:03 -04:00
using Newtonsoft.Json ;
2023-04-03 13:50:33 -04:00
using System.Linq ;
2023-04-05 01:44:46 -04:00
using System.Diagnostics ;
2023-04-05 22:02:37 -04:00
using System.Text.RegularExpressions ;
2023-04-07 22:24:31 -04:00
using ttrss_co_client.tasks ;
2023-04-02 23:32:03 -04:00
namespace ttrss_co_client
{
class Program
{
2023-04-07 22:24:31 -04:00
private static ttrss . ApiClient ttrssClient ;
2023-04-02 23:32:03 -04:00
static async Task Main ( string [ ] args )
{
var conf = Configure ( ) ;
2023-04-09 17:01:25 -04:00
Console . WriteLine ( $"{DateTime.Now.ToString(" o ")}" ) ;
2023-04-05 21:05:58 -04:00
2023-04-07 22:24:31 -04:00
ttrssClient = new ttrss . ApiClient ( conf . BaseURI ) ;
2023-04-02 23:32:03 -04:00
await ttrssClient . Login ( conf . Username , conf . Password ) ;
2023-04-03 00:36:53 -04:00
var loggedin = await ttrssClient . IsLoggedIn ( ) ;
Console . WriteLine ( $"logged in: {loggedin}" ) ;
2023-04-07 22:24:31 -04:00
#region phase 1
Console . WriteLine ( "===== phase 1 =====" ) ;
var Phase1Tasks = new List < Task < WorkOrder > > ( ) ;
Phase1Task . TtrssClient = ttrssClient ;
Phase1Task . Conf = conf ;
var phase1TaskTypes = AppDomain . CurrentDomain . GetAssemblies ( )
. SelectMany ( domainAssembly = > domainAssembly . GetTypes ( ) )
. Where ( type = > type . IsSubclassOf ( typeof ( Phase1Task ) ) & & ! type . IsAbstract )
. ToList ( ) ;
var phase1TaskConcretions = new List < Phase1Task > ( ) ;
2023-04-09 17:01:25 -04:00
foreach ( var phase1TaskType in phase1TaskTypes )
{
var concretion = ( Phase1Task ) Activator . CreateInstance ( phase1TaskType ) ;
concretion . TriggerLabel = conf . feedActions . FirstOrDefault ( fa = > fa . command = = concretion . TaskName ) . triggerlabelCaption ;
phase1TaskConcretions . Add ( concretion ) ;
}
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $"{phase1TaskConcretions.Count()} phase 1 task types understood" ) ;
2023-04-05 01:44:46 -04:00
var unreadFeeds = await ttrssClient . GetFeeds ( cat_id : - 3 , unread_only : true ) ;
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $"{unreadFeeds.Count()} feeds unread" ) ;
2023-04-05 01:44:46 -04:00
foreach ( var uf in unreadFeeds )
{
2023-04-05 12:43:58 -04:00
Console . WriteLine ( $"unread feed: {uf.title}" ) ;
2023-04-05 16:25:58 -04:00
var headlines = await ttrssClient . GetHeadlines ( uf . id , view_mode : ttrss . ApiClient . VIEWMODE . Unread , include_attachments : true ) ;
2023-04-05 12:43:58 -04:00
foreach ( var hl in headlines )
{
2023-04-07 22:24:31 -04:00
var labelsWRTArticle = ( await ttrssClient . GetLabels ( hl . id ) ) ;
2023-04-05 16:25:58 -04:00
var actionsForFeed = conf . feedActions . Where ( fa = >
2023-04-07 22:24:31 -04:00
labelsWRTArticle . Where ( l = > l . @checked ) . Select ( l = > l . caption ) . Contains ( fa . triggerlabelCaption ) ) ? . ToList ( ) ;
2023-04-05 16:25:58 -04:00
if ( actionsForFeed ! = null & & actionsForFeed . Any ( ) )
2023-04-05 01:44:46 -04:00
{
2023-04-07 22:24:31 -04:00
var action = actionsForFeed . First ( ) ;
2023-04-09 17:01:25 -04:00
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $" headline {hl.title} has label {action.triggerlabelCaption} / action {action.command}" ) ;
var appropriatePhase1Task = phase1TaskConcretions . FirstOrDefault ( tt = > tt . TaskName = = action . command ) ;
2023-04-09 17:01:25 -04:00
if ( appropriatePhase1Task ! = null )
2023-04-05 22:02:37 -04:00
{
2023-04-07 22:24:31 -04:00
Phase1Tasks . Add ( appropriatePhase1Task . ActOn ( hl , labelsWRTArticle ) ) ;
2023-04-05 22:02:37 -04:00
}
2023-04-07 22:24:31 -04:00
else
2023-04-05 01:44:46 -04:00
{
2023-04-07 22:24:31 -04:00
Console . Error . WriteLine ( $"couldn't find phase 1 task {action.command} for workorder referring to article id {hl.id}!" ) ;
2023-04-05 01:44:46 -04:00
}
}
2023-04-05 12:43:58 -04:00
}
2023-04-05 01:44:46 -04:00
}
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $"done processing feeds. phase 1 tasks launched. wait to complete." ) ;
var remainingWork = new List < WorkOrder > ( ) ;
2023-04-09 17:01:25 -04:00
foreach ( var lingeringTask in Phase1Tasks )
2023-04-07 22:24:31 -04:00
{
var wo = await lingeringTask ;
2023-04-09 17:01:25 -04:00
if ( wo ! = null )
2023-04-07 22:24:31 -04:00
{
Console . WriteLine ( $"articleId {wo.articleId} left a work order; it has {wo.Phase2TaskList.Count()} phase 2 task{(wo.Phase2TaskList.Count() == 1 ? "" : " s ")}" ) ;
remainingWork . Add ( wo ) ;
}
}
Console . WriteLine ( $"phase 1 tasks complete, carrying {remainingWork.Count()} workorder{(remainingWork.Count() == 1 ? "" : " s ")} to phase 2" ) ;
#endregion
2023-04-03 13:50:33 -04:00
2023-04-07 22:24:31 -04:00
#region local tasks ( stripping ads , converting . )
Console . WriteLine ( "===== phase 2 =====" ) ;
//loop through working directory looking for other work orders to add
2023-04-09 17:01:25 -04:00
var workOrderFiles = Directory . GetFiles ( $"{conf.WorkingDirectory}" , "workorder.json" , new EnumerationOptions ( ) { RecurseSubdirectories = true } ) ;
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $"{workOrderFiles.Count()} workorder file{(workOrderFiles.Count() == 1 ? "" : " s ")} located" ) ;
2023-04-09 17:01:25 -04:00
foreach ( var workorderFile in workOrderFiles )
2023-04-07 22:24:31 -04:00
{
try
{
2023-04-09 17:01:25 -04:00
remainingWork . Add ( JsonConvert . DeserializeObject < WorkOrder > ( File . ReadAllText ( workorderFile ) ) ) ;
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $"picked up workorder task; {workorderFile}" ) ;
2023-04-09 17:01:25 -04:00
File . Delete ( workorderFile ) ;
2023-04-07 22:24:31 -04:00
}
2023-04-09 17:01:25 -04:00
catch ( Exception e )
2023-04-07 22:24:31 -04:00
{
Console . Error . WriteLine ( $"error picking up work order file {workorderFile} - {e.Message}; {e.StackTrace}" ) ;
}
}
Console . WriteLine ( $"{remainingWork.Count} phase 2 workorders, between pulled from ttrss and read from files" ) ;
2023-04-03 00:36:53 -04:00
2023-04-07 23:54:09 -04:00
Phase2Task . TtrssClient = ttrssClient ;
Phase2Task . Conf = conf ;
var phase2TaskTypes = AppDomain . CurrentDomain . GetAssemblies ( )
. SelectMany ( domainAssembly = > domainAssembly . GetTypes ( ) )
. Where ( type = > type . IsSubclassOf ( typeof ( Phase2Task ) ) & & ! type . IsAbstract )
. ToList ( ) ;
var phase2TaskConcretions = new List < Phase2Task > ( ) ;
foreach ( var phase2TaskType in phase2TaskTypes )
{
2023-04-09 17:01:25 -04:00
var concretion = ( Phase2Task ) Activator . CreateInstance ( phase2TaskType ) ;
2023-04-07 23:54:09 -04:00
phase2TaskConcretions . Add ( concretion ) ;
}
2023-07-06 22:31:53 -04:00
ChatMessage . ChatScript = conf . ChatScript ;
2023-04-09 17:01:25 -04:00
while ( remainingWork . Count > 0 )
2023-04-05 02:52:26 -04:00
{
2023-04-07 22:24:31 -04:00
//todo: solve the halting problem
2023-04-07 23:54:09 -04:00
//ok but seriously, maybe I could time out work orders after a while? but converting a 3-hour 4k video could easily take an hour!
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $"{phase2TaskConcretions.Count()} phase 2 task types understood" ) ;
var Phase2Tasks = new List < Task < Tuple < Phase2Task . TaskStatus , WorkOrder > > > ( ) ;
2023-04-09 17:01:25 -04:00
2023-04-07 22:24:31 -04:00
Console . WriteLine ( $"launching first pass over work orders in phase 2." ) ;
2023-04-09 17:01:25 -04:00
foreach ( var wo in remainingWork )
2023-04-05 02:52:26 -04:00
{
2023-04-07 22:24:31 -04:00
var taskName = wo . Phase2TaskList [ wo . Phase2TaskList . Keys . Min ( ) ] ;
2023-04-09 17:01:25 -04:00
2023-04-07 22:24:31 -04:00
var appropriatePhase2Task = phase2TaskConcretions . FirstOrDefault ( tt = > tt . TaskName = = taskName ) ;
2023-04-09 17:01:25 -04:00
if ( appropriatePhase2Task ! = null )
2023-04-05 02:52:26 -04:00
{
2023-04-07 22:24:31 -04:00
wo . Phase2TaskList . Remove ( wo . Phase2TaskList . Keys . Min ( ) ) ;
2023-07-06 22:31:53 -04:00
Console . WriteLine ( "launching phase 2 task: " + taskName ) ;
2023-04-07 22:24:31 -04:00
Phase2Tasks . Add ( appropriatePhase2Task . ActOn ( wo ) ) ;
2023-04-05 02:52:26 -04:00
}
2023-04-07 22:24:31 -04:00
else
2023-04-05 21:05:58 -04:00
{
2023-04-07 22:24:31 -04:00
Console . Error . WriteLine ( $"couldn't find phase 2 task {taskName} for workorder referring to article id {wo.articleId}!" ) ;
2023-04-05 21:05:58 -04:00
}
2023-04-07 22:24:31 -04:00
}
Console . WriteLine ( $"phase 2 tasks launched. now the complex part." ) ;
2023-04-09 17:01:25 -04:00
2023-04-07 22:24:31 -04:00
remainingWork = new List < WorkOrder > ( ) ;
2023-04-09 17:01:25 -04:00
foreach ( var lingeringTask in Phase2Tasks )
2023-04-07 22:24:31 -04:00
{
var wo = await lingeringTask ;
2023-07-06 22:31:53 -04:00
var wop = Path . Combine ( conf . WorkingDirectory , wo . Item2 . guid . ToString ( ) , "workorder.json" ) ;
2023-04-09 17:01:25 -04:00
//if you tell me it's done, or you need to continue now.... I believe you.
2023-04-07 22:24:31 -04:00
switch ( wo . Item1 )
2023-04-05 21:05:58 -04:00
{
2023-04-07 22:24:31 -04:00
case Phase2Task . TaskStatus . Done :
2023-07-17 16:22:26 -04:00
var workingSubDir = Path . Combine ( conf . WorkingDirectory , wo . Item2 . guid . ToString ( ) ) ;
foreach ( var file in Directory . GetFiles ( workingSubDir , "*.*" , SearchOption . AllDirectories ) )
{
File . Delete ( file ) ;
}
//imo this next line should also handle the above, but apparently I'm alone in that.
Directory . Delete ( workingSubDir , true ) ;
2023-04-09 17:01:25 -04:00
break ;
2023-04-07 22:24:31 -04:00
case Phase2Task . TaskStatus . ContinueNow :
remainingWork . Add ( wo . Item2 ) ;
2023-04-09 17:01:25 -04:00
break ;
2023-04-07 22:24:31 -04:00
case Phase2Task . TaskStatus . TryLater :
2023-07-17 16:22:26 -04:00
File . WriteAllText ( wop , JsonConvert . SerializeObject ( wo . Item2 ) ) ;
2023-04-09 17:01:25 -04:00
break ;
2023-04-05 21:05:58 -04:00
}
2023-04-05 02:52:26 -04:00
}
2023-04-07 23:54:09 -04:00
Console . WriteLine ( $"{remainingWork.Count} phase 2 tasks to be re-looped." ) ;
2023-04-05 02:52:26 -04:00
}
2023-04-09 17:01:25 -04:00
#endregion
await ttrssClient . Logout ( ) ;
Console . WriteLine ( $"logged out of ttrss." ) ;
2023-04-05 02:52:26 -04:00
Console . WriteLine ( $"done for real" ) ;
2023-04-02 23:32:03 -04:00
}
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 < Configuration > ( 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 ;
}
}
}