Conversation
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
…ting Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Co-authored-by: EthanThePhoenix38 <103653068+EthanThePhoenix38@users.noreply.github.com>
Bumps [openai](https://github.com/openai/openai-node) from 6.17.0 to 6.19.0. - [Release notes](https://github.com/openai/openai-node/releases) - [Changelog](https://github.com/openai/openai-node/blob/master/CHANGELOG.md) - [Commits](openai/openai-node@v6.17.0...v6.19.0) --- updated-dependencies: - dependency-name: openai dependency-version: 6.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@csstools/css-syntax-patches-for-csstree](https://github.com/csstools/postcss-plugins/tree/HEAD/packages/css-syntax-patches-for-csstree) from 1.0.26 to 1.0.27. - [Changelog](https://github.com/csstools/postcss-plugins/blob/main/packages/css-syntax-patches-for-csstree/CHANGELOG.md) - [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/packages/css-syntax-patches-for-csstree) --- updated-dependencies: - dependency-name: "@csstools/css-syntax-patches-for-csstree" dependency-version: 1.0.27 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…/css-syntax-patches-for-csstree-1.0.27' into securite
Bumps [@csstools/css-calc](https://github.com/csstools/postcss-plugins/tree/HEAD/packages/css-calc) from 3.0.0 to 3.0.1. - [Changelog](https://github.com/csstools/postcss-plugins/blob/main/packages/css-calc/CHANGELOG.md) - [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/packages/css-calc) --- updated-dependencies: - dependency-name: "@csstools/css-calc" dependency-version: 3.0.1 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…/css-calc-3.0.1' into securite
Bumps [lru-cache](https://github.com/isaacs/node-lru-cache) from 11.2.5 to 11.2.6. - [Changelog](https://github.com/isaacs/node-lru-cache/blob/main/CHANGELOG.md) - [Commits](isaacs/node-lru-cache@v11.2.5...v11.2.6) --- updated-dependencies: - dependency-name: lru-cache dependency-version: 11.2.6 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…e-11.2.6' into securite
Bumps [@exodus/bytes](https://github.com/ExodusOSS/bytes) from 1.12.0 to 1.14.0. - [Release notes](https://github.com/ExodusOSS/bytes/releases) - [Commits](ExodusOSS/bytes@v1.12.0...v1.14.0) --- updated-dependencies: - dependency-name: "@exodus/bytes" dependency-version: 1.14.0 dependency-type: indirect update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
…ytes-1.14.0' into securite
Bumps [qs](https://github.com/ljharb/qs) from 6.14.1 to 6.14.2. - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](ljharb/qs@v6.14.1...v6.14.2) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.2 dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the npm_and_yarn group with 1 update in the / directory: [qs](https://github.com/ljharb/qs). Updates `qs` from 6.14.1 to 6.14.2 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](ljharb/qs@v6.14.1...v6.14.2) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.2 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com>
…yarn-14dc0ebc5a' into securite
The labeler workflow was completing with `action_required` status instead of success, blocking PR checks. ## Changes - Added `issues: write` permission to `.github/workflows/label.yml` The labeler action requires this permission to manage labels on pull requests. Without it, the workflow completes but GitHub marks it as requiring action. <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
The digest emails reference `template=unsubscribe.yml` in unsubscribe links (line 438 of `src/aggregator.js`), but the template file didn't exist. Users clicking "Se désabonner" were sent to a 404. **Changes:** - Added `.github/ISSUE_TEMPLATE/unsubscribe.yml` with required email field and optional reason field - Follows existing bilingual (French/English) pattern from `subscribe.yml` The unsubscribe flow is now functional end-to-end. <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/ThePhoenixAgency/AI-Pulse/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.
…-pr-57-another-one
The error handler for missing `franc-min` logged "franc-min not
available, using feed-declared language only" without telling users how
to fix it.
## Changes
- Enhanced error message to include actionable resolution steps
- Message now explicitly instructs users to run `npm install` when the
package is missing
```javascript
} catch (e) {
console.error('WARNING: franc-min package not found. Language detection will be limited to feed-declared languages only.');
console.error('To enable automatic language detection, please run: npm install');
detectLang = () => null;
}
```
This addresses the review feedback that error messages should be
informative and include instructions for resolution.
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
…tibility (#61) Addresses all actionable review feedback from PR #57 focusing on i18n quality, input validation, error handling, and browser compatibility. ## French Localization - Fixed missing accents throughout: `Français`, `Cybersécurité`, `Générale`, `préférée`, `Fréquence`, `Réinitialiser`, etc. - Updated config.json, issue templates, email templates, and UI strings ## Input Validation & Security **Aggregator (`src/aggregator.js`)** - Config loading with actionable error messages on missing/malformed files - Email validation (RFC-compliant regex) before sending - API key format validation with sanitized error output - Deduplication threshold type checking - HTML lang attribute validation (ISO 639-1 codes only) **Workflows** - Email validation with abuse detection (max @ symbol threshold) - RSS URL format validation - Input sanitization for source names and tags (XSS prevention) ```javascript // Before: silent crash on missing config const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')); // After: actionable error try { if (!fs.existsSync(CONFIG_PATH)) { console.error(`ERROR: Configuration file not found at ${CONFIG_PATH}`); console.error('Please ensure config.json exists in the repository root.'); process.exit(1); } // ... } catch (e) { console.error(`ERROR: Failed to load or parse config.json: ${e.message}`); process.exit(1); } ``` ## Browser Compatibility - `localStorage` availability checks before all operations - Try-catch wrappers with quota overflow recovery - `IntersectionObserver` feature detection with graceful degradation - `ReadHistory.markRead` safety guard against undefined ## Rate Limiting & Configuration - Email sending rate limit (100ms delay between sends) - Configurable sender via `EMAIL_FROM` env var with dev domain warning - Constants extracted for magic numbers (`VALID_LANG_CODES`, `MAX_SOURCE_NAME_LENGTH`, etc.) ## Security Scan ✅ CodeQL: 0 alerts <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
--- <!-- continue-task-summary-start --> **Continue Tasks:**▶️ 1 queued — [View all](https://hub.continue.dev/inbox?pr=https%3A%2F%2Fgithub.com%2FThePhoenixAgency%2FAI-Pulse%2Fpull%2F88&utm_source=github_pr&utm_medium=pr_body&utm_campaign=continue_tasks) <!-- continue-task-summary-end -->
--- <!-- continue-task-summary-start --> **Continue Tasks:**▶️ 1 queued — [View all](https://hub.continue.dev/inbox?pr=https%3A%2F%2Fgithub.com%2FThePhoenixAgency%2FAI-Pulse%2Fpull%2F89&utm_source=github_pr&utm_medium=pr_body&utm_campaign=continue_tasks) <!-- continue-task-summary-end -->
--- <!-- continue-task-summary-start --> **Continue Tasks:**▶️ 1 queued — [View all](https://hub.continue.dev/inbox?pr=https%3A%2F%2Fgithub.com%2FThePhoenixAgency%2FAI-Pulse%2Fpull%2F92&utm_source=github_pr&utm_medium=pr_body&utm_campaign=continue_tasks) <!-- continue-task-summary-end -->
There was a problem hiding this comment.
Pull request overview
This PR appears to be a “rebase”-style update that mixes French copy fixes with operational hardening across the Node aggregator, GitHub issue-based automation, and the in-browser reader UI.
Changes:
- Improved robustness in
src/aggregator.js(config load error handling, email sending validation/rate limiting, language validation scaffolding). - Hardened GitHub Actions workflows that parse issue forms (added validation/sanitization; updated labels with accents).
- Added localStorage availability checks and some UI text fixes in the reader UI.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| templates/email-digest.html | Fixes French accents in email template copy. |
| src/aggregator.js | Adds config load guards + email digest improvements; introduces language validation and refactors dedupe. |
| readme-viewer.html | Adds localStorage availability checks; updates French copy; introduces scroll-spy code. |
| package.json | Bumps openai dependency version. |
| package-lock.json | Updates lockfile for dependency bumps. |
| js/tracker.js | Adds localStorage availability checks and better error handling around tracking. |
| config.json | Fixes French accents in category/tag labels. |
| CHANGELOG.md | Appends automated security update entries across multiple versions. |
| .github/workflows/manage-subscriber.yml | Adds stricter email validation/abuse heuristic; updates labels with accents. |
| .github/workflows/label.yml | Adds issues: write permission for labeler workflow. |
| .github/workflows/add-source.yml | Adds validation/sanitization for source proposals; updates category label text. |
| .github/ISSUE_TEMPLATE/unsubscribe.yml | Adds a new unsubscribe issue form. |
| .github/ISSUE_TEMPLATE/subscribe.yml | Updates labels/options with accents. |
| .github/ISSUE_TEMPLATE/new-source.yml | Updates labels/options with accents. |
Comments suppressed due to low confidence (2)
.github/workflows/manage-subscriber.yml:96
- Category mapping mismatch: the subscribe issue form uses the option label
"Tech Generale"(no accent) but the workflow expects"Tech Générale". If a user selects only that category, it won’t map and they’ll be subscribed to all categories due to the fallback. Align the option label in.github/ISSUE_TEMPLATE/subscribe.ymlwith the keys incatMap, or accept both spellings in the mapping.
const catMap = {
'AI / IA': 'ai',
'Cybersecurity': 'cybersecurity',
'IoT': 'iot',
'Windows': 'windows',
'Mac / Apple': 'mac',
'Linux': 'linux',
'Tech Générale': 'tech',
'Entrepreneuriat': 'entrepreneurship',
'Bourse & Finance': 'finance',
'Crypto & Blockchain': 'crypto',
'Open Source & GitHub': 'opensource',
src/aggregator.js:477
deduplicateArticles()now computesthreshold/normalizedTitlesbut the loop still referencesnormalizedData,titleThreshold, andcontentThreshold, which are no longer defined. This will throw at runtime and break aggregation. Either restore the originaltitleThreshold/contentThreshold+normalizedDatavariables, or refactor the comparison logic to consistently use the newthresholdand the new normalized cache array.
const threshold = (typeof SETTINGS.deduplication.similarityThreshold === 'number' &&
SETTINGS.deduplication.similarityThreshold > 0 &&
SETTINGS.deduplication.similarityThreshold <= 1)
? SETTINGS.deduplication.similarityThreshold
: 0.7;
const kept = [];
const normalizedTitles = [];
for (const article of articles) {
// Normaliser le titre et le résumé de l'article courant
const titleWords = normalizeText(article.title);
const contentWords = normalizeText(article.summary || '');
let isDuplicate = false;
// Comparer avec chaque article déjà gardé
for (let i = 0; i < normalizedData.length; i++) {
const titleSim = textSimilarity(titleWords, normalizedData[i].title);
const contentSim = textSimilarity(contentWords, normalizedData[i].content);
// Conditions pour être considéré comme doublon:
// 1. Titre très similaire (70%+)
// 2. OU contenu très similaire (50%+)
// 3. OU les deux modérément similaires (40%+ chacun)
if (titleSim >= titleThreshold ||
contentSim >= contentThreshold ||
(titleSim >= 0.4 && contentSim >= 0.4)) {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // Map language | ||
| const lang = languageRaw.includes('Francais') ? 'fr' : 'en'; | ||
| const lang = languageRaw.includes('Français') ? 'fr' : 'en'; |
There was a problem hiding this comment.
Language mapping will always fall back to 'en' because it checks languageRaw.includes('Français'), but the issue form options are "Francais / French", "Anglais / English", etc. Update the check to handle the actual option text (e.g., match both Francais and Français, or map explicit option values).
| const lang = languageRaw.includes('Français') ? 'fr' : 'en'; | |
| const normalizedLanguage = (languageRaw || '').toLowerCase(); | |
| const isFrench = normalizedLanguage.includes('francais') || normalizedLanguage.includes('français'); | |
| const lang = isFrench ? 'fr' : 'en'; |
| ## [1.1.0] - 2026-01-31 | ||
| ### Added | ||
| - **2026-02-14 11:26 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-13 06:16 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-13 06:15 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-12 06:17 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-12 06:16 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-11 06:17 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-11 06:16 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-11 06:16 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-10 06:16 UTC**: Automated Security Batch Update (DependabotSecureFlow) | ||
| - **2026-02-10 06:16 UTC**: Automated Security Batch Update (DependabotSecureFlow) |
There was a problem hiding this comment.
Changelog entries appear to be duplicated across multiple released versions and include timestamps after the stated release date (e.g., entries dated 2026-02-14 under [1.1.0] - 2026-01-31). This makes the changelog misleading. Consider moving these automated security-update lines to an [Unreleased] section or the correct version, and avoid repeating the same items under multiple releases.
| @@ -613,16 +610,16 @@ async function processArticle(article, sourceName, tags, category, feedLang) { | |||
|
|
|||
| // Retourner l'objet article traité | |||
| return { | |||
| title: (sanitizeText(article.title) || 'Untitled').slice(0, 200), // Titre limité à 200 caractères | |||
| link: finalReaderLink, // Lien vers la version locale ou originale | |||
| originalLink: articleUrl, // Lien original (toujours gardé) | |||
| pubDate: new Date(article.pubDate || Date.now()), // Date de publication | |||
| source: sourceName, // Nom de la source | |||
| tags: tags, // Tags de la source | |||
| category: category, // Catégorie | |||
| lang: detectedLang || feedLang || 'en', // Langue détectée ou déclarée | |||
| summary: smartTruncate(rawSummary), // Résumé tronqué intelligemment | |||
| hasLocalContent: hasLocalContent // Indique si on a une copie locale | |||
| title: (sanitizeText(article.title) || 'Untitled').slice(0, 200), | |||
| link: finalReaderLink, | |||
| originalLink: articleUrl, // Always set to ensure RSS feeds have valid external URLs | |||
| pubDate: new Date(article.pubDate || Date.now()), | |||
| source: sourceName, | |||
| tags: tags, | |||
| category: category, | |||
| lang: detectedLang || feedLang || 'en', | |||
| summary: smartTruncate(rawSummary), | |||
There was a problem hiding this comment.
Language validation is currently ineffective/inconsistent: lang is computed once (after an initial validation), but detectedLang may be reassigned later from full-article text without re-validation, and the returned article uses detectedLang || feedLang instead of the validated lang. Recompute/validate the final language after all detection steps and use that same value both in the generated HTML (<html lang=...>) and the returned article.lang.
| @@ -481,11 +481,7 @@ <h4>Articles par section</h4> | |||
| <span class="range-value" id="prefMaxArticlesValue">30</span> | |||
| </div> | |||
|
|
|||
There was a problem hiding this comment.
The preferences UI removed the prefHideRead checkbox, but the script still does document.getElementById('prefHideRead')... in setupPrefsPanel(). This will throw (null dereference) and break the preferences panel. Either re-add the checkbox markup, or remove the prefHideRead event wiring and the hideRead preference if it’s no longer supported.
| <div class="prefs-section"> | |
| <h4>Articles lus</h4> | |
| <label> | |
| <input type="checkbox" id="prefHideRead" name="prefHideRead"> | |
| Masquer les articles déjà lus | |
| </label> | |
| </div> |
| }); | ||
|
|
||
| // Rebuild section dropdown when filters change | ||
| buildSectionNav(); | ||
| // ─── Intersection Observer for active section ─────── | ||
| function setupScrollSpy() { | ||
| // Check if IntersectionObserver is supported | ||
| if (!('IntersectionObserver' in window)) { | ||
| console.warn('IntersectionObserver not supported. Scroll spy disabled.'); | ||
| return; | ||
| } | ||
|
|
||
| const sections = document.querySelectorAll('#readme-content section[data-category]'); | ||
| const observer = new IntersectionObserver((entries) => { | ||
| entries.forEach(entry => { | ||
| if (entry.isIntersecting) { | ||
| const cat = entry.target.getAttribute('data-category'); | ||
| document.querySelectorAll('.section-nav a').forEach(a => a.classList.remove('active')); | ||
| const activeLink = document.querySelector(`.section-nav a[data-nav-cat="${cat}"]`); | ||
| if (activeLink) activeLink.classList.add('active'); | ||
| } | ||
| }); | ||
| }, { rootMargin: '-20% 0px -60% 0px' }); | ||
|
|
||
| sections.forEach(section => observer.observe(section)); | ||
| } |
There was a problem hiding this comment.
applyFilters() is missing its closing brace before the new setupScrollSpy() declaration, which nests setupScrollSpy, setupPrefsPanel, and loadReadme inside applyFilters and leaves braces unbalanced. This will prevent the script from running correctly (and may cause a syntax error). Close applyFilters() before declaring the other functions, and call setupScrollSpy() after rendering (e.g., in the post-render setup of loadReadme()).
Continue Tasks:▶️ 1 queued — View all