Example #1
0
        private bool UpdateLastPosted(string groupName, long id)
        {
            if (!LastPostedDictionary.ContainsKey(groupName))
            {
                LastPostedDictionary.AddOrUpdateEx(groupName, 0);
            }

            if (LastPostedDictionary[groupName] == id)
            {
                return(true);
            }
            LastPostedDictionary[groupName] = id;
            return(false);
        }
Example #2
0
        public override async Task Run(object prm)
        {
            if (IsRunning || !APIHelper.IsDiscordAvailable)
            {
                return;
            }
            IsRunning = true;
            try
            {
                if ((DateTime.Now - _lastCheckTime).TotalMinutes < _checkInterval)
                {
                    return;
                }
                _lastCheckTime = DateTime.Now;
                await LogHelper.LogModule("Running Mail module check...", Category);

                foreach (var(groupName, group) in Settings.MailModule.GetEnabledGroups())
                {
                    if (group.DefaultChannel == 0)
                    {
                        continue;
                    }
                    var defaultChannel = group.DefaultChannel;

                    var chars = GetParsedCharacters(groupName) ?? new List <long>();
                    foreach (var charId in chars)
                    {
                        if (charId == 0)
                        {
                            continue;
                        }

                        var rToken = await SQLHelper.GetRefreshTokenMail(charId);

                        if (string.IsNullOrEmpty(rToken))
                        {
                            await SendOneTimeWarning(charId, $"Mail feed token for character {charId} not found! User is not authenticated.");

                            continue;
                        }

                        var tq = await APIHelper.ESIAPI.RefreshToken(rToken, Settings.WebServerModule.CcpAppClientId, Settings.WebServerModule.CcpAppSecret
                                                                     , $"From {Category} | Char ID: {charId}");

                        var token = tq.Result;
                        if (string.IsNullOrEmpty(token))
                        {
                            await LogHelper.LogWarning($"Unable to get contracts token for character {charId}. Refresh token might be outdated or no more valid {tq.Data.ErrorCode}({tq.Data.Message})", Category);

                            continue;
                        }

                        var includePrivate = group.IncludePrivateMail;

                        if (group.Filters.Values.All(a => a.FilterSenders.Count == 0 && a.FilterLabels.Count == 0 && a.FilterMailList.Count == 0))
                        {
                            await LogHelper.LogWarning($"Mail feed for user {charId} has no labels, lists or senders configured!", Category);

                            continue;
                        }

                        var labelsData = await APIHelper.ESIAPI.GetMailLabels(Reason, charId.ToString(), token);

                        var searchLabels = labelsData?.labels.Where(a => a.name?.ToLower() != "sent" && a.name?.ToLower() != "received").ToList() ??
                                           new List <JsonClasses.MailLabel>();
                        var mailLists = await APIHelper.ESIAPI.GetMailLists(Reason, charId, token);

                        var etag   = _tags.GetOrNull(charId);
                        var result = await APIHelper.ESIAPI.GetMailHeaders(Reason, charId.ToString(), token, 0, etag);

                        _tags.AddOrUpdateEx(charId, result.Data.ETag);
                        if (result.Data.IsNotModified)
                        {
                            continue;
                        }

                        var mailsHeaders = result.Result;

                        var lastMailId = await SQLHelper.GetLastMailId(charId);

                        var prevMailId = lastMailId;

                        if (lastMailId > 0)
                        {
                            mailsHeaders = mailsHeaders.Where(a => a.mail_id > lastMailId).OrderBy(a => a.mail_id).ToList();
                        }
                        else
                        {
                            lastMailId = mailsHeaders.OrderBy(a => a.mail_id).LastOrDefault()?.mail_id ?? 0;
                            mailsHeaders.Clear();
                        }

                        foreach (var mailHeader in mailsHeaders)
                        {
                            try
                            {
                                if (mailHeader.mail_id <= lastMailId)
                                {
                                    continue;
                                }
                                lastMailId = mailHeader.mail_id;
                                if (!includePrivate && (mailHeader.recipients.Count(a => a.recipient_id == charId) > 0))
                                {
                                    continue;
                                }

                                foreach (var filter in group.Filters.Values)
                                {
                                    //filter by senders
                                    if (filter.FilterSenders.Count > 0 && !filter.FilterSenders.Contains(mailHeader.from))
                                    {
                                        continue;
                                    }
                                    //filter by labels
                                    var labelIds = searchLabels.Where(a => filter.FilterLabels.Contains(a.name)).Select(a => a.label_id).ToList();
                                    if (labelIds.Count > 0 && !mailHeader.labels.Any(a => labelIds.Contains(a)))
                                    {
                                        continue;
                                    }
                                    //filter by mail lists
                                    var mailListIds = filter.FilterMailList.Count > 0
                                        ? mailLists.Where(a => filter.FilterMailList.Any(b => a.name.Equals(b, StringComparison.OrdinalIgnoreCase))).Select(a => a.mailing_list_id)
                                                      .ToList()
                                        : new List <long>();
                                    if (mailListIds.Count > 0 && !mailHeader.recipients.Where(a => a.recipient_type == "mailing_list")
                                        .Any(a => mailListIds.Contains(a.recipient_id)))
                                    {
                                        continue;
                                    }

                                    var mail = await APIHelper.ESIAPI.GetMail(Reason, charId.ToString(), token, mailHeader.mail_id);

                                    // var labelNames = string.Join(",", mail.labels.Select(a => searchLabels.FirstOrDefault(l => l.label_id == a)?.name)).Trim(',');
                                    var sender = await APIHelper.ESIAPI.GetCharacterData(Reason, mail.from);

                                    var from = sender?.name;
                                    var ml   = mailHeader.recipients.FirstOrDefault(a => a.recipient_type == "mailing_list" && mailListIds.Contains(a.recipient_id));
                                    if (ml != null)
                                    {
                                        from = $"{sender?.name}[{mailLists.First(a => a.mailing_list_id == ml.recipient_id).name}]";
                                    }
                                    var channel = filter.FeedChannel > 0 ? filter.FeedChannel : defaultChannel;
                                    await SendMailNotification(channel, mail, LM.Get("mailMsgTitle", from), group.DefaultMention, filter.DisplayDetailsSummary);

                                    break;
                                }
                            }
                            catch (Exception ex)
                            {
                                await LogHelper.LogEx($"MailCheck: {mailHeader?.mail_id} {mailHeader?.subject}", ex);
                            }
                        }

                        if (prevMailId != lastMailId || lastMailId == 0)
                        {
                            await SQLHelper.UpdateMail(charId, lastMailId);
                        }
                    }
                }

                // await LogHelper.LogModule("Completed", Category);
            }
            catch (Exception ex)
            {
                await LogHelper.LogEx(ex.Message, ex, Category);

                // await LogHelper.LogModule("Completed", Category);
            }
            finally
            {
                IsRunning = false;
            }
        }
Example #3
0
        private async Task ProcessContracts(bool isCorp, ContractNotifyGroup group, long characterID, string token)
        {
            if (group == null)
            {
                return;
            }
            var maxContracts = Settings.ContractNotificationsModule.MaxTrackingCount > 0 ? Settings.ContractNotificationsModule.MaxTrackingCount : 150;
            List <JsonClasses.Contract> contracts;

            var corpID = isCorp ? (await APIHelper.ESIAPI.GetCharacterData(Reason, characterID))?.corporation_id ?? 0 : 0;

            if (isCorp)
            {
                var etag   = _corpEtokens.GetOrNull(characterID);
                var result = await APIHelper.ESIAPI.GetCorpContracts(Reason, corpID, token, etag);

                if (result?.Data == null || result.Data.IsNotModified)
                {
                    return;
                }
                _corpEtokens.AddOrUpdateEx(characterID, result.Data.ETag);

                contracts = result.Result?.OrderByDescending(a => a.contract_id).ToList();
            }
            else
            {
                var etag   = _etokens.GetOrNull(characterID);
                var result = await APIHelper.ESIAPI.GetCharacterContracts(Reason, characterID, token, etag);

                if (result?.Data == null || result.Data.IsNotModified)
                {
                    return;
                }
                _etokens.AddOrUpdateEx(characterID, result.Data.ETag);

                contracts = result.Result?.OrderByDescending(a => a.contract_id).ToList();
            }

            if (contracts == null || !contracts.Any())
            {
                return;
            }

            var lastContractId = contracts.FirstOrDefault()?.contract_id ?? 0;

            if (lastContractId == 0)
            {
                return;
            }

            var lst = !isCorp ? await SQLHelper.LoadContracts(characterID, false) : await SQLHelper.LoadContracts(characterID, true);

            var otherList = isCorp ? await SQLHelper.LoadContracts(characterID, false) : null;

            if (lst == null)
            {
                lst = new List <JsonClasses.Contract>(contracts.Where(a => _activeStatuses.ContainsCaseInsensitive(a.status)).TakeSmart(maxContracts));
                await SQLHelper.SaveContracts(characterID, lst, isCorp);

                return;
            }

            /*
             * if (lst == null)
             * {
             *  var cs = contracts.Where(a => !_completeStatuses.Contains(a.status)).TakeSmart(maxContracts).ToList();
             *  //initial cache - only progressing contracts
             *  await SQLHelper.SaveContracts(characterID, cs, isCorp);
             *  return;
             * }*/

            //process cache
            foreach (var contract in lst.ToList())
            {
                var freshContract = contracts.FirstOrDefault(a => a.contract_id == contract.contract_id);
                //check if it present
                if (freshContract == null)
                {
                    lst.Remove(contract);
                    continue;
                }

                if (group.Filters == null)
                {
                    continue;
                }
                foreach (var filter in group.Filters.Values)
                {
                    if (filter.Types.Any() && !filter.Types.Contains(contract.type))
                    {
                        continue;
                    }

                    if (filter.Availability.Any() && !filter.Availability.ContainsCaseInsensitive(contract.availability))
                    {
                        continue;
                    }

                    //check for completion
                    if (_completeStatuses.Contains(freshContract.status) && filter.Statuses.Contains(freshContract.status))
                    {
                        if (filter.DiscordChannelId > 0 && APIHelper.DiscordAPI.GetChannel(filter.DiscordChannelId) != null)
                        {
                            await PrepareFinishedDiscordMessage(filter.DiscordChannelId, freshContract, group.DefaultMention, isCorp, characterID, corpID, token, filter);
                        }
                        else
                        {
                            await LogHelper.LogWarning($"Specified filter channel ID: {filter.DiscordChannelId} is not accessible!", Category);
                        }
                        await LogHelper.LogModule($"--> Contract {freshContract.contract_id} is {freshContract.status}!", Category);

                        if (lst.Contains(contract))
                        {
                            lst.Remove(contract);
                        }
                        continue;
                    }
                    //check for accepted
                    if (contract.type == "courier" && contract.status == "outstanding" && freshContract.status == "in_progress" && filter.Statuses.Contains("in_progress"))
                    {
                        await PrepareAcceptedDiscordMessage(filter.DiscordChannelId, freshContract, group.DefaultMention, isCorp, characterID, corpID, token, filter);

                        var index = lst.IndexOf(contract);
                        lst.Remove(contract);
                        lst.Insert(index < 0 ? 0 : index, freshContract);
                        await LogHelper.LogModule($"--> Contract {freshContract.contract_id} is accepted!", Category);

                        continue;
                    }
                }
            }


            //silently remove filtered out expired contracts
            var lefties = lst.Where(a => _completeStatuses.Contains(a.status)).ToList();

            foreach (var lefty in lefties)
            {
                lst.Remove(lefty);
            }

            //update cache list and look for new contracts
            var lastRememberedId = lst.FirstOrDefault()?.contract_id ?? 0;

            if (lastContractId > lastRememberedId)
            {
                //get and report new contracts, forget already finished
                var list = contracts.Where(a => a.contract_id > lastRememberedId && !_completeStatuses.Contains(a.status)).ToList();
                if (otherList != null)
                {
                    list = list.Where(a => otherList.All(b => b.contract_id != a.contract_id)).ToList();
                }

                //fix loop
                foreach (var contract in list)
                {
                    var isCharAssignee = await APIHelper.ESIAPI.GetCharacterData(Reason, contract.assignee_id) != null;

                    bool isCorpAssignee = false;
                    bool isAllyAssignee = false;
                    if (!isCharAssignee)
                    {
                        isCorpAssignee = await APIHelper.ESIAPI.GetCorporationData(Reason, contract.assignee_id) != null;

                        isAllyAssignee = !isCorpAssignee;
                    }

                    contract.availability = isCharAssignee ? "personal" : (isCorpAssignee ? "corporation" : "alliance");
                }

                bool stop = false;
                foreach (var contract in list)
                {
                    foreach (var(filterName, filter) in group.Filters)
                    {
                        if (stop)
                        {
                            break;
                        }
                        if (!filter.Statuses.Contains(contract.status))
                        {
                            continue;
                        }
                        //types
                        if (filter.Types.Any() && !filter.Types.Contains(contract.type))
                        {
                            continue;
                        }
                        //availability
                        if (filter.Availability.Any() && !filter.Availability.ContainsCaseInsensitive(contract.availability))
                        {
                            continue;
                        }

                        //filter by issue target
                        if (!filter.FeedIssuedBy)
                        {
                            if (isCorp)
                            {
                                if (contract.for_corporation && contract.issuer_corporation_id == corpID)
                                {
                                    continue;
                                }
                            }
                            else
                            {
                                if (contract.issuer_id == characterID)
                                {
                                    continue;
                                }
                            }
                        }

                        if (!filter.FeedIssuedTo)
                        {
                            if (isCorp)
                            {
                                if (contract.assignee_id == corpID)
                                {
                                    continue;
                                }
                                else if (contract.assignee_id == characterID)
                                {
                                    continue;
                                }
                            }
                        }

                        try
                        {
                            await LogHelper.LogModule($"--> New Contract {contract.contract_id} found!", Category);

                            if (filter.DiscordChannelId != 0)
                            {
                                await PrepareDiscordMessage(filter.DiscordChannelId, contract, group.DefaultMention, isCorp, characterID, corpID, token, filter);
                            }
                            if (group.StopOnFirstFilterMatch)
                            {
                                stop = true;
                            }
                        }
                        catch (Exception ex)
                        {
                            await LogHelper.LogEx($"Contract {contract.contract_id}", ex, Category);
                        }
                    }
                }

                if (list.Count > 0)
                {
                    lst.InsertRange(0, list);
                    //cut
                    if (lst.Count >= maxContracts)
                    {
                        var count = lst.Count - maxContracts;
                        lst.RemoveRange(lst.Count - count, count);
                    }
                }
            }

            //kill dupes
            var rr = lst.GroupBy(a => a.contract_id).Where(a => a.Count() > 1).Select(a => a.Key).Distinct();

            foreach (var item in rr)
            {
                var o = lst.FirstOrDefault(a => a.contract_id == item);
                if (o != null)
                {
                    lst.Remove(o);
                }
            }

            await SQLHelper.SaveContracts(characterID, lst, isCorp);
        }
Example #4
0
        private async Task ProcessIndustryJobs(bool isCorp, IndustrialJobGroup group, long characterID, string token)
        {
            if (group == null)
            {
                return;
            }
            List <JsonClasses.IndustryJob> esiJobs;

            var corpID = isCorp ? (await APIHelper.ESIAPI.GetCharacterData(Reason, characterID))?.corporation_id ?? 0 : 0;

            if (isCorp)
            {
                var etag   = _corpEtokens.GetOrNull(characterID);
                var result = await APIHelper.ESIAPI.GetCorpIndustryJobs(Reason, corpID, token, etag);

                _corpEtokens.AddOrUpdateEx(characterID, result?.Data?.ETag);
                if (result?.Data?.IsNotModified ?? true)
                {
                    return;
                }
                esiJobs = result.Result?.OrderByDescending(a => a.job_id).ToList();
            }
            else
            {
                var etag   = _etokens.GetOrNull(characterID);
                var result = await APIHelper.ESIAPI.GetCharacterIndustryJobs(Reason, characterID, token, etag);

                _etokens.AddOrUpdateEx(characterID, result?.Data?.ETag);
                if (result?.Data?.IsNotModified ?? true)
                {
                    return;
                }
                esiJobs = result.Result?.OrderByDescending(a => a.job_id).ToList();
            }

            if (esiJobs == null || !esiJobs.Any())
            {
                return;
            }

            //ccp bug workaround
            var now = DateTime.UtcNow;

            foreach (var job in esiJobs.Where(a => a.StatusValue == IndustryJobStatusEnum.active && a.end_date < now))
            {
                job.status = "ready";
            }

            var lastJobId = esiJobs.FirstOrDefault()?.job_id ?? 0;

            if (lastJobId == 0)
            {
                return;
            }

            var dbJobs = !isCorp ? await SQLHelper.LoadIndustryJobs(characterID, false) : await SQLHelper.LoadIndustryJobs(characterID, true);

            var dbJobsOther = isCorp ? await SQLHelper.LoadIndustryJobs(characterID, false) : null;//TODO check for sanity

            //check if initial startup
            if (dbJobs == null)
            {
                dbJobs = new List <JsonClasses.IndustryJob>(esiJobs.Where(a => a.StatusValue != IndustryJobStatusEnum.delivered));
                //if (dbJobs.Any())
                // {
                await SQLHelper.SaveIndustryJobs(characterID, dbJobs, isCorp);

                return;
                //}
            }


            // var ready = dbJobs.Where(a => a.StatusValue == IndustryJobStatusEnum.ready);
            // var ready2 = esiJobs.Where(a => a.StatusValue == IndustryJobStatusEnum.ready);
            //var x = dbJobs.FirstOrDefault(a => a.blueprint_type_id == 41607);


            //check db jobs
            foreach (var job in dbJobs.ToList())
            {
                var freshJob = esiJobs.FirstOrDefault(a => a.job_id == job.job_id);
                //check if job is present in esi, delete from db if not
                if (freshJob == null)
                {
                    dbJobs.Remove(job);
                    continue;
                }

                var filters = group.Filters.Count == 0
                    ? new Dictionary <string, IndustryJobFilter> {
                    { "default", new IndustryJobFilter() }
                }
                    : group.Filters;
                foreach (var(filterName, filter) in filters)
                {
                    if (!CheckJobForFilter(filter, job, isCorp))
                    {
                        continue;
                    }

                    //check delivered
                    if (freshJob.StatusValue != job.StatusValue)
                    {
                        await SendDiscordMessage(freshJob, true, filter.DiscordChannels.Any()?filter.DiscordChannels : @group.DiscordChannels, isCorp, token);

                        var index = dbJobs.IndexOf(job);
                        dbJobs.Remove(job);
                        dbJobs.Insert(index, freshJob);
                    }
                }

                //remove delivered job from db
                if (freshJob.StatusValue == IndustryJobStatusEnum.delivered)
                {
                    dbJobs.Remove(job);
                }
            }

            //silently remove filtered out expired contracts
            //probably not needed...
            dbJobs.RemoveAll(a => a.StatusValue == IndustryJobStatusEnum.delivered);

            //update cache list and look for new contracts
            var lastRememberedId = dbJobs?.FirstOrDefault()?.job_id ?? 0;

            if (lastJobId > lastRememberedId)
            {
                //get and report new jobs, forget already finished
                var newJobs = esiJobs.Where(a => a.job_id > lastRememberedId && a.StatusValue != IndustryJobStatusEnum.delivered).ToList();
                if (dbJobsOther != null)
                {
                    newJobs = newJobs.Where(a => dbJobsOther.All(b => b.job_id != a.job_id)).ToList();
                }

                //process new jobs
                foreach (var job in newJobs)
                {
                    foreach (var(filterName, filter) in @group.Filters)
                    {
                        if (!CheckJobForFilter(filter, job, isCorp))
                        {
                            continue;
                        }
                        await SendDiscordMessage(job, false, filter.DiscordChannels.Any()?filter.DiscordChannels : @group.DiscordChannels, isCorp, token);
                    }
                }

                //add new jobs to db list
                if (newJobs.Count > 0)
                {
                    dbJobs.InsertRange(0, newJobs);
                }
            }

            //kill dupes
            var rr = dbJobs.GroupBy(a => a.job_id).Where(a => a.Count() > 1).Select(a => a.Key).Distinct();

            foreach (var item in rr)
            {
                var o = dbJobs.FirstOrDefault(a => a.job_id == item);
                if (o != null)
                {
                    dbJobs.Remove(o);
                }
            }

            await SQLHelper.SaveIndustryJobs(characterID, dbJobs, isCorp);
        }
        public override async Task Run(object prm)
        {
            if (IsRunning)
            {
                return;
            }
            IsRunning = true;

            await ProcessExistingCampaigns();

            try
            {
                if (DateTime.Now <= _nextNotificationCheck)
                {
                    return;
                }
                _nextNotificationCheck = DateTime.Now.AddMinutes(Settings.NullCampaignModule.CheckIntervalInMinutes);

                var etag   = _tags.GetOrNull(0);
                var result = await APIHelper.ESIAPI.GetNullCampaigns(Reason, etag);

                _tags.AddOrUpdateEx(0, result.Data.ETag);
                if (result.Data.IsNotModified)
                {
                    return;
                }

                var allCampaigns = result.Result;
                if (allCampaigns == null)
                {
                    return;
                }
                foreach (var pair in Settings.NullCampaignModule.Groups)
                {
                    var groupName = pair.Key;
                    var group     = pair.Value;
                    var systems   = new List <JsonClasses.SystemName>();

                    foreach (var regionId in @group.Regions)
                    {
                        systems.AddRange(await SQLHelper.GetSystemsByRegion(regionId));
                    }
                    foreach (var cId in @group.Constellations)
                    {
                        systems.AddRange(await SQLHelper.GetSystemsByConstellation(cId));
                    }

                    var systemIds = systems.Select(a => a.system_id);
                    var campaigns = allCampaigns.Where(a => systemIds.Contains(a.solar_system_id));
                    var existIds  = await SQLHelper.GetNullsecCampaignIdList(groupName);

                    campaigns = campaigns.Where(a => !existIds.Contains(a.campaign_id));

                    foreach (var campaign in campaigns)
                    {
                        if (await SQLHelper.IsNullsecCampaignExists(groupName, campaign.campaign_id))
                        {
                            continue;
                        }

                        var startTime    = campaign.Time;
                        var totalMinutes = DateTime.UtcNow >= startTime ? 0 : (int)(startTime - DateTime.UtcNow).TotalMinutes;
                        if (totalMinutes == 0)
                        {
                            continue;
                        }

                        await SQLHelper.UpdateNullCampaign(groupName, campaign.campaign_id, startTime, campaign.ToJson());

                        if (group.ReportNewCampaign)
                        {
                            await PrepareMessage(campaign, pair.Value, LM.Get("NC_NewCampaign"), 0x00FF00);
                        }

                        await LogHelper.LogInfo($"Nullsec Campaign {campaign.campaign_id} has been registered! [{groupName} - {campaign.campaign_id}]", Category, true, false);
                    }
                }
            }
            catch (Exception ex)
            {
                await LogHelper.LogEx(ex.Message, ex, Category);
            }
            finally
            {
                IsRunning = false;
            }
        }
        private async Task ProcessContracts(bool isCorp, ContractNotifyGroup group, long characterID, string token)
        {
            var maxContracts = Settings.ContractNotificationsModule.MaxTrackingCount > 0 ? Settings.ContractNotificationsModule.MaxTrackingCount : 150;
            List <JsonClasses.Contract> contracts;

            var corpID = isCorp ? (await APIHelper.ESIAPI.GetCharacterData(Reason, characterID))?.corporation_id ?? 0 : 0;

            if (isCorp)
            {
                var etag   = _corpEtokens.GetOrNull(characterID);
                var result = await APIHelper.ESIAPI.GetCorpContracts(Reason, corpID, token, etag);

                _corpEtokens.AddOrUpdateEx(characterID, result.Data.ETag);
                if (result.Data.IsNotModified)
                {
                    return;
                }
                contracts = result.Result?.OrderByDescending(a => a.contract_id).ToList();
            }
            else
            {
                var etag   = _etokens.GetOrNull(characterID);
                var result = await APIHelper.ESIAPI.GetCharacterContracts(Reason, characterID, token, etag);

                _etokens.AddOrUpdateEx(characterID, result.Data.ETag);
                if (result.Data.IsNotModified)
                {
                    return;
                }

                contracts = result.Result?.OrderByDescending(a => a.contract_id).ToList();
            }

            if (contracts == null || !contracts.Any())
            {
                return;
            }

            var lastContractId = contracts.FirstOrDefault()?.contract_id ?? 0;

            if (lastContractId == 0)
            {
                return;
            }

            var lst = !isCorp ? await SQLHelper.LoadContracts(characterID, false) : await SQLHelper.LoadContracts(characterID, true);

            var otherList = isCorp ? await SQLHelper.LoadContracts(characterID, false) : null;


            if (lst == null)
            {
                var cs = contracts.Where(a => !_completeStatuses.Contains(a.status)).TakeSmart(maxContracts).ToList();
                //initial cache - only progressing contracts
                await SQLHelper.SaveContracts(characterID, cs, isCorp);

                return;
            }

            //process cache
            foreach (var contract in lst.ToList())
            {
                var freshContract = contracts.FirstOrDefault(a => a.contract_id == contract.contract_id);
                //check if it present
                if (freshContract == null)
                {
                    lst.Remove(contract);
                    continue;
                }

                foreach (var filter in group.Filters.Values)
                {
                    if (filter.Types.Any() && !filter.Types.Contains(contract.type))
                    {
                        continue;
                    }

                    //check for completion
                    if (_completeStatuses.Contains(freshContract.status) && filter.Statuses.Contains(freshContract.status))
                    {
                        await PrepareFinishedDiscordMessage(filter.DiscordChannelId, freshContract, group.DefaultMention, isCorp, characterID, corpID, token, filter.ShowIngameOpen);

                        await LogHelper.LogModule($"--> Contract {freshContract.contract_id} is expired!", Category);

                        lst.Remove(contract);
                        continue;
                    }
                    //check for accepted
                    if (contract.type == "courier" && contract.status == "outstanding" && freshContract.status == "in_progress" && filter.Statuses.Contains("in_progress"))
                    {
                        await PrepareAcceptedDiscordMessage(filter.DiscordChannelId, freshContract, group.DefaultMention, isCorp, characterID, corpID, token, filter.ShowIngameOpen);

                        var index = lst.IndexOf(contract);
                        lst.Remove(contract);
                        lst.Insert(index, freshContract);
                        await LogHelper.LogModule($"--> Contract {freshContract.contract_id} is accepted!", Category);

                        continue;
                    }
                }

                //silently remove filtered out expired contracts
                var lefties = lst.Where(a => _completeStatuses.Contains(a.status)).ToList();
                foreach (var lefty in lefties)
                {
                    lst.Remove(lefty);
                }
            }

            //update cache list and look for new contracts
            var lastRememberedId = lst.FirstOrDefault()?.contract_id ?? 0;

            if (lastContractId > lastRememberedId)
            {
                //get and report new contracts, forget already finished
                var list = contracts.Where(a => a.contract_id > lastRememberedId && !_completeStatuses.Contains(a.status)).ToList();
                if (otherList != null)
                {
                    list = list.Where(a => otherList.All(b => b.contract_id != a.contract_id)).ToList();
                }

                var crFilter        = group.Filters.Values.FirstOrDefault(a => a.Statuses.Contains("outstanding"));
                var crFilterChannel = crFilter?.DiscordChannelId ?? 0;

                //filter by issue target
                if (!crFilter?.FeedIssuedBy ?? false)
                {
                    list = list.Where(a => a.issuer_id != characterID && (a.issuer_corporation_id != corpID || a.issuer_corporation_id == 0)).ToList();
                }
                if (!crFilter?.FeedIssuedTo ?? false)
                {
                    list = list.Where(a => a.assignee_id != characterID && a.assignee_id != corpID).ToList();
                }

                if (crFilter != null && crFilter.Types.Any())
                {
                    list = list.Where(a => crFilter.Types.Contains(a.type)).ToList();
                }

                foreach (var contract in list)
                {
                    try
                    {
                        await LogHelper.LogModule($"--> New Contract {contract.contract_id} found!", Category);

                        if (crFilterChannel != 0)
                        {
                            await PrepareDiscordMessage(crFilterChannel, contract, group.DefaultMention, isCorp, characterID, corpID, token, crFilter.ShowIngameOpen);
                        }
                    }
                    catch (Exception ex)
                    {
                        await LogHelper.LogEx($"Contract {contract.contract_id}", ex, Category);
                    }
                }

                if (list.Count > 0)
                {
                    lst.InsertRange(0, list);
                    //cut
                    if (lst.Count >= maxContracts)
                    {
                        var count = lst.Count - maxContracts;
                        lst.RemoveRange(lst.Count - count, count);
                    }
                }
            }

            await SQLHelper.SaveContracts(characterID, lst, isCorp);
        }