public async Task <ImageCollatorOutputs> FunctionHandler(ImageCollatorInputs input, ILambdaContext context)
        {
            this.context = context;

            Log("Collation: " + input.collation);
            Log("Accounts:  " + string.Join(",", input.accounts));
            Log("Period:    " + input.period);
            Log("Filter:    " + input.filter);
            Log("Group:     " + input.group);

            var collation = EnumHelper.EnsureArgument <Collations>(input.collation, "collation");

            var twitterApiKey            = GetEnv("TWITTER_CONSUMER_KEY");
            var twitterApiKeySecret      = GetEnv("TWITTER_CONSUMER_KEY_SECRET");
            var twitterAccessToken       = GetEnv("TWITTER_ACCESS_TOKEN");
            var twitterAccessTokenSecret = GetEnv("TWITTER_ACCESS_TOKEN_SECRET");
            var githubToken      = GetEnv("GITHUB_TOKEN", collation == Collations.github);
            var githubOwner      = GetEnv("GITHUB_OWNER", collation == Collations.github);
            var githubRepository = GetEnv("GITHUB_REPOSITORY", collation == Collations.github);
            var bucket           = GetEnv("AWS_S3_BUCKET", collation == Collations.s3);

            var filter = EnumHelper.EnsureArgument <Filters>(input.filter, "filter");

            var dates = PeriodHelper.ParsePeriod(input.period);
            var start = dates[0];
            var end   = dates[1];

            var keywords = await KeywordsHelper.FindKeywordsAsync(input.keywords_list, input.keywords_list_url);

            var inspector = new TwitterInspector(
                twitterApiKey,
                twitterApiKeySecret,
                twitterAccessToken,
                twitterAccessTokenSecret,
                filter,
                context.Logger.LogLine,
                keywords);

            ICollator collator = CollatorFactory.Create(collation, Log, input.group, bucket, githubToken, githubOwner, githubRepository);

            collator.Verbose = true;

            var outputs = new ImageCollatorOutputs();

            foreach (var account in input.accounts)
            {
                var medias = await inspector.FilterTimelineAsync(account, start, end);

                var summary = await collator.CollateAsync(medias);

                outputs.summaries += summary.Summaries;
                outputs.files     += summary.Files;
                outputs.errors.AddRange(summary.Errors);
            }

            return(outputs);
        }
        static async Task Main(string[] args)
        {
            var environment     = GetArg(args, 0, "environment").Trim().ToLower();
            var username        = GetArg(args, 1, "username").Trim().ToLower();
            var periodStr       = GetArg(args, 2, "period").Trim().ToLower();
            var filterStr       = GetArg(args, 3, "filter").Trim().ToLower();
            var collationStr    = GetArg(args, 4, "collation").Trim().ToLower();
            var group           = GetArg(args, 5, "group").Trim().ToLower();
            var keywordsList    = new string[0];
            var keywordsListUrl = GetArg(args, 6, "keywords-list-url", false)?.Trim().ToLower();

            // TODO: permit the user to provide a keywords list from a local file if they like

            Console.WriteLine("Environment: " + environment);
            Console.WriteLine("Username:    "******"Filter:      " + filterStr);
            Console.WriteLine("Period:      " + periodStr);

            var dates = PeriodHelper.ParsePeriod(periodStr);
            var start = dates[0];
            var end   = dates[1];

            Console.WriteLine("↳ Start:     " + start.ToShortDateString());
            Console.WriteLine("↳ End:       " + end.ToShortDateString());

            var filter    = EnumHelper.EnsureArgument <Filters>(filterStr, "filter");
            var collation = EnumHelper.EnsureArgument <Collations>(collationStr, "collation");

            var envFile = ".env." + environment;

            DotNetEnv.Env.Load(envFile);
            var twitterApiKey            = GetEnv("TWITTER_CONSUMER_KEY");
            var twitterApiKeySecret      = GetEnv("TWITTER_CONSUMER_KEY_SECRET");
            var twitterAccessToken       = GetEnv("TWITTER_ACCESS_TOKEN");
            var twitterAccessTokenSecret = GetEnv("TWITTER_ACCESS_TOKEN_SECRET");
            var bucket           = GetEnv("AWS_S3_BUCKET", collation == Collations.s3);
            var githubToken      = GetEnv("GITHUB_TOKEN", collation == Collations.github);
            var githubOwner      = GetEnv("GITHUB_OWNER", collation == Collations.github);
            var githubRepository = GetEnv("GITHUB_REPOSITORY", collation == Collations.github);

            var keywords = filter == Filters.keywords ? await KeywordsHelper.FindKeywordsAsync(keywordsList, keywordsListUrl) : null;

            var inspector = new TwitterInspector(
                twitterApiKey,
                twitterApiKeySecret,
                twitterAccessToken,
                twitterAccessTokenSecret,
                filter,
                Console.WriteLine,
                keywords);

            var found = await inspector.FilterTimelineAsync(username, start, end);

            var collator = CollatorFactory.Create(
                collation,
                Console.WriteLine,
                group,
                s3bucket: bucket,
                githubToken: githubToken,
                githubOwner: githubOwner,
                githubRepository: githubRepository);

            collator.Verbose = true;

            var result = await collator.CollateAsync(found);

            Console.WriteLine(string.Format("Summaries: {0}", result.Summaries));
            Console.WriteLine(string.Format("Files:     {0}", result.Files));
            Console.WriteLine(string.Format("Errors:\n{0}", string.Join('\n', result.Errors)));
        }