Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4a4cb3a
feat: configurable news reader with 12 categories, preferences, email…
claude Feb 5, 2026
1a13d20
chore: add generated RSS feed XML files (global + per-category)
claude Feb 5, 2026
6c982ba
Update readme-viewer.html
EthanThePhoenix38 Feb 5, 2026
3102127
Potential fix for code scanning alert no. 19: DOM text reinterpreted …
EthanThePhoenix38 Feb 5, 2026
0ce0082
Potential fix for code scanning alert no. 20: DOM text reinterpreted …
EthanThePhoenix38 Feb 5, 2026
7a8b414
feat: add quick navigation bar and improved section styling
claude Feb 7, 2026
f065484
refactor: remove unused quick-nav CSS, keep improved section styling
claude Feb 7, 2026
f256beb
feat: replace sidebar with top dropdown navigation
claude Feb 7, 2026
d438f50
feat: improve deduplication and read articles management
claude Feb 7, 2026
55b9641
fix: footer links overflow on small screens
claude Feb 7, 2026
5b41d39
fix: ThePhoenixAgency link now points to actual website
claude Feb 7, 2026
2c2164c
fix: 404 redirects to previous page or reader
claude Feb 7, 2026
ad89e01
fix: replace iframe with link in portfolio
claude Feb 9, 2026
2a10380
chore: change workflow schedule from 3h to 2h
claude Feb 9, 2026
2333504
Remove automation details from README for security
claude Feb 10, 2026
ecc9937
Security: remove source submission link, fix footer responsive
claude Feb 10, 2026
49f7f5e
Fix portfolio to use ThePhoenixAgency instead of EthanThePhoenix38
claude Feb 10, 2026
dab78fd
Revert "Fix portfolio to use ThePhoenixAgency instead of EthanThePhoe…
claude Feb 10, 2026
655c2c6
docs: Ajoute documentation complète en français
claude Feb 11, 2026
95bda56
docs: Documente CSS et workflow GitHub en français
claude Feb 11, 2026
2ad31a0
docs: Documente les scripts JavaScript frontend
claude Feb 11, 2026
9c4c51e
Merge main into feature branch - resolve conflicts
claude Feb 13, 2026
9fe95ff
docs: Documente pages secondaires HTML en français
claude Feb 13, 2026
618507d
docs: Documente portfolio.html avec API GitHub
claude Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: AI-Pulse Reader
url: https://thephoenixagency.github.io/AI-Pulse/app.html
about: Accedez au reader en ligne
65 changes: 65 additions & 0 deletions .github/ISSUE_TEMPLATE/new-source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Proposer une nouvelle source / Suggest a new source
description: Proposez une source RSS a ajouter a AI-Pulse
title: "[SOURCE] "
labels: ["new-source"]
body:
- type: input
id: source_name
attributes:
label: Nom de la source / Source Name
placeholder: "Ex: Korben, TechCrunch..."
validations:
required: true
- type: input
id: source_url
attributes:
label: URL du site / Website URL
placeholder: "https://example.com"
validations:
required: true
- type: input
id: rss_url
attributes:
label: URL du flux RSS / RSS Feed URL
placeholder: "https://example.com/feed/"
validations:
required: true
- type: dropdown
id: category
attributes:
label: Categorie / Category
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The French word "Categorie" is missing an accent. It should be "Catégorie".

Suggested change
label: Categorie / Category
label: Catégorie / Category

Copilot uses AI. Check for mistakes.
options:
- AI / IA
- Cybersecurity / Cybersecurite
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The French word "Cybersecurite" is missing an accent. It should be "Cybersécurité".

Suggested change
- Cybersecurity / Cybersecurite
- Cybersecurity / Cybersécurité

Copilot uses AI. Check for mistakes.
- IoT
- Windows
- Mac / Apple
- Linux
- Tech Generale
- Entrepreneuriat / Entrepreneurship
- Bourse & Finance
- Crypto & Blockchain
- Open Source & GitHub
- Produits & Innovation
validations:
required: true
- type: dropdown
id: language
attributes:
label: Langue / Language
options:
- Francais / French
- Anglais / English
- Autre / Other
validations:
required: true
- type: input
id: tags
attributes:
label: Tags / Mots-cles
placeholder: "Ex: tech, security, AI..."
- type: textarea
id: reason
attributes:
label: Pourquoi cette source? / Why this source?
placeholder: "Contenu original, source fiable..."
48 changes: 48 additions & 0 deletions .github/ISSUE_TEMPLATE/subscribe.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: S'abonner a AI-Pulse / Subscribe to AI-Pulse
description: Recevez votre digest personnalise par email
title: "[SUBSCRIBE] "
labels: ["subscription"]
body:
- type: input
id: email
attributes:
label: Votre email / Your email
placeholder: "you@example.com"
validations:
required: true
- type: checkboxes
id: categories
attributes:
label: Categories souhaitees / Desired categories
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The French word "souhaitees" is missing accents. It should be "souhaitées".

Suggested change
label: Categories souhaitees / Desired categories
label: Categories souhaitées / Desired categories

Copilot uses AI. Check for mistakes.
options:
- label: AI / IA
- label: Cybersecurity
- label: IoT
- label: Windows
- label: Mac / Apple
- label: Linux
- label: Tech Generale
- label: Entrepreneuriat
- label: Bourse & Finance
- label: Crypto & Blockchain
- label: Open Source & GitHub
- label: Produits & Innovation
- type: dropdown
id: language
attributes:
label: Langue preferee / Preferred language
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The French word "preferee" is missing accents. It should be "préférée".

Suggested change
label: Langue preferee / Preferred language
label: Langue préférée / Preferred language

Copilot uses AI. Check for mistakes.
options:
- Francais
- English
- Les deux / Both
validations:
required: true
- type: dropdown
id: frequency
attributes:
label: Frequence / Frequency
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The French word "Frequence" is missing an accent. It should be "Fréquence".

Suggested change
label: Frequence / Frequency
label: Fréquence / Frequency

Copilot uses AI. Check for mistakes.
options:
- Chaque mise a jour (toutes les 3h) / Every update
- Quotidien / Daily digest
validations:
required: true
122 changes: 122 additions & 0 deletions .github/workflows/add-source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Auto-add approved source

on:
issues:
types: [labeled]

permissions:
contents: write
issues: write

jobs:
add-source:
if: github.event.label.name == 'source-approved'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Parse issue and add source
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const issue = context.payload.issue;
const body = issue.body;

// Parse form fields from issue body
function getField(label) {
const regex = new RegExp(`### ${label}\\s*\\n\\s*(.+)`, 'i');
const match = body.match(regex);
return match ? match[1].trim() : '';
}

const sourceName = getField('Nom de la source / Source Name');
const sourceUrl = getField('URL du site / Website URL');
const rssUrl = getField('URL du flux RSS / RSS Feed URL');
const categoryRaw = getField('Categorie / Category');
const languageRaw = getField('Langue / Language');
const tags = getField('Tags / Mots-cles');

if (!sourceName || !rssUrl) {
console.log('Missing required fields');
return;
}

// Map category
const catMap = {
'AI / IA': 'ai',
'Cybersecurity / Cybersecurite': 'cybersecurity',
'IoT': 'iot',
'Windows': 'windows',
'Mac / Apple': 'mac',
'Linux': 'linux',
'Tech Generale': 'tech',
'Entrepreneuriat / Entrepreneurship': 'entrepreneurship',
'Bourse & Finance': 'finance',
'Crypto & Blockchain': 'crypto',
'Open Source & GitHub': 'opensource',
'Produits & Innovation': 'products'
};
const category = catMap[categoryRaw] || 'tech';

// Map language
const lang = languageRaw.includes('Francais') ? 'fr' : 'en';

// Parse tags
const tagList = tags ? tags.split(',').map(t => t.trim()).filter(t => t) : [category];

// Read and update config.json
const config = JSON.parse(fs.readFileSync('config.json', 'utf-8'));
if (!config.categories[category]) {
console.log(`Category ${category} not found`);
return;
}

// Check for duplicates
const exists = config.categories[category].feeds.some(f => f.url === rssUrl);
if (exists) {
console.log('Source already exists');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `This source (${rssUrl}) already exists in the ${category} category.`
});
return;
}

// Add new feed
config.categories[category].feeds.push({
name: sourceName,
url: rssUrl,
tags: tagList,
lang: lang
});
Comment on lines +88 to +94
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow parses user-supplied issue body content and directly uses it to update config.json without sufficient sanitization. The sourceName, rssUrl, and tags fields from user input are directly added to the configuration. This could allow malicious users to inject invalid data or potentially break the JSON structure. Consider adding validation to ensure these fields contain only expected characters and formats (e.g., URL validation for rssUrl, character limits and sanitization for sourceName).

Copilot uses AI. Check for mistakes.

fs.writeFileSync('config.json', JSON.stringify(config, null, 2));

// Comment on issue
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `Source "${sourceName}" added to category "${category}" in config.json. It will be active at the next aggregation cycle.`
});

// Close issue
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed'
});

- name: Commit changes
run: |
git config --global user.name 'PhoenixProject-AutoSync'
git config --global user.email '${{ secrets.GIT_AUTHOR_EMAIL }}'
git add config.json
if ! git diff --cached --exit-code; then
git commit -m "Add new source from issue #${{ github.event.issue.number }}"
git push
fi
Comment on lines +114 to +122
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow has write permissions to contents and issues, and automatically commits changes based on user input from issue forms. A malicious user could potentially craft an issue that, when labeled, causes the workflow to commit malicious content to config.json. While there is a label requirement ('source-approved'), repository maintainers should be extremely careful about which issues they label, as this triggers automatic code commits. Consider adding additional validation steps or requiring manual review before auto-committing, or implement a pull request workflow instead of direct commits.

Suggested change
- name: Commit changes
run: |
git config --global user.name 'PhoenixProject-AutoSync'
git config --global user.email '${{ secrets.GIT_AUTHOR_EMAIL }}'
git add config.json
if ! git diff --cached --exit-code; then
git commit -m "Add new source from issue #${{ github.event.issue.number }}"
git push
fi
- name: Create pull request for config update
uses: peter-evans/create-pull-request@v6
with:
commit-message: "Add new source from issue #${{ github.event.issue.number }}"
title: "Add new source from issue #${{ github.event.issue.number }}"
body: |
This pull request was automatically generated from issue #${{ github.event.issue.number }} after it was labeled 'source-approved'.
Please review the changes to config.json before merging.
branch: "auto/add-source-${{ github.event.issue.number }}"
add-paths: |
config.json

Copilot uses AI. Check for mistakes.
122 changes: 122 additions & 0 deletions .github/workflows/manage-subscriber.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Manage subscriber

on:
issues:
types: [labeled]

permissions:
contents: write
issues: write

jobs:
add-subscriber:
if: github.event.label.name == 'subscription'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Parse and add subscriber
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const issue = context.payload.issue;
const body = issue.body;

function getField(label) {
const regex = new RegExp(`### ${label}\\s*\\n\\s*(.+)`, 'i');
const match = body.match(regex);
return match ? match[1].trim() : '';
}

function getCheckboxes(label) {
const regex = new RegExp(`### ${label}\\s*\\n([\\s\\S]*?)(?=###|$)`, 'i');
const match = body.match(regex);
if (!match) return [];
return [...match[1].matchAll(/- \[X\] (.+)/gi)].map(m => m[1].trim());
}

const email = getField('Votre email / Your email');
if (!email || !email.includes('@')) {
console.log('Invalid email');
return;
}

Comment on lines +39 to +44
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow parses email addresses from issue bodies without proper validation. While there's a basic check for '@' presence, this is insufficient. The email should be validated using a proper regex pattern to ensure it's a valid email format. Additionally, there's no check to prevent email harvesting or abuse (e.g., rate limiting, CAPTCHA, or verification email). This could be exploited to add arbitrary email addresses to the subscriber list without consent.

Suggested change
const email = getField('Votre email / Your email');
if (!email || !email.includes('@')) {
console.log('Invalid email');
return;
}
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return (
typeof email === 'string' &&
email.length > 0 &&
email.length <= 254 &&
emailRegex.test(email)
);
}
// Basic abuse heuristic: ignore issues that appear to contain many email-like strings.
const atCount = (body.match(/@/g) || []).length;
if (atCount > 3) {
console.log('Suspicious issue body (too many email-like values); skipping subscription.');
return;
}
const rawEmail = getField('Votre email / Your email');
// In case multiple emails or extra text are provided, only keep the first token.
const email = rawEmail.split(/[,\s]+/).filter(Boolean)[0] || '';
if (!isValidEmail(email)) {
console.log('Invalid email:', rawEmail);
return;
}

Copilot uses AI. Check for mistakes.
const selectedCats = getCheckboxes('Categories souhaitees / Desired categories');
const langRaw = getField('Langue preferee / Preferred language');
const freqRaw = getField('Frequence / Frequency');

const catMap = {
'AI / IA': 'ai',
'Cybersecurity': 'cybersecurity',
'IoT': 'iot',
'Windows': 'windows',
'Mac / Apple': 'mac',
'Linux': 'linux',
'Tech Generale': 'tech',
'Entrepreneuriat': 'entrepreneurship',
'Bourse & Finance': 'finance',
'Crypto & Blockchain': 'crypto',
'Open Source & GitHub': 'opensource',
'Produits & Innovation': 'products'
};

const categories = selectedCats.map(c => catMap[c]).filter(Boolean);
const lang = langRaw.includes('Francais') ? 'fr' : langRaw.includes('English') ? 'en' : 'all';
const frequency = freqRaw.includes('Quotidien') ? 'daily' : 'each_update';

// Read subscribers
const subsPath = 'data/subscribers.json';
let subscribers = [];
try {
subscribers = JSON.parse(fs.readFileSync(subsPath, 'utf-8'));
} catch (e) {
subscribers = [];
}

// Check duplicate
const exists = subscribers.some(s => s.email === email);
if (exists) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `This email is already subscribed. To update preferences, please unsubscribe first and resubscribe.`
});
return;
}

// Add subscriber
subscribers.push({
email,
categories: categories.length > 0 ? categories : Object.values(catMap),
lang,
frequency,
subscribed_at: new Date().toISOString().split('T')[0]
});

fs.writeFileSync(subsPath, JSON.stringify(subscribers, null, 2));

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `Welcome! You've been subscribed to AI-Pulse digest.\n\nCategories: ${categories.join(', ') || 'All'}\nLanguage: ${lang}\nFrequency: ${frequency}\n\nYou will receive your personalized digest at your next update cycle.`
});

await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed'
});

- name: Commit changes
run: |
git config --global user.name 'PhoenixProject-AutoSync'
git config --global user.email '${{ secrets.GIT_AUTHOR_EMAIL }}'
git add data/subscribers.json
if ! git diff --cached --exit-code; then
git commit -m "Add subscriber from issue #${{ github.event.issue.number }}"
git push
fi
Loading