diff --git a/.github/workflows/dependabot-secure-flow.yaml b/.github/workflows/dependabot-secure-flow.yaml
index 4438faf82..90ad1cb77 100755
--- a/.github/workflows/dependabot-secure-flow.yaml
+++ b/.github/workflows/dependabot-secure-flow.yaml
@@ -43,12 +43,25 @@ jobs:
with:
fetch-depth: 0
- - name: Ensure security branch exists
+ - name: Ensure security branch exists and is up to date
run: |
- git fetch origin security 2>/dev/null || git switch --create security
- git push origin security || true
+ git fetch origin main
+ if git fetch origin security 2>/dev/null; then
+ # Security branch exists, check if it's behind main
+ git checkout security
+ BEHIND=$(git rev-list --count HEAD..origin/main)
+ if [ "$BEHIND" -gt 50 ]; then
+ echo "Security branch is $BEHIND commits behind main. Resetting to main..."
+ git reset --hard origin/main
+ fi
+ else
+ # Security branch doesn't exist, create from main
+ echo "Creating security branch from main..."
+ git checkout -b security origin/main
+ fi
+ git push origin security --force-with-lease || git push origin security || true
- - name: Merge dependabot changes to dependencies branch
+ - name: Merge dependabot changes to security branch
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
@@ -56,9 +69,16 @@ jobs:
# Fetch the PR branch
git fetch origin ${{ github.head_ref }}:${{ github.head_ref }} || true
- # Switch to security and merge
- git switch security
- git merge origin/${{ github.head_ref }} --no-edit || true
+ # Switch to security branch
+ git checkout security || git checkout -b security origin/main
+
+ # Try to merge, abort if conflicts
+ if ! git merge origin/${{ github.head_ref }} --no-edit; then
+ echo "Merge conflict detected, aborting and retrying with main as base..."
+ git merge --abort || true
+ git reset --hard origin/main
+ git merge origin/${{ github.head_ref }} --no-edit || true
+ fi
# Push to security
git push origin security
diff --git a/reader.html b/reader.html
index 17c51c657..42e4b7f8e 100644
--- a/reader.html
+++ b/reader.html
@@ -117,81 +117,42 @@
}
#view-article .iframe-container {
- padding-top: 48px;
+ padding-top: 0;
}
- .article-topbar {
- position: absolute;
- top: 8px;
- left: 10px;
- right: 10px;
- z-index: 2147483647;
- display: flex;
- gap: 8px;
- align-items: center;
- pointer-events: auto;
- }
-
- .article-topbar-btn {
- border: 1px solid rgba(255, 255, 255, 0.2);
- background: rgba(15, 23, 42, 0.84);
- color: #fff;
- font-size: 12px;
- font-weight: 600;
- border-radius: 10px;
- padding: 8px 12px;
- cursor: pointer;
- }
-
- .article-topbar-btn:hover {
- background: rgba(15, 23, 42, 0.95);
- }
-
- .back-to-previous {
- position: absolute;
- top: 12px;
- left: 12px;
- z-index: 40;
- background: rgba(15, 23, 42, 0.78);
- color: #fff;
- border: 1px solid rgba(255, 255, 255, 0.18);
- padding: 8px 12px;
- border-radius: 10px;
- font-size: 13px;
- cursor: pointer;
- backdrop-filter: blur(4px);
- }
-
- .back-to-previous:hover {
- background: rgba(15, 23, 42, 0.9);
- }
.reader-elevator {
position: fixed;
- right: 12px;
- top: 58px;
- bottom: auto;
+ right: 20px;
+ bottom: 20px;
z-index: 2147483646;
display: flex;
flex-direction: column;
- gap: 8px;
+ gap: 10px;
}
.reader-elevator-btn {
- width: 36px;
- height: 36px;
- border: 1px solid rgba(255, 255, 255, 0.22);
- border-radius: 10px;
- background: rgba(15, 23, 42, 0.82);
- color: #fff;
+ width: 40px;
+ height: 40px;
+ background: rgba(255, 255, 255, 0.05);
+ border: 1px solid rgba(0, 217, 255, 0.2);
+ border-radius: 50%;
+ color: #00d9ff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
cursor: pointer;
- font-size: 16px;
- line-height: 1;
- backdrop-filter: blur(4px);
+ backdrop-filter: blur(5px);
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+ font-size: 1.2rem;
}
.reader-elevator-btn:hover {
- background: rgba(15, 23, 42, 0.95);
+ background: #00d9ff;
+ color: white;
+ transform: scale(1.1);
+ box-shadow: 0 0 15px rgba(0, 217, 255, 0.5);
}
.article-mobile-footer {
@@ -332,16 +293,9 @@
padding: 12px 16px;
}
- .back-to-previous {
- top: 10px;
- font-size: 12px;
- padding: 7px 10px;
- }
-
.reader-elevator {
right: 10px;
- top: 56px;
- bottom: auto;
+ bottom: 10px;
}
.article-mobile-footer {
@@ -393,11 +347,6 @@
-
-
-
-
-
Chargement de l'article...
@@ -875,36 +824,6 @@
Impossible de charger l'article
});
}
- // Back to previous page in navigation history (fallback to list).
- const backToPreviousBtn = document.getElementById('backToPreviousBtn');
- const articleBackBtn = document.getElementById('articleBackBtn');
- const articleHomeBtn = document.getElementById('articleHomeBtn');
-
- function navigateBackOrHome() {
- if (window.history.length > 1) {
- window.history.back();
- return;
- }
- const fallbackUrl = new URL(window.location.href);
- fallbackUrl.search = '';
- fallbackUrl.hash = '';
- window.location.assign(fallbackUrl.toString());
- }
-
- if (backToPreviousBtn) {
- backToPreviousBtn.addEventListener('click', navigateBackOrHome);
- }
- if (articleBackBtn) {
- articleBackBtn.addEventListener('click', navigateBackOrHome);
- }
- if (articleHomeBtn) {
- articleHomeBtn.addEventListener('click', () => {
- const fallbackUrl = new URL(window.location.href);
- fallbackUrl.search = '';
- fallbackUrl.hash = '';
- window.location.assign(fallbackUrl.toString());
- });
- }
function scrollArticleFrame(direction) {
const iframe = document.getElementById('articleFrame');
diff --git a/src/aggregator.js b/src/aggregator.js
index 68647f5a5..3a4f600ff 100644
--- a/src/aggregator.js
+++ b/src/aggregator.js
@@ -260,6 +260,14 @@ function addUTMParams(url, category = 'general') {
if (mediumHosts.includes(hostname)) {
url = `https://freedium.cloud/${url}`;
}
+
+ // Liste des domaines avec paywalls stricts
+ const paywalledHosts = ['ft.com', 'wsj.com', 'economist.com', 'bloomberg.com', 'investing.com'];
+
+ // Ajouter Archive.ph en query parameter pour fallback
+ if (paywalledHosts.some(host => hostname.includes(host))) {
+ // On ne change pas l'URL ici, on l'utilisera comme fallback
+ }
} catch (e) {
// Erreur de parsing URL, on continue sans modification
}
@@ -1408,9 +1416,42 @@ async function processArticle(article, sourceName, tags, category, feedLang) {
}
const lang = detectedLang || feedLang || 'en';
+ // Essayer d'utiliser Archive.ph ou autres services de bypass
+ const tryPaywallBypass = async (url) => {
+ const bypassServices = [
+ {
+ name: 'archive.ph',
+ transform: (u) => `https://archive.ph/?url=${encodeURIComponent(u)}`
+ },
+ {
+ name: 'scribe.rip',
+ transform: (u) => u.includes('medium.com') ? u.replace('medium.com', 'scribe.rip') : null
+ },
+ {
+ name: 'web.archive.org',
+ transform: (u) => `https://web.archive.org/web/*/${u}`
+ }
+ ];
+
+ for (const service of bypassServices) {
+ const bypassUrl = service.transform(url);
+ if (!bypassUrl) continue;
+ try {
+ const response = await axios.get(bypassUrl, {
+ timeout: 5000,
+ headers: { 'User-Agent': 'AI-Pulse/3.0' }
+ });
+ return { success: true, html: response.data, service: service.name };
+ } catch (e) {
+ // Continuer vers le service suivant
+ }
+ }
+ return { success: false };
+ };
+
const writeFallbackLocalArticle = () => {
const safeTitle = sanitizeText(article.title) || 'Untitled';
- const safeSummary = smartTruncate(cleanupNoiseText(rawSummary || ''), 1200) || 'Summary unavailable for this article.';
+ const safeSummary = smartTruncate(cleanupNoiseText(rawSummary || ''), 1200) || (rawSummary ? sanitizeText(rawSummary) : 'Summary unavailable for this article.');
const fallbackHtml = `
@@ -1518,11 +1559,26 @@ async function processArticle(article, sourceName, tags, category, feedLang) {
if (articleContent && articleContent.textContent) {
if (isPaywallText(articleContent.textContent)) {
- writeFallbackLocalArticle();
- } else {
- if (isLikelyBoilerplateExtraction(articleContent.textContent)) {
- writeFallbackLocalArticle();
+ // Essayer les services de bypass avant de renoncer
+ const bypassResult = await tryPaywallBypass(resolvedArticleUrl);
+ if (bypassResult.success) {
+ const bypassDom = createSafeDom(bypassResult.html, resolvedArticleUrl);
+ const bypassReader = new Readability(bypassDom.window.document);
+ const bypassContent = bypassReader.parse();
+ if (bypassContent && bypassContent.textContent && !isPaywallText(bypassContent.textContent) && bypassContent.textContent.length > 200) {
+ articleContent = bypassContent;
+ // Continuer le traitement normal avec le contenu bypassed
+ } else {
+ writeFallbackLocalArticle();
+ }
+ } else {
+ writeFallbackLocalArticle();
+ }
} else {
+ // Contenu normal sans paywall
+ if (isLikelyBoilerplateExtraction(articleContent.textContent)) {
+ writeFallbackLocalArticle();
+ } else {
if (!computedSummary || computedSummary.trim().length < 20) {
computedSummary = trimPromotionalTailText(cleanupNoiseText(sanitizeText(articleContent.textContent.slice(0, 1400))));
}
@@ -1633,10 +1689,10 @@ async function processArticle(article, sourceName, tags, category, feedLang) {
// Sauvegarder le fichier HTML localement
fs.writeFileSync(localPath, cleanHtml);
+ }
}
}
}
- }
} catch (e) {
if (!shouldSuppressExtractionLog(resolvedArticleUrl, e)) {
console.error(` Could not extract content for: ${articleUrl}`);