@omnizap-system/omnizap 2.6.1 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +54 -9
- package/.github/workflows/ci.yml +3 -3
- package/.github/workflows/security-runner-hardening.yml +1 -1
- package/.github/workflows/security-zap-full-scan.yml +1 -0
- package/app/config/index.js +2 -0
- package/app/configParts/adminIdentity.js +5 -5
- package/app/configParts/baileysConfig.js +226 -55
- package/app/configParts/groupUtils.js +5 -0
- package/app/configParts/messagePersistenceService.js +143 -3
- package/app/configParts/sessionConfig.js +157 -0
- package/app/connection/baileysCompatibility.test.js +1 -1
- package/app/connection/groupOwnerWriteStateResolver.js +109 -0
- package/app/connection/socketController.js +625 -124
- package/app/connection/socketController.multiSession.test.js +108 -0
- package/app/controllers/messageController.js +1 -1
- package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
- package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
- package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
- package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
- package/app/controllers/messageProcessingPipeline.js +88 -9
- package/app/controllers/messageProcessingPipeline.test.js +200 -0
- package/app/modules/adminModule/AGENT.md +1 -1
- package/app/modules/adminModule/commandConfig.json +3318 -1347
- package/app/modules/adminModule/groupCommandHandlers.js +856 -14
- package/app/modules/adminModule/groupCommandHandlers.test.js +375 -9
- package/app/modules/adminModule/groupWarningRepository.js +152 -0
- package/app/modules/aiModule/AGENT.md +47 -30
- package/app/modules/aiModule/aiConfigRuntime.js +1 -0
- package/app/modules/aiModule/catCommand.js +132 -25
- package/app/modules/aiModule/commandConfig.json +114 -28
- package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
- package/app/modules/gameModule/AGENT.md +1 -1
- package/app/modules/gameModule/commandConfig.json +29 -0
- package/app/modules/menuModule/AGENT.md +1 -1
- package/app/modules/menuModule/commandConfig.json +45 -10
- package/app/modules/menuModule/menuCatalogService.js +190 -0
- package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
- package/app/modules/menuModule/menuDynamicService.js +511 -0
- package/app/modules/menuModule/menuDynamicService.test.js +141 -0
- package/app/modules/menuModule/menus.js +36 -5
- package/app/modules/playModule/AGENT.md +10 -5
- package/app/modules/playModule/commandConfig.json +74 -16
- package/app/modules/playModule/playCommandConstants.js +13 -7
- package/app/modules/playModule/playCommandCore.js +4 -6
- package/app/modules/playModule/{playCommandYtDlpClient.js → playCommandMediaClient.js} +684 -332
- package/app/modules/playModule/playConfigRuntime.js +5 -6
- package/app/modules/playModule/playModuleCriticalFlows.test.js +44 -59
- package/app/modules/quoteModule/AGENT.md +1 -1
- package/app/modules/quoteModule/commandConfig.json +29 -0
- package/app/modules/rpgPokemonModule/AGENT.md +1 -1
- package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
- package/app/modules/statsModule/AGENT.md +1 -1
- package/app/modules/statsModule/commandConfig.json +58 -0
- package/app/modules/stickerModule/AGENT.md +1 -1
- package/app/modules/stickerModule/commandConfig.json +145 -0
- package/app/modules/stickerPackModule/AGENT.md +1 -1
- package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
- package/app/modules/stickerPackModule/commandConfig.json +29 -0
- package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +1 -1
- package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +78 -57
- package/app/modules/stickerPackModule/stickerPackService.js +13 -6
- package/app/modules/systemMetricsModule/AGENT.md +1 -1
- package/app/modules/systemMetricsModule/commandConfig.json +29 -0
- package/app/modules/tiktokModule/AGENT.md +1 -1
- package/app/modules/tiktokModule/commandConfig.json +29 -0
- package/app/modules/userModule/AGENT.md +1 -1
- package/app/modules/userModule/commandConfig.json +29 -0
- package/app/modules/waifuPicsModule/AGENT.md +57 -27
- package/app/modules/waifuPicsModule/commandConfig.json +87 -0
- package/app/observability/metrics.js +136 -0
- package/app/services/ai/commandConfigEnrichmentService.js +229 -47
- package/app/services/ai/geminiService.js +131 -7
- package/app/services/ai/geminiService.test.js +59 -2
- package/app/services/ai/moduleAiHelpCoreService.js +33 -4
- package/app/services/group/groupMetadataService.js +24 -1
- package/app/services/infra/dbWriteQueue.js +51 -21
- package/app/services/messaging/newsBroadcastService.js +843 -27
- package/app/services/multiSession/assignmentBalancerService.js +457 -0
- package/app/services/multiSession/groupOwnershipRepository.js +381 -0
- package/app/services/multiSession/groupOwnershipService.js +890 -0
- package/app/services/multiSession/groupOwnershipService.test.js +309 -0
- package/app/services/multiSession/sessionRegistryService.js +293 -0
- package/app/store/aiPromptStore.js +36 -19
- package/app/store/groupConfigStore.js +41 -5
- package/app/store/premiumUserStore.js +21 -7
- package/app/utils/antiLink/antiLinkModule.js +352 -16
- package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
- package/database/index.js +6 -0
- package/database/migrations/20260307_d0_hardening_down.sql +1 -1
- package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
- package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
- package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
- package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
- package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
- package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
- package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
- package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
- package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
- package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
- package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
- package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
- package/database/schema.sql +102 -1
- package/docker-compose.yml +4 -1
- package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
- package/docs/compliance/privacy-policy-2026-03-07.md +2 -2
- package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
- package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
- package/docs/security/omnizap-static-security-headers.conf +25 -0
- package/ecosystem.prod.config.cjs +31 -11
- package/index.js +52 -18
- package/observability/alert-rules.yml +20 -0
- package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
- package/observability/mysql-setup.sql +4 -4
- package/observability/system-admin-observability.md +26 -0
- package/package.json +12 -5
- package/public/comandos/commands-catalog.json +2253 -78
- package/public/js/apps/commandsReactApp.js +267 -87
- package/public/js/apps/createPackApp.js +3 -3
- package/public/js/apps/stickersApp.js +255 -103
- package/public/js/apps/termsReactApp.js +57 -8
- package/public/js/apps/userPasswordResetReactApp.js +406 -0
- package/public/js/apps/userReactApp.js +96 -47
- package/public/js/apps/userSystemAdmReactApp.js +1506 -0
- package/public/pages/politica-de-privacidade.html +1 -1
- package/public/pages/stickers.html +5 -5
- package/public/pages/termos-de-uso-texto-integral.html +1 -1
- package/public/pages/termos-de-uso.html +1 -1
- package/public/pages/user-password-reset.html +3 -4
- package/public/pages/user-systemadm.html +8 -462
- package/public/pages/user.html +1 -1
- package/scripts/clear-whatsapp-session.sh +123 -0
- package/scripts/core-ai-mode.mjs +163 -0
- package/scripts/deploy.sh +10 -0
- package/scripts/enrich-command-config-ux-openai.mjs +492 -0
- package/scripts/generate-commands-catalog.mjs +155 -0
- package/scripts/new-whatsapp-session.sh +317 -0
- package/scripts/security-web-surface-check.mjs +218 -0
- package/server/controllers/admin/adminPanelHandlers.js +253 -3
- package/server/controllers/admin/systemAdminController.js +267 -0
- package/server/controllers/sticker/stickerCatalogController.js +9 -23
- package/server/controllers/system/contactController.js +9 -17
- package/server/controllers/system/stickerCatalogSystemContext.js +27 -6
- package/server/controllers/system/systemController.js +254 -1
- package/server/controllers/userController.js +6 -0
- package/server/email/emailTemplateService.js +3 -2
- package/server/http/httpServer.js +8 -4
- package/server/middleware/securityHeaders.js +20 -1
- package/server/routes/admin/systemAdminRouter.js +6 -0
- package/server/routes/indexRouter.js +30 -6
- package/server/routes/observability/grafanaProxyRouter.js +254 -0
- package/server/routes/static/staticPageRouter.js +27 -1
- package/server/utils/publicContact.js +31 -0
- package/utils/whatsapp/contactEnv.js +39 -0
- package/vite.config.mjs +2 -1
- package/app/modules/playModule/local/installYtDlp.js +0 -25
- package/app/modules/playModule/local/ytDlpInstaller.js +0 -28
|
@@ -8,7 +8,46 @@ import getImageBuffer from '../../utils/http/getImageBufferModule.js';
|
|
|
8
8
|
import { sendAndStore } from './messagePersistenceService.js';
|
|
9
9
|
|
|
10
10
|
const DEFAULT_NEWS_API_URL = 'http://127.0.0.1:3001';
|
|
11
|
+
const DEFAULT_NEWS_API_ARTICLES_PATH = '/articles';
|
|
12
|
+
const DEFAULT_NEWS_API_ARTICLE_BY_ID_PATH = '/articles/:id';
|
|
13
|
+
const DEFAULT_NEWS_API_ARTICLE_BY_SLUG_PATH = '/articles/slug/:slug';
|
|
14
|
+
const DEFAULT_NEWS_API_TRENDS_PATH = '/trends';
|
|
15
|
+
const DEFAULT_NEWS_API_FRANCHISES_PATH = '/franchises';
|
|
16
|
+
const DEFAULT_NEWS_API_FRANCHISE_BY_SLUG_PATH = '/franchises/:slug';
|
|
17
|
+
const DEFAULT_NEWS_API_SOURCES_PATH = '/sources';
|
|
18
|
+
const DEFAULT_NEWS_API_SOURCE_BY_ID_PATH = '/sources/:sourceId';
|
|
19
|
+
const DEFAULT_NEWS_API_SEO_ENTITIES_PATH = '/seo/entities';
|
|
20
|
+
const DEFAULT_NEWS_API_SEO_BY_TYPE_SLUG_PATH = '/seo/:type/:slug';
|
|
11
21
|
const NEWS_API_URL = (process.env.NEWS_API_URL || DEFAULT_NEWS_API_URL).replace(/\/+$/, '');
|
|
22
|
+
const NEWS_API_ARTICLES_PATH = String(process.env.NEWS_API_ARTICLES_PATH || DEFAULT_NEWS_API_ARTICLES_PATH).trim() || DEFAULT_NEWS_API_ARTICLES_PATH;
|
|
23
|
+
const NEWS_API_ARTICLE_BY_ID_PATH = String(process.env.NEWS_API_ARTICLE_BY_ID_PATH || DEFAULT_NEWS_API_ARTICLE_BY_ID_PATH).trim() || DEFAULT_NEWS_API_ARTICLE_BY_ID_PATH;
|
|
24
|
+
const NEWS_API_ARTICLE_BY_SLUG_PATH = String(process.env.NEWS_API_ARTICLE_BY_SLUG_PATH || DEFAULT_NEWS_API_ARTICLE_BY_SLUG_PATH).trim() || DEFAULT_NEWS_API_ARTICLE_BY_SLUG_PATH;
|
|
25
|
+
const NEWS_API_TRENDS_PATH = String(process.env.NEWS_API_TRENDS_PATH || DEFAULT_NEWS_API_TRENDS_PATH).trim() || DEFAULT_NEWS_API_TRENDS_PATH;
|
|
26
|
+
const NEWS_API_FRANCHISES_PATH = String(process.env.NEWS_API_FRANCHISES_PATH || DEFAULT_NEWS_API_FRANCHISES_PATH).trim() || DEFAULT_NEWS_API_FRANCHISES_PATH;
|
|
27
|
+
const NEWS_API_FRANCHISE_BY_SLUG_PATH = String(process.env.NEWS_API_FRANCHISE_BY_SLUG_PATH || DEFAULT_NEWS_API_FRANCHISE_BY_SLUG_PATH).trim() || DEFAULT_NEWS_API_FRANCHISE_BY_SLUG_PATH;
|
|
28
|
+
const NEWS_API_SOURCES_PATH = String(process.env.NEWS_API_SOURCES_PATH || DEFAULT_NEWS_API_SOURCES_PATH).trim() || DEFAULT_NEWS_API_SOURCES_PATH;
|
|
29
|
+
const NEWS_API_SOURCE_BY_ID_PATH = String(process.env.NEWS_API_SOURCE_BY_ID_PATH || DEFAULT_NEWS_API_SOURCE_BY_ID_PATH).trim() || DEFAULT_NEWS_API_SOURCE_BY_ID_PATH;
|
|
30
|
+
const NEWS_API_SEO_ENTITIES_PATH = String(process.env.NEWS_API_SEO_ENTITIES_PATH || DEFAULT_NEWS_API_SEO_ENTITIES_PATH).trim() || DEFAULT_NEWS_API_SEO_ENTITIES_PATH;
|
|
31
|
+
const NEWS_API_SEO_BY_TYPE_SLUG_PATH = String(process.env.NEWS_API_SEO_BY_TYPE_SLUG_PATH || DEFAULT_NEWS_API_SEO_BY_TYPE_SLUG_PATH).trim() || DEFAULT_NEWS_API_SEO_BY_TYPE_SLUG_PATH;
|
|
32
|
+
const NEWS_API_LIMIT = Math.max(1, Math.min(500, Number(process.env.NEWS_API_LIMIT) || 120));
|
|
33
|
+
const NEWS_API_TIMEOUT_MS = Math.max(1000, Number(process.env.NEWS_API_TIMEOUT_MS) || 15000);
|
|
34
|
+
const NEWS_API_DETAILS_TIMEOUT_MS = Math.max(1000, Number(process.env.NEWS_API_DETAILS_TIMEOUT_MS) || NEWS_API_TIMEOUT_MS);
|
|
35
|
+
const NEWS_API_CONTEXT_TTL_MS = Math.max(30_000, Number(process.env.NEWS_API_CONTEXT_TTL_MS) || 180_000);
|
|
36
|
+
const NEWS_API_DETAILS_CACHE_TTL_MS = Math.max(60_000, Number(process.env.NEWS_API_DETAILS_CACHE_TTL_MS) || NEWS_API_CONTEXT_TTL_MS * 2);
|
|
37
|
+
const NEWS_API_CONTEXT_TOP = Math.max(5, Math.min(200, Number(process.env.NEWS_API_CONTEXT_TOP) || 40));
|
|
38
|
+
const NEWS_SMART_SELECTION_WINDOW = Math.max(5, Math.min(200, Number(process.env.NEWS_SMART_SELECTION_WINDOW) || 80));
|
|
39
|
+
const NEWS_SMART_SELECTION_ENABLED =
|
|
40
|
+
String(process.env.NEWS_SMART_SELECTION_ENABLED || 'true')
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase() !== 'false';
|
|
43
|
+
const NEWS_CAPTION_CONTEXT_ENABLED =
|
|
44
|
+
String(process.env.NEWS_CAPTION_CONTEXT_ENABLED || 'true')
|
|
45
|
+
.trim()
|
|
46
|
+
.toLowerCase() !== 'false';
|
|
47
|
+
const NEWS_API_LEGACY_FALLBACK =
|
|
48
|
+
String(process.env.NEWS_API_LEGACY_FALLBACK || 'true')
|
|
49
|
+
.trim()
|
|
50
|
+
.toLowerCase() !== 'false';
|
|
12
51
|
const MIN_DELAY_MS = 60 * 1000;
|
|
13
52
|
const MAX_DELAY_MS = 120 * 1000;
|
|
14
53
|
const MAX_SENT_IDS = Number(process.env.NEWS_SENT_IDS_LIMIT || 500);
|
|
@@ -16,6 +55,15 @@ const LOOP_START_DELAY_MS = 5000;
|
|
|
16
55
|
const GROUP_UNAVAILABLE_ERROR_PATTERNS = ['item-not-found', 'not-authorized', 'not in group', 'group does not exist', 'recipient not found', 'recipient-unavailable'];
|
|
17
56
|
|
|
18
57
|
const groupLoops = new Map();
|
|
58
|
+
const newsContextState = {
|
|
59
|
+
data: null,
|
|
60
|
+
expiresAt: 0,
|
|
61
|
+
inFlight: null,
|
|
62
|
+
};
|
|
63
|
+
const articleDetailsCache = new Map();
|
|
64
|
+
const sourceDetailsCache = new Map();
|
|
65
|
+
const franchiseDetailsCache = new Map();
|
|
66
|
+
const seoDetailsCache = new Map();
|
|
19
67
|
|
|
20
68
|
const getRandomDelayMs = () => {
|
|
21
69
|
const min = MIN_DELAY_MS;
|
|
@@ -45,6 +93,271 @@ const parseConfigValue = (value) => {
|
|
|
45
93
|
return {};
|
|
46
94
|
};
|
|
47
95
|
|
|
96
|
+
const resolveNewsApiRequestUrl = (baseUrl, pathValue = '') => {
|
|
97
|
+
const normalizedBase = String(baseUrl || '')
|
|
98
|
+
.trim()
|
|
99
|
+
.replace(/\/+$/, '');
|
|
100
|
+
const normalizedPath = String(pathValue || '').trim();
|
|
101
|
+
|
|
102
|
+
if (!normalizedPath) return normalizedBase;
|
|
103
|
+
if (/^https?:\/\//i.test(normalizedPath)) return normalizedPath;
|
|
104
|
+
if (!normalizedBase) return normalizedPath;
|
|
105
|
+
if (normalizedPath.startsWith('/')) return `${normalizedBase}${normalizedPath}`;
|
|
106
|
+
return `${normalizedBase}/${normalizedPath}`;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const resolvePathTemplate = (template, params = {}) => {
|
|
110
|
+
let resolved = String(template || '').trim();
|
|
111
|
+
if (!resolved) return '';
|
|
112
|
+
|
|
113
|
+
for (const [key, rawValue] of Object.entries(params || {})) {
|
|
114
|
+
const value = String(rawValue ?? '').trim();
|
|
115
|
+
if (!value) continue;
|
|
116
|
+
resolved = resolved.replace(new RegExp(`:${key}\\b`, 'g'), encodeURIComponent(value));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return resolved;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const toFiniteNumber = (value, fallback = 0) => {
|
|
123
|
+
const numeric = Number(value);
|
|
124
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const normalizeToken = (value) =>
|
|
128
|
+
String(value || '')
|
|
129
|
+
.trim()
|
|
130
|
+
.toLowerCase();
|
|
131
|
+
|
|
132
|
+
const normalizeStringList = (value) => {
|
|
133
|
+
const source = Array.isArray(value) ? value : typeof value === 'string' ? value.split(',') : [];
|
|
134
|
+
return source.map((entry) => normalizeToken(entry)).filter(Boolean);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const toArrayItems = (data) => {
|
|
138
|
+
if (Array.isArray(data)) return data;
|
|
139
|
+
if (Array.isArray(data?.items)) return data.items;
|
|
140
|
+
if (Array.isArray(data?.data?.items)) return data.data.items;
|
|
141
|
+
if (Array.isArray(data?.data)) return data.data;
|
|
142
|
+
return [];
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const getCachedEntry = (cache, key) => {
|
|
146
|
+
if (!(cache instanceof Map)) return null;
|
|
147
|
+
const entry = cache.get(key);
|
|
148
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
149
|
+
const expiresAt = Number(entry.expiresAt || 0);
|
|
150
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= __timeNowMs()) {
|
|
151
|
+
cache.delete(key);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return entry.value;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const setCachedEntry = (cache, key, value, ttlMs = NEWS_API_DETAILS_CACHE_TTL_MS) => {
|
|
158
|
+
if (!(cache instanceof Map) || !key) return;
|
|
159
|
+
cache.set(key, {
|
|
160
|
+
value,
|
|
161
|
+
expiresAt: __timeNowMs() + Math.max(1_000, Number(ttlMs) || NEWS_API_DETAILS_CACHE_TTL_MS),
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const requestNewsApi = async ({ path, timeoutMs = NEWS_API_TIMEOUT_MS, params = undefined }) => {
|
|
166
|
+
const requestUrl = resolveNewsApiRequestUrl(NEWS_API_URL, path);
|
|
167
|
+
const response = await axios.get(requestUrl, {
|
|
168
|
+
timeout: timeoutMs,
|
|
169
|
+
params,
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
data: response?.data,
|
|
173
|
+
url: requestUrl,
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const requestNewsApiOptional = async ({ path, timeoutMs = NEWS_API_TIMEOUT_MS, params = undefined, label = 'news_api_optional' }) => {
|
|
178
|
+
if (!path) return null;
|
|
179
|
+
try {
|
|
180
|
+
return await requestNewsApi({ path, timeoutMs, params });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger.warn('Falha ao consultar endpoint auxiliar da API de noticias.', {
|
|
183
|
+
label,
|
|
184
|
+
path,
|
|
185
|
+
error: error.message,
|
|
186
|
+
});
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const buildEmptyNewsContext = () => ({
|
|
192
|
+
generatedAt: __timeNowIso(),
|
|
193
|
+
trendingFranchiseSlugs: new Set(),
|
|
194
|
+
franchiseStatsBySlug: new Map(),
|
|
195
|
+
sourceStatsById: new Map(),
|
|
196
|
+
seoEntitySlugsByType: new Map(),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const parseFranchiseEntries = (payload) => {
|
|
200
|
+
const entries = [];
|
|
201
|
+
const addEntries = (value) => {
|
|
202
|
+
if (!Array.isArray(value)) return;
|
|
203
|
+
value.forEach((entry) => {
|
|
204
|
+
if (!entry || typeof entry !== 'object') return;
|
|
205
|
+
entries.push(entry);
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
addEntries(payload?.topFranchises);
|
|
210
|
+
addEntries(payload?.ranking?.byMentions);
|
|
211
|
+
addEntries(payload?.ranking?.byTrend);
|
|
212
|
+
addEntries(toArrayItems(payload));
|
|
213
|
+
|
|
214
|
+
return entries
|
|
215
|
+
.map((entry) => {
|
|
216
|
+
const slug = normalizeToken(entry.slug || entry.franchiseSlug || entry.name);
|
|
217
|
+
if (!slug) return null;
|
|
218
|
+
return {
|
|
219
|
+
slug,
|
|
220
|
+
name: String(entry.name || '').trim(),
|
|
221
|
+
mentions: toFiniteNumber(entry.mentions || entry.count || entry.total, 0),
|
|
222
|
+
maxTrendScore: toFiniteNumber(entry.maxTrendScore || entry.trendScore, 0),
|
|
223
|
+
avgScore: toFiniteNumber(entry.avgScore || entry.score, 0),
|
|
224
|
+
};
|
|
225
|
+
})
|
|
226
|
+
.filter(Boolean);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const parseSourceEntries = (payload) => {
|
|
230
|
+
return toArrayItems(payload)
|
|
231
|
+
.filter((entry) => entry && typeof entry === 'object')
|
|
232
|
+
.map((entry) => {
|
|
233
|
+
const stats = entry.stats && typeof entry.stats === 'object' ? entry.stats : {};
|
|
234
|
+
const sourceId = normalizeToken(entry.id || entry.sourceId || entry.source?.id);
|
|
235
|
+
if (!sourceId) return null;
|
|
236
|
+
return {
|
|
237
|
+
id: sourceId,
|
|
238
|
+
name: String(entry.name || entry.sourceName || entry.source?.name || '').trim(),
|
|
239
|
+
avgScore: toFiniteNumber(entry.avgScore || stats.avgScore, 0),
|
|
240
|
+
count: toFiniteNumber(entry.count || stats.count, 0),
|
|
241
|
+
newCount: toFiniteNumber(entry.newCount || stats.newCount || stats.lifecycle?.new, 0),
|
|
242
|
+
};
|
|
243
|
+
})
|
|
244
|
+
.filter(Boolean);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const parseSeoEntitySets = (payload) => {
|
|
248
|
+
const byType = new Map();
|
|
249
|
+
const items = payload?.items && typeof payload.items === 'object' ? payload.items : {};
|
|
250
|
+
|
|
251
|
+
for (const [rawType, rawEntries] of Object.entries(items)) {
|
|
252
|
+
const type = normalizeToken(rawType);
|
|
253
|
+
if (!type || !Array.isArray(rawEntries)) continue;
|
|
254
|
+
|
|
255
|
+
const slugs = new Set();
|
|
256
|
+
rawEntries.slice(0, NEWS_API_CONTEXT_TOP).forEach((entry) => {
|
|
257
|
+
if (!entry || typeof entry !== 'object') return;
|
|
258
|
+
const slug = normalizeToken(entry.slug || entry.name);
|
|
259
|
+
if (slug) {
|
|
260
|
+
slugs.add(slug);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (slugs.size) {
|
|
265
|
+
byType.set(type, slugs);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return byType;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const buildNewsContextFromPayloads = ({ trendsPayload, franchisesPayload, sourcesPayload, seoPayload }) => {
|
|
273
|
+
const context = buildEmptyNewsContext();
|
|
274
|
+
const franchiseEntries = [...parseFranchiseEntries(trendsPayload), ...parseFranchiseEntries(franchisesPayload)];
|
|
275
|
+
const sourceEntries = parseSourceEntries(sourcesPayload);
|
|
276
|
+
const seoEntitySets = parseSeoEntitySets(seoPayload);
|
|
277
|
+
|
|
278
|
+
for (const entry of franchiseEntries) {
|
|
279
|
+
const existing = context.franchiseStatsBySlug.get(entry.slug);
|
|
280
|
+
if (!existing) {
|
|
281
|
+
context.franchiseStatsBySlug.set(entry.slug, { ...entry });
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
context.franchiseStatsBySlug.set(entry.slug, {
|
|
285
|
+
slug: entry.slug,
|
|
286
|
+
name: entry.name || existing.name || '',
|
|
287
|
+
mentions: Math.max(existing.mentions, entry.mentions),
|
|
288
|
+
maxTrendScore: Math.max(existing.maxTrendScore, entry.maxTrendScore),
|
|
289
|
+
avgScore: Math.max(existing.avgScore, entry.avgScore),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const topFranchises = Array.from(context.franchiseStatsBySlug.values())
|
|
294
|
+
.sort((a, b) => {
|
|
295
|
+
if (b.mentions !== a.mentions) return b.mentions - a.mentions;
|
|
296
|
+
if (b.maxTrendScore !== a.maxTrendScore) return b.maxTrendScore - a.maxTrendScore;
|
|
297
|
+
return b.avgScore - a.avgScore;
|
|
298
|
+
})
|
|
299
|
+
.slice(0, NEWS_API_CONTEXT_TOP);
|
|
300
|
+
topFranchises.forEach((entry) => context.trendingFranchiseSlugs.add(entry.slug));
|
|
301
|
+
|
|
302
|
+
for (const entry of sourceEntries) {
|
|
303
|
+
const existing = context.sourceStatsById.get(entry.id);
|
|
304
|
+
if (!existing) {
|
|
305
|
+
context.sourceStatsById.set(entry.id, { ...entry });
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
context.sourceStatsById.set(entry.id, {
|
|
309
|
+
id: entry.id,
|
|
310
|
+
name: entry.name || existing.name || '',
|
|
311
|
+
avgScore: Math.max(existing.avgScore, entry.avgScore),
|
|
312
|
+
count: Math.max(existing.count, entry.count),
|
|
313
|
+
newCount: Math.max(existing.newCount, entry.newCount),
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
context.seoEntitySlugsByType = seoEntitySets;
|
|
318
|
+
context.generatedAt = __timeNowIso();
|
|
319
|
+
return context;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const getNewsContext = async () => {
|
|
323
|
+
const now = __timeNowMs();
|
|
324
|
+
if (newsContextState.data && newsContextState.expiresAt > now) {
|
|
325
|
+
return newsContextState.data;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (newsContextState.inFlight) {
|
|
329
|
+
return newsContextState.inFlight;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const staleContext = newsContextState.data || buildEmptyNewsContext();
|
|
333
|
+
|
|
334
|
+
newsContextState.inFlight = (async () => {
|
|
335
|
+
const [trendsResponse, franchisesResponse, sourcesResponse, seoResponse] = await Promise.all([requestNewsApiOptional({ path: NEWS_API_TRENDS_PATH, timeoutMs: NEWS_API_TIMEOUT_MS, params: { top: NEWS_API_CONTEXT_TOP }, label: 'trends' }), requestNewsApiOptional({ path: NEWS_API_FRANCHISES_PATH, timeoutMs: NEWS_API_TIMEOUT_MS, params: { top: NEWS_API_CONTEXT_TOP, limit: NEWS_API_CONTEXT_TOP }, label: 'franchises' }), requestNewsApiOptional({ path: NEWS_API_SOURCES_PATH, timeoutMs: NEWS_API_TIMEOUT_MS, params: { top: NEWS_API_CONTEXT_TOP }, label: 'sources' }), requestNewsApiOptional({ path: NEWS_API_SEO_ENTITIES_PATH, timeoutMs: NEWS_API_TIMEOUT_MS, params: { top: NEWS_API_CONTEXT_TOP }, label: 'seo_entities' })]);
|
|
336
|
+
|
|
337
|
+
const context = buildNewsContextFromPayloads({
|
|
338
|
+
trendsPayload: trendsResponse?.data,
|
|
339
|
+
franchisesPayload: franchisesResponse?.data,
|
|
340
|
+
sourcesPayload: sourcesResponse?.data,
|
|
341
|
+
seoPayload: seoResponse?.data,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
newsContextState.data = context;
|
|
345
|
+
newsContextState.expiresAt = __timeNowMs() + NEWS_API_CONTEXT_TTL_MS;
|
|
346
|
+
return context;
|
|
347
|
+
})()
|
|
348
|
+
.catch((error) => {
|
|
349
|
+
logger.warn('Falha ao construir contexto inteligente de noticias. Usando cache anterior.', {
|
|
350
|
+
error: error.message,
|
|
351
|
+
});
|
|
352
|
+
return staleContext;
|
|
353
|
+
})
|
|
354
|
+
.finally(() => {
|
|
355
|
+
newsContextState.inFlight = null;
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return newsContextState.inFlight;
|
|
359
|
+
};
|
|
360
|
+
|
|
48
361
|
const loadEnabledGroupsFromDb = async () => {
|
|
49
362
|
const enabledGroups = [];
|
|
50
363
|
const limit = 100;
|
|
@@ -69,35 +382,532 @@ const loadEnabledGroupsFromDb = async () => {
|
|
|
69
382
|
};
|
|
70
383
|
|
|
71
384
|
const normalizeNewsItems = (data) => {
|
|
72
|
-
|
|
73
|
-
return data
|
|
385
|
+
return toArrayItems(data)
|
|
74
386
|
.filter((item) => item && typeof item === 'object')
|
|
75
|
-
.map((item) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
refined
|
|
79
|
-
|
|
387
|
+
.map((item) => {
|
|
388
|
+
const refined = item.refined && typeof item.refined === 'object' ? { ...item.refined } : {};
|
|
389
|
+
|
|
390
|
+
const resolvedUrl = String(refined.url || item.url || '').trim();
|
|
391
|
+
const resolvedCanonicalUrl = String(refined.canonicalUrl || item.canonicalUrl || '').trim();
|
|
392
|
+
const resolvedId = String(item.id || refined.identityHash || item.identityHash || refined.contentHash || item.contentHash || resolvedCanonicalUrl || resolvedUrl || '').trim();
|
|
393
|
+
const normalizedSourceId = normalizeToken(refined.sourceId || item.sourceId || '');
|
|
394
|
+
const normalizedFranchiseSlug = normalizeToken(item.franchiseSlug || refined.franchiseSlug || '');
|
|
395
|
+
const normalizedNewsSlug = normalizeToken(item.newsSlug || refined.newsSlug || '');
|
|
396
|
+
|
|
397
|
+
if (!resolvedId) return null;
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
id: resolvedId,
|
|
401
|
+
timestamp: String(item.timestamp || refined.timestamp || refined.publishedAt || refined.firstSeenAt || item.publishedAt || '').trim() || null,
|
|
402
|
+
newsSlug: normalizedNewsSlug || null,
|
|
403
|
+
sourceId: normalizedSourceId || null,
|
|
404
|
+
sourceName: String(refined.sourceName || item.sourceName || '').trim() || '',
|
|
405
|
+
franchiseSlug: normalizedFranchiseSlug || null,
|
|
406
|
+
franchiseName: String(item.franchiseName || refined.franchiseName || '').trim() || '',
|
|
407
|
+
score: toFiniteNumber(item.score || refined.score, 0),
|
|
408
|
+
trendScore: toFiniteNumber(item.trendScore || refined.trendScore || item.topicTrendScore || 0, 0),
|
|
409
|
+
qualityScore: toFiniteNumber(item.qualityScore || refined.qualityScore || 0, 0),
|
|
410
|
+
importanceScore: toFiniteNumber(item.importanceScore || refined.importanceScore || 0, 0),
|
|
411
|
+
entities: item.entities && typeof item.entities === 'object' ? item.entities : refined.entities && typeof refined.entities === 'object' ? refined.entities : {},
|
|
412
|
+
refined: {
|
|
413
|
+
...refined,
|
|
414
|
+
name: String(refined.name || refined.title || item.name || item.title || '').trim() || '',
|
|
415
|
+
summary: String(refined.summary || item.summary || '').trim() || '',
|
|
416
|
+
url: resolvedUrl || resolvedCanonicalUrl,
|
|
417
|
+
canonicalUrl: resolvedCanonicalUrl || resolvedUrl,
|
|
418
|
+
image: String(refined.image || item.image || '').trim() || '',
|
|
419
|
+
sourceId: normalizedSourceId || '',
|
|
420
|
+
sourceName: String(refined.sourceName || item.sourceName || '').trim() || '',
|
|
421
|
+
franchiseSlug: normalizedFranchiseSlug || '',
|
|
422
|
+
franchiseName: String(item.franchiseName || refined.franchiseName || '').trim() || '',
|
|
423
|
+
categories: Array.isArray(refined.categories) ? refined.categories : Array.isArray(item.categories) ? item.categories : [],
|
|
424
|
+
categoriesNormalized: Array.isArray(refined.categoriesNormalized) ? refined.categoriesNormalized.map((entry) => normalizeToken(entry)).filter(Boolean) : Array.isArray(item.categoriesNormalized) ? item.categoriesNormalized.map((entry) => normalizeToken(entry)).filter(Boolean) : [],
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
})
|
|
428
|
+
.filter(Boolean);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const extractItemSourceId = (item) => normalizeToken(item?.sourceId || item?.refined?.sourceId || '');
|
|
432
|
+
|
|
433
|
+
const extractItemFranchiseSlug = (item) => normalizeToken(item?.franchiseSlug || item?.refined?.franchiseSlug || '');
|
|
434
|
+
|
|
435
|
+
const extractItemNewsSlug = (item) => normalizeToken(item?.newsSlug || item?.refined?.newsSlug || '');
|
|
436
|
+
|
|
437
|
+
const appendEntitySlug = (target, value) => {
|
|
438
|
+
const normalized = normalizeToken(value);
|
|
439
|
+
if (normalized) {
|
|
440
|
+
target.add(normalized);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const extractItemEntitySlugs = (item) => {
|
|
445
|
+
const slugs = new Set();
|
|
446
|
+
if (!item || typeof item !== 'object') return slugs;
|
|
447
|
+
|
|
448
|
+
appendEntitySlug(slugs, extractItemFranchiseSlug(item));
|
|
449
|
+
const categoriesNormalized = Array.isArray(item?.refined?.categoriesNormalized) ? item.refined.categoriesNormalized : [];
|
|
450
|
+
categoriesNormalized.forEach((entry) => appendEntitySlug(slugs, entry));
|
|
451
|
+
|
|
452
|
+
const entities = item?.entities && typeof item.entities === 'object' ? item.entities : {};
|
|
453
|
+
for (const value of Object.values(entities)) {
|
|
454
|
+
if (!Array.isArray(value)) continue;
|
|
455
|
+
value.forEach((entry) => {
|
|
456
|
+
if (!entry || typeof entry !== 'object') return;
|
|
457
|
+
appendEntitySlug(slugs, entry.slug || entry.name);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return slugs;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const getItemTimestampMs = (item) => {
|
|
465
|
+
const raw = String(item?.timestamp || item?.refined?.publishedAt || item?.refined?.firstSeenAt || '').trim();
|
|
466
|
+
if (!raw) return 0;
|
|
467
|
+
const parsed = Date.parse(raw);
|
|
468
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const sortByTimestampAsc = (items) =>
|
|
472
|
+
items.sort((a, b) => {
|
|
473
|
+
const aTime = getItemTimestampMs(a);
|
|
474
|
+
const bTime = getItemTimestampMs(b);
|
|
475
|
+
return aTime - bTime;
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const sortByTimestampDesc = (items) =>
|
|
479
|
+
items.sort((a, b) => {
|
|
480
|
+
const aTime = getItemTimestampMs(a);
|
|
481
|
+
const bTime = getItemTimestampMs(b);
|
|
482
|
+
return bTime - aTime;
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const mergeNewsItem = (primaryItem, fallbackItem) => {
|
|
486
|
+
const base = primaryItem && typeof primaryItem === 'object' ? primaryItem : null;
|
|
487
|
+
const fallback = fallbackItem && typeof fallbackItem === 'object' ? fallbackItem : null;
|
|
488
|
+
if (!base) return fallback;
|
|
489
|
+
if (!fallback) return base;
|
|
490
|
+
|
|
491
|
+
const mergedRefined = {
|
|
492
|
+
...(fallback.refined || {}),
|
|
493
|
+
...(base.refined || {}),
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const merged = {
|
|
497
|
+
...fallback,
|
|
498
|
+
...base,
|
|
499
|
+
id: String(base.id || fallback.id || '').trim() || null,
|
|
500
|
+
timestamp: String(base.timestamp || fallback.timestamp || '').trim() || null,
|
|
501
|
+
newsSlug: extractItemNewsSlug(base) || extractItemNewsSlug(fallback) || null,
|
|
502
|
+
sourceId: extractItemSourceId(base) || extractItemSourceId(fallback) || null,
|
|
503
|
+
sourceName: String(base.sourceName || fallback.sourceName || mergedRefined.sourceName || '').trim(),
|
|
504
|
+
franchiseSlug: extractItemFranchiseSlug(base) || extractItemFranchiseSlug(fallback) || null,
|
|
505
|
+
franchiseName: String(base.franchiseName || fallback.franchiseName || mergedRefined.franchiseName || '').trim(),
|
|
506
|
+
score: Math.max(toFiniteNumber(base.score, 0), toFiniteNumber(fallback.score, 0)),
|
|
507
|
+
trendScore: Math.max(toFiniteNumber(base.trendScore, 0), toFiniteNumber(fallback.trendScore, 0)),
|
|
508
|
+
qualityScore: Math.max(toFiniteNumber(base.qualityScore, 0), toFiniteNumber(fallback.qualityScore, 0)),
|
|
509
|
+
importanceScore: Math.max(toFiniteNumber(base.importanceScore, 0), toFiniteNumber(fallback.importanceScore, 0)),
|
|
510
|
+
entities: Object.keys(base.entities || {}).length ? base.entities : fallback.entities || {},
|
|
511
|
+
refined: {
|
|
512
|
+
...mergedRefined,
|
|
513
|
+
name: String(base?.refined?.name || fallback?.refined?.name || '').trim(),
|
|
514
|
+
summary: String(base?.refined?.summary || fallback?.refined?.summary || '').trim(),
|
|
515
|
+
url: String(base?.refined?.url || fallback?.refined?.url || '').trim(),
|
|
516
|
+
canonicalUrl: String(base?.refined?.canonicalUrl || fallback?.refined?.canonicalUrl || '').trim(),
|
|
517
|
+
image: String(base?.refined?.image || fallback?.refined?.image || '').trim(),
|
|
518
|
+
sourceId: extractItemSourceId(base) || extractItemSourceId(fallback) || '',
|
|
519
|
+
sourceName: String(base?.refined?.sourceName || fallback?.refined?.sourceName || '').trim(),
|
|
520
|
+
franchiseSlug: extractItemFranchiseSlug(base) || extractItemFranchiseSlug(fallback) || '',
|
|
521
|
+
franchiseName: String(base?.refined?.franchiseName || fallback?.refined?.franchiseName || '').trim(),
|
|
522
|
+
categoriesNormalized: Array.isArray(base?.refined?.categoriesNormalized) && base.refined.categoriesNormalized.length ? base.refined.categoriesNormalized : Array.isArray(fallback?.refined?.categoriesNormalized) ? fallback.refined.categoriesNormalized : [],
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
return merged;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const extractSingleNewsItemFromPayload = (payload) => {
|
|
530
|
+
if (payload?.item && typeof payload.item === 'object') {
|
|
531
|
+
const normalizedFromItem = normalizeNewsItems([payload.item]);
|
|
532
|
+
return normalizedFromItem[0] || null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
|
|
536
|
+
if (candidates.length > 0) {
|
|
537
|
+
const normalizedCandidates = normalizeNewsItems(candidates);
|
|
538
|
+
return normalizedCandidates[0] || null;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const normalized = normalizeNewsItems(payload);
|
|
542
|
+
return normalized[0] || null;
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
const fetchArticleById = async (articleId) => {
|
|
546
|
+
const normalizedId = String(articleId || '').trim();
|
|
547
|
+
if (!normalizedId) return null;
|
|
548
|
+
|
|
549
|
+
const cacheKey = `id:${normalizedId}`;
|
|
550
|
+
const cached = getCachedEntry(articleDetailsCache, cacheKey);
|
|
551
|
+
if (cached) return cached;
|
|
552
|
+
|
|
553
|
+
const path = resolvePathTemplate(NEWS_API_ARTICLE_BY_ID_PATH, { id: normalizedId });
|
|
554
|
+
if (!path || path.includes(':id')) return null;
|
|
555
|
+
|
|
556
|
+
const response = await requestNewsApiOptional({
|
|
557
|
+
path,
|
|
558
|
+
timeoutMs: NEWS_API_DETAILS_TIMEOUT_MS,
|
|
559
|
+
params: { limit: 1 },
|
|
560
|
+
label: 'article_by_id',
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const normalized = extractSingleNewsItemFromPayload(response?.data);
|
|
564
|
+
if (normalized) {
|
|
565
|
+
setCachedEntry(articleDetailsCache, cacheKey, normalized);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return normalized;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const fetchArticleBySlug = async (newsSlug) => {
|
|
572
|
+
const normalizedSlug = normalizeToken(newsSlug);
|
|
573
|
+
if (!normalizedSlug) return null;
|
|
574
|
+
|
|
575
|
+
const cacheKey = `slug:${normalizedSlug}`;
|
|
576
|
+
const cached = getCachedEntry(articleDetailsCache, cacheKey);
|
|
577
|
+
if (cached) return cached;
|
|
578
|
+
|
|
579
|
+
const path = resolvePathTemplate(NEWS_API_ARTICLE_BY_SLUG_PATH, { slug: normalizedSlug });
|
|
580
|
+
if (!path || path.includes(':slug')) return null;
|
|
581
|
+
|
|
582
|
+
const response = await requestNewsApiOptional({
|
|
583
|
+
path,
|
|
584
|
+
timeoutMs: NEWS_API_DETAILS_TIMEOUT_MS,
|
|
585
|
+
params: { limit: 1 },
|
|
586
|
+
label: 'article_by_slug',
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const normalized = extractSingleNewsItemFromPayload(response?.data);
|
|
590
|
+
if (normalized) {
|
|
591
|
+
setCachedEntry(articleDetailsCache, cacheKey, normalized);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return normalized;
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const enrichNewsItemIfNeeded = async (newsItem) => {
|
|
598
|
+
if (!newsItem || typeof newsItem !== 'object') return newsItem;
|
|
599
|
+
|
|
600
|
+
const hasSummary = Boolean(String(newsItem?.refined?.summary || '').trim());
|
|
601
|
+
const hasImage = Boolean(String(newsItem?.refined?.image || '').trim());
|
|
602
|
+
const hasSource = Boolean(extractItemSourceId(newsItem));
|
|
603
|
+
const hasFranchise = Boolean(extractItemFranchiseSlug(newsItem));
|
|
604
|
+
|
|
605
|
+
if (hasSummary && hasImage && hasSource && hasFranchise) {
|
|
606
|
+
return newsItem;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const [byId, bySlug] = await Promise.all([fetchArticleById(newsItem.id), fetchArticleBySlug(extractItemNewsSlug(newsItem))]);
|
|
610
|
+
|
|
611
|
+
return mergeNewsItem(mergeNewsItem(newsItem, byId), bySlug);
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const fetchSourceDetails = async (sourceId) => {
|
|
615
|
+
const normalizedSourceId = normalizeToken(sourceId);
|
|
616
|
+
if (!normalizedSourceId) return null;
|
|
617
|
+
|
|
618
|
+
const cached = getCachedEntry(sourceDetailsCache, normalizedSourceId);
|
|
619
|
+
if (cached) return cached;
|
|
620
|
+
|
|
621
|
+
const path = resolvePathTemplate(NEWS_API_SOURCE_BY_ID_PATH, { sourceId: normalizedSourceId });
|
|
622
|
+
if (!path || path.includes(':sourceId')) return null;
|
|
623
|
+
|
|
624
|
+
const response = await requestNewsApiOptional({
|
|
625
|
+
path,
|
|
626
|
+
timeoutMs: NEWS_API_DETAILS_TIMEOUT_MS,
|
|
627
|
+
params: { limit: 1 },
|
|
628
|
+
label: 'source_by_id',
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
const source = response?.data?.source;
|
|
632
|
+
if (!source || typeof source !== 'object') return null;
|
|
633
|
+
|
|
634
|
+
const parsed = {
|
|
635
|
+
id: normalizeToken(source.id || normalizedSourceId),
|
|
636
|
+
name: String(source.name || '').trim(),
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
setCachedEntry(sourceDetailsCache, normalizedSourceId, parsed);
|
|
640
|
+
return parsed;
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const fetchFranchiseDetails = async (franchiseSlug) => {
|
|
644
|
+
const normalizedSlug = normalizeToken(franchiseSlug);
|
|
645
|
+
if (!normalizedSlug) return null;
|
|
646
|
+
|
|
647
|
+
const cached = getCachedEntry(franchiseDetailsCache, normalizedSlug);
|
|
648
|
+
if (cached) return cached;
|
|
649
|
+
|
|
650
|
+
const path = resolvePathTemplate(NEWS_API_FRANCHISE_BY_SLUG_PATH, { slug: normalizedSlug });
|
|
651
|
+
if (!path || path.includes(':slug')) return null;
|
|
652
|
+
|
|
653
|
+
const response = await requestNewsApiOptional({
|
|
654
|
+
path,
|
|
655
|
+
timeoutMs: NEWS_API_DETAILS_TIMEOUT_MS,
|
|
656
|
+
params: { limit: 1 },
|
|
657
|
+
label: 'franchise_by_slug',
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
const payload = response?.data;
|
|
661
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
662
|
+
|
|
663
|
+
const parsed = {
|
|
664
|
+
slug: normalizeToken(payload.slug || normalizedSlug),
|
|
665
|
+
name: String(payload.name || '').trim(),
|
|
666
|
+
mentions: toFiniteNumber(payload.total, 0),
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
setCachedEntry(franchiseDetailsCache, normalizedSlug, parsed);
|
|
670
|
+
return parsed;
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
const fetchSeoEntityDetails = async ({ type, slug }) => {
|
|
674
|
+
const normalizedType = normalizeToken(type);
|
|
675
|
+
const normalizedSlug = normalizeToken(slug);
|
|
676
|
+
if (!normalizedType || !normalizedSlug) return null;
|
|
677
|
+
|
|
678
|
+
const cacheKey = `${normalizedType}:${normalizedSlug}`;
|
|
679
|
+
const cached = getCachedEntry(seoDetailsCache, cacheKey);
|
|
680
|
+
if (cached) return cached;
|
|
681
|
+
|
|
682
|
+
const path = resolvePathTemplate(NEWS_API_SEO_BY_TYPE_SLUG_PATH, {
|
|
683
|
+
type: normalizedType,
|
|
684
|
+
slug: normalizedSlug,
|
|
685
|
+
});
|
|
686
|
+
if (!path || path.includes(':type') || path.includes(':slug')) return null;
|
|
687
|
+
|
|
688
|
+
const response = await requestNewsApiOptional({
|
|
689
|
+
path,
|
|
690
|
+
timeoutMs: NEWS_API_DETAILS_TIMEOUT_MS,
|
|
691
|
+
params: { limit: 1 },
|
|
692
|
+
label: 'seo_by_type_slug',
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
const entity = response?.data?.entity;
|
|
696
|
+
if (!entity || typeof entity !== 'object') return null;
|
|
697
|
+
|
|
698
|
+
const parsed = {
|
|
699
|
+
type: normalizeToken(entity.type || normalizedType),
|
|
700
|
+
slug: normalizeToken(entity.slug || normalizedSlug),
|
|
701
|
+
name: String(entity.name || '').trim(),
|
|
702
|
+
count: toFiniteNumber(entity.count, 0),
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
setCachedEntry(seoDetailsCache, cacheKey, parsed);
|
|
706
|
+
return parsed;
|
|
80
707
|
};
|
|
81
708
|
|
|
82
709
|
const fetchNewsItems = async () => {
|
|
710
|
+
const articlesRequestUrl = resolveNewsApiRequestUrl(NEWS_API_URL, NEWS_API_ARTICLES_PATH);
|
|
711
|
+
|
|
83
712
|
try {
|
|
84
|
-
const response = await
|
|
713
|
+
const response = await requestNewsApi({
|
|
714
|
+
path: NEWS_API_ARTICLES_PATH,
|
|
715
|
+
timeoutMs: NEWS_API_TIMEOUT_MS,
|
|
716
|
+
params: { limit: NEWS_API_LIMIT },
|
|
717
|
+
});
|
|
85
718
|
return normalizeNewsItems(response.data);
|
|
86
719
|
} catch (error) {
|
|
87
|
-
|
|
720
|
+
if (!NEWS_API_LEGACY_FALLBACK || articlesRequestUrl === NEWS_API_URL) {
|
|
721
|
+
logger.error('Erro ao buscar noticias da API.', {
|
|
722
|
+
error: error.message,
|
|
723
|
+
url: articlesRequestUrl,
|
|
724
|
+
});
|
|
725
|
+
return [];
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
logger.warn('Falha no endpoint de artigos paginado; tentando fallback legado.', {
|
|
88
729
|
error: error.message,
|
|
89
|
-
url:
|
|
730
|
+
url: articlesRequestUrl,
|
|
731
|
+
fallback_url: NEWS_API_URL,
|
|
90
732
|
});
|
|
91
|
-
|
|
733
|
+
|
|
734
|
+
try {
|
|
735
|
+
const legacyResponse = await requestNewsApi({ path: '', timeoutMs: NEWS_API_TIMEOUT_MS });
|
|
736
|
+
return normalizeNewsItems(legacyResponse.data);
|
|
737
|
+
} catch (legacyError) {
|
|
738
|
+
logger.error('Erro ao buscar noticias da API.', {
|
|
739
|
+
error: legacyError.message,
|
|
740
|
+
url: NEWS_API_URL,
|
|
741
|
+
});
|
|
742
|
+
return [];
|
|
743
|
+
}
|
|
92
744
|
}
|
|
93
745
|
};
|
|
94
746
|
|
|
95
|
-
const
|
|
747
|
+
const normalizeNewsFilters = (config = {}) => {
|
|
748
|
+
const nested = config?.newsFilters && typeof config.newsFilters === 'object' ? config.newsFilters : {};
|
|
749
|
+
|
|
750
|
+
return {
|
|
751
|
+
includeSourceIds: new Set([...normalizeStringList(config.newsSources), ...normalizeStringList(config.newsSourceIds), ...normalizeStringList(nested.sources), ...normalizeStringList(nested.sourceIds)].filter(Boolean)),
|
|
752
|
+
excludeSourceIds: new Set([...normalizeStringList(config.newsBlockedSources), ...normalizeStringList(config.newsExcludeSources), ...normalizeStringList(nested.excludeSources), ...normalizeStringList(nested.blockedSources)].filter(Boolean)),
|
|
753
|
+
includeFranchiseSlugs: new Set([...normalizeStringList(config.newsFranchises), ...normalizeStringList(config.newsFranchiseSlugs), ...normalizeStringList(nested.franchises), ...normalizeStringList(nested.franchiseSlugs)].filter(Boolean)),
|
|
754
|
+
excludeFranchiseSlugs: new Set([...normalizeStringList(config.newsBlockedFranchises), ...normalizeStringList(config.newsExcludeFranchises), ...normalizeStringList(nested.excludeFranchises), ...normalizeStringList(nested.blockedFranchises)].filter(Boolean)),
|
|
755
|
+
includeEntitySlugs: new Set([...normalizeStringList(config.newsEntities), ...normalizeStringList(config.newsEntitySlugs), ...normalizeStringList(config.newsTags), ...normalizeStringList(nested.entities), ...normalizeStringList(nested.entitySlugs), ...normalizeStringList(nested.tags)].filter(Boolean)),
|
|
756
|
+
excludeEntitySlugs: new Set([...normalizeStringList(config.newsBlockedEntities), ...normalizeStringList(config.newsExcludeEntities), ...normalizeStringList(config.newsBlockedTags), ...normalizeStringList(nested.excludeEntities), ...normalizeStringList(nested.blockedEntities), ...normalizeStringList(nested.excludeTags)].filter(Boolean)),
|
|
757
|
+
onlyTrending: Boolean(config.newsOnlyTrending || nested.onlyTrending),
|
|
758
|
+
};
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
const hasSetIntersection = (setA, setB) => {
|
|
762
|
+
if (!(setA instanceof Set) || !(setB instanceof Set)) return false;
|
|
763
|
+
if (setA.size === 0 || setB.size === 0) return false;
|
|
764
|
+
|
|
765
|
+
const [smallest, largest] = setA.size <= setB.size ? [setA, setB] : [setB, setA];
|
|
766
|
+
for (const value of smallest.values()) {
|
|
767
|
+
if (largest.has(value)) return true;
|
|
768
|
+
}
|
|
769
|
+
return false;
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
const itemMatchesNewsFilters = (newsItem, filters, context) => {
|
|
773
|
+
if (!newsItem || typeof newsItem !== 'object') return false;
|
|
774
|
+
const safeFilters = filters || normalizeNewsFilters();
|
|
775
|
+
|
|
776
|
+
const sourceId = extractItemSourceId(newsItem);
|
|
777
|
+
const franchiseSlug = extractItemFranchiseSlug(newsItem);
|
|
778
|
+
const entitySlugs = extractItemEntitySlugs(newsItem);
|
|
779
|
+
|
|
780
|
+
if (safeFilters.includeSourceIds.size > 0 && (!sourceId || !safeFilters.includeSourceIds.has(sourceId))) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
if (sourceId && safeFilters.excludeSourceIds.has(sourceId)) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (safeFilters.includeFranchiseSlugs.size > 0 && (!franchiseSlug || !safeFilters.includeFranchiseSlugs.has(franchiseSlug))) {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
if (franchiseSlug && safeFilters.excludeFranchiseSlugs.has(franchiseSlug)) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (safeFilters.includeEntitySlugs.size > 0 && !hasSetIntersection(safeFilters.includeEntitySlugs, entitySlugs)) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
if (safeFilters.excludeEntitySlugs.size > 0 && hasSetIntersection(safeFilters.excludeEntitySlugs, entitySlugs)) {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (safeFilters.onlyTrending) {
|
|
802
|
+
if (!franchiseSlug) return false;
|
|
803
|
+
if (!(context?.trendingFranchiseSlugs instanceof Set)) return false;
|
|
804
|
+
if (!context.trendingFranchiseSlugs.has(franchiseSlug)) return false;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return true;
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
const computeNewsPriority = (newsItem, context) => {
|
|
811
|
+
const sourceId = extractItemSourceId(newsItem);
|
|
812
|
+
const franchiseSlug = extractItemFranchiseSlug(newsItem);
|
|
813
|
+
const entitySlugs = extractItemEntitySlugs(newsItem);
|
|
814
|
+
const timestampMs = getItemTimestampMs(newsItem);
|
|
815
|
+
const ageHours = timestampMs > 0 ? Math.max(0, (__timeNowMs() - timestampMs) / (60 * 60 * 1000)) : 48;
|
|
816
|
+
|
|
817
|
+
const baseScore = toFiniteNumber(newsItem?.score, 0);
|
|
818
|
+
const trendScore = toFiniteNumber(newsItem?.trendScore, 0);
|
|
819
|
+
const qualityScore = toFiniteNumber(newsItem?.qualityScore, 0);
|
|
820
|
+
const importanceScore = toFiniteNumber(newsItem?.importanceScore, 0);
|
|
821
|
+
const recencyScore = Math.max(0, 48 - Math.min(48, ageHours));
|
|
822
|
+
|
|
823
|
+
const sourceStats = sourceId ? context?.sourceStatsById?.get(sourceId) : null;
|
|
824
|
+
const sourceBoost = sourceStats ? Math.min(12, toFiniteNumber(sourceStats.avgScore, 0) / 10) : 0;
|
|
825
|
+
|
|
826
|
+
const franchiseStats = franchiseSlug ? context?.franchiseStatsBySlug?.get(franchiseSlug) : null;
|
|
827
|
+
const franchiseBoost = franchiseStats ? Math.min(18, toFiniteNumber(franchiseStats.mentions, 0) * 2 + toFiniteNumber(franchiseStats.maxTrendScore, 0)) : 0;
|
|
828
|
+
const trendingBoost = franchiseSlug && context?.trendingFranchiseSlugs?.has(franchiseSlug) ? 12 : 0;
|
|
829
|
+
|
|
830
|
+
let seoBoost = 0;
|
|
831
|
+
if (context?.seoEntitySlugsByType instanceof Map && entitySlugs.size > 0) {
|
|
832
|
+
for (const [type, seoSlugs] of context.seoEntitySlugsByType.entries()) {
|
|
833
|
+
if (!(seoSlugs instanceof Set) || seoSlugs.size === 0) continue;
|
|
834
|
+
if (!hasSetIntersection(entitySlugs, seoSlugs)) continue;
|
|
835
|
+
seoBoost += type === 'anime' ? 8 : 4;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return baseScore + trendScore + qualityScore * 0.2 + importanceScore * 0.3 + recencyScore + sourceBoost + franchiseBoost + trendingBoost + seoBoost;
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
const selectNextNewsItem = ({ unsentItems = [], config = {}, context = null }) => {
|
|
843
|
+
const filters = normalizeNewsFilters(config);
|
|
844
|
+
const filteredItems = unsentItems.filter((item) => itemMatchesNewsFilters(item, filters, context));
|
|
845
|
+
if (filteredItems.length === 0) {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (!NEWS_SMART_SELECTION_ENABLED) {
|
|
850
|
+
sortByTimestampAsc(filteredItems);
|
|
851
|
+
return filteredItems[0] || null;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const recentPool = sortByTimestampDesc([...filteredItems]).slice(0, NEWS_SMART_SELECTION_WINDOW);
|
|
855
|
+
const ranked = recentPool
|
|
856
|
+
.map((item) => ({
|
|
857
|
+
item,
|
|
858
|
+
score: computeNewsPriority(item, context),
|
|
859
|
+
timestampMs: getItemTimestampMs(item),
|
|
860
|
+
}))
|
|
861
|
+
.sort((a, b) => {
|
|
862
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
863
|
+
return b.timestampMs - a.timestampMs;
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
return ranked[0]?.item || null;
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
const buildNewsCaption = async (newsItem, context = null) => {
|
|
96
870
|
const title = newsItem?.refined?.name || 'Notícia';
|
|
97
871
|
const summary = (newsItem?.refined?.summary || '').trim();
|
|
98
|
-
const url = newsItem?.refined?.url || '';
|
|
872
|
+
const url = newsItem?.refined?.url || newsItem?.refined?.canonicalUrl || '';
|
|
873
|
+
const sourceId = extractItemSourceId(newsItem);
|
|
874
|
+
const franchiseSlug = extractItemFranchiseSlug(newsItem);
|
|
875
|
+
const fallbackTagSlug = normalizeToken(newsItem?.refined?.categoriesNormalized?.[0] || '');
|
|
876
|
+
|
|
877
|
+
const sourceFromContext = sourceId ? context?.sourceStatsById?.get(sourceId) : null;
|
|
878
|
+
const franchiseFromContext = franchiseSlug ? context?.franchiseStatsBySlug?.get(franchiseSlug) : null;
|
|
879
|
+
|
|
880
|
+
const needsSourceDetail = sourceId && !String(newsItem?.sourceName || newsItem?.refined?.sourceName || sourceFromContext?.name || '').trim();
|
|
881
|
+
const needsFranchiseDetail = franchiseSlug && !String(newsItem?.franchiseName || newsItem?.refined?.franchiseName || franchiseFromContext?.name || '').trim();
|
|
882
|
+
|
|
883
|
+
const [sourceDetails, franchiseDetails, seoByFranchise, seoByTag] = await Promise.all([needsSourceDetail ? fetchSourceDetails(sourceId) : null, needsFranchiseDetail ? fetchFranchiseDetails(franchiseSlug) : null, franchiseSlug ? fetchSeoEntityDetails({ type: 'anime', slug: franchiseSlug }) : null, !franchiseSlug && fallbackTagSlug ? fetchSeoEntityDetails({ type: 'tag', slug: fallbackTagSlug }) : null]);
|
|
99
884
|
|
|
100
885
|
const lines = [`📰 *${title}*`];
|
|
886
|
+
if (NEWS_CAPTION_CONTEXT_ENABLED) {
|
|
887
|
+
const sourceName = String(newsItem?.sourceName || newsItem?.refined?.sourceName || sourceFromContext?.name || sourceDetails?.name || '').trim();
|
|
888
|
+
const franchiseName = String(newsItem?.franchiseName || newsItem?.refined?.franchiseName || franchiseFromContext?.name || franchiseDetails?.name || '').trim();
|
|
889
|
+
const franchiseMentions = toFiniteNumber(franchiseFromContext?.mentions || franchiseDetails?.mentions || seoByFranchise?.count, 0);
|
|
890
|
+
const isTrending = Boolean(franchiseSlug && context?.trendingFranchiseSlugs?.has(franchiseSlug));
|
|
891
|
+
const metadataParts = [];
|
|
892
|
+
|
|
893
|
+
if (sourceName) {
|
|
894
|
+
metadataParts.push(`Fonte: ${sourceName}`);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (franchiseName) {
|
|
898
|
+
const label = isTrending ? 'Tendência' : 'Franquia';
|
|
899
|
+
const mentionText = franchiseMentions > 0 ? ` (${franchiseMentions} menções)` : '';
|
|
900
|
+
metadataParts.push(`${label}: ${franchiseName}${mentionText}`);
|
|
901
|
+
} else if (seoByTag?.name) {
|
|
902
|
+
const mentionText = seoByTag.count > 0 ? ` (${seoByTag.count} menções)` : '';
|
|
903
|
+
metadataParts.push(`Tag: ${seoByTag.name}${mentionText}`);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (metadataParts.length > 0) {
|
|
907
|
+
lines.push('', `📌 ${metadataParts.slice(0, 2).join(' • ')}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
101
911
|
if (summary) {
|
|
102
912
|
lines.push('', summary);
|
|
103
913
|
}
|
|
@@ -107,16 +917,6 @@ const buildNewsCaption = (newsItem) => {
|
|
|
107
917
|
return lines.join('\n').trim();
|
|
108
918
|
};
|
|
109
919
|
|
|
110
|
-
const sortByTimestampAsc = (items) =>
|
|
111
|
-
items.sort((a, b) => {
|
|
112
|
-
const aTime = a?.timestamp ? Date.parse(a.timestamp) : 0;
|
|
113
|
-
const bTime = b?.timestamp ? Date.parse(b.timestamp) : 0;
|
|
114
|
-
if (Number.isNaN(aTime) && Number.isNaN(bTime)) return 0;
|
|
115
|
-
if (Number.isNaN(aTime)) return 1;
|
|
116
|
-
if (Number.isNaN(bTime)) return -1;
|
|
117
|
-
return aTime - bTime;
|
|
118
|
-
});
|
|
119
|
-
|
|
120
920
|
const trimSentIds = (ids) => {
|
|
121
921
|
if (!Array.isArray(ids)) return [];
|
|
122
922
|
if (!Number.isFinite(MAX_SENT_IDS) || MAX_SENT_IDS <= 0) return ids;
|
|
@@ -188,7 +988,7 @@ const processGroupNews = async (groupId) => {
|
|
|
188
988
|
return;
|
|
189
989
|
}
|
|
190
990
|
|
|
191
|
-
const allNews = await fetchNewsItems();
|
|
991
|
+
const [allNews, newsContext] = await Promise.all([fetchNewsItems(), getNewsContext()]);
|
|
192
992
|
if (allNews.length === 0) {
|
|
193
993
|
return;
|
|
194
994
|
}
|
|
@@ -200,9 +1000,25 @@ const processGroupNews = async (groupId) => {
|
|
|
200
1000
|
return;
|
|
201
1001
|
}
|
|
202
1002
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
1003
|
+
const selectedItem = selectNextNewsItem({
|
|
1004
|
+
unsentItems: unsent,
|
|
1005
|
+
config,
|
|
1006
|
+
context: newsContext,
|
|
1007
|
+
});
|
|
1008
|
+
if (!selectedItem) {
|
|
1009
|
+
const now = __timeNowMs();
|
|
1010
|
+
if (!state.lastNoMatchLogAt || now - state.lastNoMatchLogAt > 10 * 60_000) {
|
|
1011
|
+
state.lastNoMatchLogAt = now;
|
|
1012
|
+
logger.debug('Nenhuma noticia compativel com os filtros configurados para o grupo.', {
|
|
1013
|
+
groupId,
|
|
1014
|
+
unsent_count: unsent.length,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const nextItem = await enrichNewsItemIfNeeded(selectedItem);
|
|
1021
|
+
const caption = await buildNewsCaption(nextItem, newsContext);
|
|
206
1022
|
const imageUrl = nextItem?.refined?.image || '';
|
|
207
1023
|
let sent = false;
|
|
208
1024
|
|