From b62581bccf5c75cea15e78638de63e4c685a057f Mon Sep 17 00:00:00 2001 From: fkrebs Date: Thu, 28 May 2026 00:31:09 +0200 Subject: [PATCH] paywall-bypass: replace Wallabag with crawl4ai, add Unpaywall OA lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Wallabag token node; fetch full text directly via crawl4ai /md. Scientific articles DOI-resolved via Unpaywall API for open-access URLs. Adds title emoji suffix: 🔓 (OA), 📖 (crawl4ai), ❌ (failed). Includes backfill variant (no time cutoff) for processing existing unread entries. --- flows/paywall-bypass-backfill.json | 56 ++---------------------------- flows/paywall-bypass.json | 54 +--------------------------- 2 files changed, 3 insertions(+), 107 deletions(-) diff --git a/flows/paywall-bypass-backfill.json b/flows/paywall-bypass-backfill.json index c47e106..209cd32 100644 --- a/flows/paywall-bypass-backfill.json +++ b/flows/paywall-bypass-backfill.json @@ -12,47 +12,6 @@ ], "parameters": {} }, - { - "id": "1a8da39b-1d21-4d64-9fed-4306e2f3eaf9", - "name": "Wallabag Token", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.2, - "position": [ - -580, - 0 - ], - "parameters": { - "method": "POST", - "url": "http://192.168.1.40:17004/oauth/v2/token", - "sendBody": true, - "contentType": "form-urlencoded", - "bodyParameters": { - "parameters": [ - { - "name": "grant_type", - "value": "password" - }, - { - "name": "client_id", - "value": "1_nuclide2026" - }, - { - "name": "client_secret", - "value": "nuclide-api-secret-2026" - }, - { - "name": "username", - "value": "wallabag" - }, - { - "name": "password", - "value": "tapirnase" - } - ] - }, - "options": {} - } - }, { "id": "1dda2695-81a1-4fab-a7cf-095f441862eb", "name": "Get Miniflux Entries", @@ -109,22 +68,11 @@ ], "parameters": { "mode": "runOnceForAllItems", - "jsCode": "const MINIFLUX_TOKEN = '3f439f2db55967ed5acd83398214d2aa76124927cde07115d945b9282597fecb';\nconst WALLABAG_URL = 'http://192.168.1.40:17004';\nconst MINIFLUX_URL = 'http://192.168.1.40:17002';\n\nconst PAYWALL_NEWS = [\n 'spiegel.de','zeit.de','faz.net','sueddeutsche.de','handelsblatt.com',\n 'nytimes.com','wsj.com','ft.com','washingtonpost.com','thetimes.co.uk',\n 'wired.com','theatlantic.com','economist.com','bloomberg.com',\n 'hbr.org','foreignpolicy.com','newyorker.com','taz.de',\n 'telegraph.co.uk','businessinsider.com'\n];\n\nconst PAYWALL_SCIENCE = [\n 'nature.com','science.org','cell.com','nejm.org','thelancet.com',\n 'link.springer.com','springer.com','wiley.com','onlinelibrary.wiley.com',\n 'sciencedirect.com','pubs.acs.org','rsc.org','pubs.rsc.org',\n 'bmj.com','annualreviews.org','pnas.org',\n 'jamanetwork.com','ahajournals.org','tandfonline.com','academic.oup.com'\n];\n\nconst CONTENT_MIN = 800;\nfunction extractDoi(url) {\n let m = url.match(/doi\\.org\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1]);\n m = url.match(/\\/doi\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1].replace(/[?#].*$/, ''));\n return null;\n}\n\nfunction hostname(url) {\n try { return new URL(url).hostname.replace(/^www\\./, ''); } catch { return ''; }\n}\n\nfunction badge(source, type) {\n if (source === 'unpaywall') return '

\ud83d\udcda Open access via Unpaywall

';\n if (type === 'science') return '

\ud83d\udcda Full text via Wallabag (not OA)

';\n return '

\ud83d\udcd6 Paywall bypass via Wallabag

';\n}\n\nconst wbToken = $('Wallabag Token').first().json.access_token;\nconst entries = $('Get Miniflux Entries').first().json.entries || [];\n\nconst results = [];\n\nfor (const e of entries) {\n const host = hostname(e.url);\n const isScience = PAYWALL_SCIENCE.some(d => host === d || host.endsWith('.' + d));\n const isNews = PAYWALL_NEWS.some(d => host === d || host.endsWith('.' + d));\n const isShort = (e.content || '').replace(/<[^>]+>/g, '').trim().length < CONTENT_MIN;\n\n if (!isScience && !isNews && !isShort) continue;\n\n const type = isScience ? 'science' : isNews ? 'news' : 'short';\n let fetchUrl = e.url;\n let source = 'wallabag';\n\n // Try Unpaywall for scientific articles with a DOI\n if (isScience) {\n const doi = extractDoi(e.url);\n if (doi) {\n try {\n const up = await $helpers.httpRequest({\n method: 'GET',\n url: `https://api.unpaywall.org/v2/${encodeURIComponent(doi)}?email=fkrebs@nucli.de`,\n });\n if (up.is_oa && up.best_oa_location) {\n const oaUrl = up.best_oa_location.url || up.best_oa_location.url_for_pdf;\n if (oaUrl) { fetchUrl = oaUrl; source = 'unpaywall'; }\n }\n } catch (_) {}\n }\n }\n\n // Fetch full text via Wallabag\n let wbContent = '';\n try {\n const wb = await $helpers.httpRequest({\n method: 'POST',\n url: `${WALLABAG_URL}/api/entries`,\n headers: { 'Authorization': `Bearer ${wbToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ url: fetchUrl }),\n });\n wbContent = wb.content || '';\n } catch (_) {}\n\n if (!wbContent) {\n // Mark title with failure emoji\n const failTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' \u274c';\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ title: failTitle }),\n });\n } catch (_) {}\n results.push({ json: { entry_id: e.id, title: failTitle, status: 'no_content', source, type } });\n continue;\n }\n\n const finalContent = badge(source, type) + wbContent;\n const titleEmoji = source === 'unpaywall' ? '\ud83d\udd13' : '\ud83d\udcd6';\n const newTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' ' + titleEmoji;\n\n // Update Miniflux entry content + title\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ content: finalContent, title: newTitle }),\n });\n results.push({ json: { entry_id: e.id, title: newTitle, status: 'updated', source, type } });\n } catch (err) {\n results.push({ json: { entry_id: e.id, title: e.title, status: 'update_error', error: String(err), source, type } });\n }\n}\n\nif (results.length === 0) return [{ json: { status: 'no_candidates', checked: entries.length } }];\nreturn results;" + "jsCode": "const MINIFLUX_TOKEN = '3f439f2db55967ed5acd83398214d2aa76124927cde07115d945b9282597fecb';\nconst MINIFLUX_URL = 'http://192.168.1.40:17002';\n\nconst PAYWALL_NEWS = [\n 'spiegel.de','zeit.de','faz.net','sueddeutsche.de','handelsblatt.com',\n 'nytimes.com','wsj.com','ft.com','washingtonpost.com','thetimes.co.uk',\n 'wired.com','theatlantic.com','economist.com','bloomberg.com',\n 'hbr.org','foreignpolicy.com','newyorker.com','taz.de',\n 'telegraph.co.uk','businessinsider.com'\n];\n\nconst PAYWALL_SCIENCE = [\n 'nature.com','science.org','cell.com','nejm.org','thelancet.com',\n 'link.springer.com','springer.com','wiley.com','onlinelibrary.wiley.com',\n 'sciencedirect.com','pubs.acs.org','rsc.org','pubs.rsc.org',\n 'bmj.com','annualreviews.org','pnas.org',\n 'jamanetwork.com','ahajournals.org','tandfonline.com','academic.oup.com'\n];\n\nconst CONTENT_MIN = 800;\nfunction extractDoi(url) {\n let m = url.match(/doi\\.org\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1]);\n m = url.match(/\\/doi\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1].replace(/[?#].*$/, ''));\n return null;\n}\n\nfunction hostname(url) {\n try { return new URL(url).hostname.replace(/^www\\./, ''); } catch { return ''; }\n}\n\nfunction badge(source, type) {\n if (source === 'unpaywall') return '

\ud83d\udcda Open access via Unpaywall

';\n if (type === 'science') return '

\ud83d\udcda Full text via Wallabag (not OA)

';\n return '

\ud83d\udcd6 Paywall bypass via Wallabag

';\n}\n\nconst entries = $('Get Miniflux Entries').first().json.entries || [];\n\nconst results = [];\n\nfor (const e of entries) {\n const host = hostname(e.url);\n const isScience = PAYWALL_SCIENCE.some(d => host === d || host.endsWith('.' + d));\n const isNews = PAYWALL_NEWS.some(d => host === d || host.endsWith('.' + d));\n const isShort = (e.content || '').replace(/<[^>]+>/g, '').trim().length < CONTENT_MIN;\n\n if (!isScience && !isNews && !isShort) continue;\n\n const type = isScience ? 'science' : isNews ? 'news' : 'short';\n let fetchUrl = e.url;\n let source = 'wallabag';\n\n // Try Unpaywall for scientific articles with a DOI\n if (isScience) {\n const doi = extractDoi(e.url);\n if (doi) {\n try {\n const up = await $helpers.httpRequest({\n method: 'GET',\n url: `https://api.unpaywall.org/v2/${encodeURIComponent(doi)}?email=fkrebs@nucli.de`,\n });\n if (up.is_oa && up.best_oa_location) {\n const oaUrl = up.best_oa_location.url || up.best_oa_location.url_for_pdf;\n if (oaUrl) { fetchUrl = oaUrl; source = 'unpaywall'; }\n }\n } catch (_) {}\n }\n }\n\n // Fetch full text via crawl4ai\n let wbContent = '';\n try {\n const cr = await $helpers.httpRequest({\n method: 'POST',\n url: 'http://mcp-crawl4ai:11235/md',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ url: fetchUrl, filter: 'fit' }),\n });\n if (cr.success && cr.markdown) wbContent = '

' + cr.markdown.replace(/\\n\\n/g, '

').replace(/\\n/g, '
') + '

';\n } catch (_) {}\n\n if (!wbContent) {\n // Mark title with failure emoji\n const failTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' \u274c';\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ title: failTitle }),\n });\n } catch (_) {}\n results.push({ json: { entry_id: e.id, title: failTitle, status: 'no_content', source, type } });\n continue;\n }\n\n const finalContent = badge(source, type) + wbContent;\n const titleEmoji = source === 'unpaywall' ? '\ud83d\udd13' : '\ud83d\udcd6';\n const newTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' ' + titleEmoji;\n\n // Update Miniflux entry content + title\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ content: finalContent, title: newTitle }),\n });\n results.push({ json: { entry_id: e.id, title: newTitle, status: 'updated', source, type } });\n } catch (err) {\n results.push({ json: { entry_id: e.id, title: e.title, status: 'update_error', error: String(err), source, type } });\n }\n}\n\nif (results.length === 0) return [{ json: { status: 'no_candidates', checked: entries.length } }];\nreturn results;" } } ], "connections": { - "Wallabag Token": { - "main": [ - [ - { - "node": "Get Miniflux Entries", - "type": "main", - "index": 0 - } - ] - ] - }, "Get Miniflux Entries": { "main": [ [ @@ -140,7 +88,7 @@ "main": [ [ { - "node": "Wallabag Token", + "node": "Get Miniflux Entries", "type": "main", "index": 0 } diff --git a/flows/paywall-bypass.json b/flows/paywall-bypass.json index 7198dbb..c61a1ab 100644 --- a/flows/paywall-bypass.json +++ b/flows/paywall-bypass.json @@ -21,47 +21,6 @@ } } }, - { - "id": "70be11ba-e47d-4536-9326-3988fb8d4b50", - "name": "Wallabag Token", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.2, - "position": [ - -580, - 0 - ], - "parameters": { - "method": "POST", - "url": "http://192.168.1.40:17004/oauth/v2/token", - "sendBody": true, - "contentType": "form-urlencoded", - "bodyParameters": { - "parameters": [ - { - "name": "grant_type", - "value": "password" - }, - { - "name": "client_id", - "value": "1_nuclide2026" - }, - { - "name": "client_secret", - "value": "nuclide-api-secret-2026" - }, - { - "name": "username", - "value": "wallabag" - }, - { - "name": "password", - "value": "tapirnase" - } - ] - }, - "options": {} - } - }, { "id": "eb644570-cca3-4464-9b99-02084f199078", "name": "Get Miniflux Entries", @@ -118,23 +77,12 @@ ], "parameters": { "mode": "runOnceForAllItems", - "jsCode": "const MINIFLUX_TOKEN = '3f439f2db55967ed5acd83398214d2aa76124927cde07115d945b9282597fecb';\nconst WALLABAG_URL = 'http://192.168.1.40:17004';\nconst MINIFLUX_URL = 'http://192.168.1.40:17002';\n\nconst PAYWALL_NEWS = [\n 'spiegel.de','zeit.de','faz.net','sueddeutsche.de','handelsblatt.com',\n 'nytimes.com','wsj.com','ft.com','washingtonpost.com','thetimes.co.uk',\n 'wired.com','theatlantic.com','economist.com','bloomberg.com',\n 'hbr.org','foreignpolicy.com','newyorker.com','taz.de',\n 'telegraph.co.uk','businessinsider.com'\n];\n\nconst PAYWALL_SCIENCE = [\n 'nature.com','science.org','cell.com','nejm.org','thelancet.com',\n 'link.springer.com','springer.com','wiley.com','onlinelibrary.wiley.com',\n 'sciencedirect.com','pubs.acs.org','rsc.org','pubs.rsc.org',\n 'bmj.com','annualreviews.org','pnas.org',\n 'jamanetwork.com','ahajournals.org','tandfonline.com','academic.oup.com'\n];\n\nconst CONTENT_MIN = 800;\nconst cutoff = (Date.now() / 1000) - 360;\n\nfunction extractDoi(url) {\n let m = url.match(/doi\\.org\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1]);\n m = url.match(/\\/doi\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1].replace(/[?#].*$/, ''));\n return null;\n}\n\nfunction hostname(url) {\n try { return new URL(url).hostname.replace(/^www\\./, ''); } catch { return ''; }\n}\n\nfunction badge(source, type) {\n if (source === 'unpaywall') return '

\ud83d\udcda Open access via Unpaywall

';\n if (type === 'science') return '

\ud83d\udcda Full text via Wallabag (not OA)

';\n return '

\ud83d\udcd6 Paywall bypass via Wallabag

';\n}\n\nconst wbToken = $('Wallabag Token').first().json.access_token;\nconst entries = $('Get Miniflux Entries').first().json.entries || [];\n\nconst results = [];\n\nfor (const e of entries) {\n const pub = new Date(e.published_at).getTime() / 1000;\n if (pub < cutoff) continue;\n\n const host = hostname(e.url);\n const isScience = PAYWALL_SCIENCE.some(d => host === d || host.endsWith('.' + d));\n const isNews = PAYWALL_NEWS.some(d => host === d || host.endsWith('.' + d));\n const isShort = (e.content || '').replace(/<[^>]+>/g, '').trim().length < CONTENT_MIN;\n\n if (!isScience && !isNews && !isShort) continue;\n\n const type = isScience ? 'science' : isNews ? 'news' : 'short';\n let fetchUrl = e.url;\n let source = 'wallabag';\n\n // Try Unpaywall for scientific articles with a DOI\n if (isScience) {\n const doi = extractDoi(e.url);\n if (doi) {\n try {\n const up = await $helpers.httpRequest({\n method: 'GET',\n url: `https://api.unpaywall.org/v2/${encodeURIComponent(doi)}?email=fkrebs@nucli.de`,\n });\n if (up.is_oa && up.best_oa_location) {\n const oaUrl = up.best_oa_location.url || up.best_oa_location.url_for_pdf;\n if (oaUrl) { fetchUrl = oaUrl; source = 'unpaywall'; }\n }\n } catch (_) {}\n }\n }\n\n // Fetch full text via Wallabag\n let wbContent = '';\n try {\n const wb = await $helpers.httpRequest({\n method: 'POST',\n url: `${WALLABAG_URL}/api/entries`,\n headers: { 'Authorization': `Bearer ${wbToken}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({ url: fetchUrl }),\n });\n wbContent = wb.content || '';\n } catch (_) {}\n\n if (!wbContent) {\n // Mark title with failure emoji\n const failTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' \u274c';\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ title: failTitle }),\n });\n } catch (_) {}\n results.push({ json: { entry_id: e.id, title: failTitle, status: 'no_content', source, type } });\n continue;\n }\n\n const finalContent = badge(source, type) + wbContent;\n const titleEmoji = source === 'unpaywall' ? '\ud83d\udd13' : '\ud83d\udcd6';\n const newTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' ' + titleEmoji;\n\n // Update Miniflux entry content + title\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ content: finalContent, title: newTitle }),\n });\n results.push({ json: { entry_id: e.id, title: newTitle, status: 'updated', source, type } });\n } catch (err) {\n results.push({ json: { entry_id: e.id, title: e.title, status: 'update_error', error: String(err), source, type } });\n }\n}\n\nif (results.length === 0) return [{ json: { status: 'no_candidates', checked: entries.length } }];\nreturn results;" + "jsCode": "const MINIFLUX_TOKEN = '3f439f2db55967ed5acd83398214d2aa76124927cde07115d945b9282597fecb';\nconst MINIFLUX_URL = 'http://192.168.1.40:17002';\n\nconst PAYWALL_NEWS = [\n 'spiegel.de','zeit.de','faz.net','sueddeutsche.de','handelsblatt.com',\n 'nytimes.com','wsj.com','ft.com','washingtonpost.com','thetimes.co.uk',\n 'wired.com','theatlantic.com','economist.com','bloomberg.com',\n 'hbr.org','foreignpolicy.com','newyorker.com','taz.de',\n 'telegraph.co.uk','businessinsider.com'\n];\n\nconst PAYWALL_SCIENCE = [\n 'nature.com','science.org','cell.com','nejm.org','thelancet.com',\n 'link.springer.com','springer.com','wiley.com','onlinelibrary.wiley.com',\n 'sciencedirect.com','pubs.acs.org','rsc.org','pubs.rsc.org',\n 'bmj.com','annualreviews.org','pnas.org',\n 'jamanetwork.com','ahajournals.org','tandfonline.com','academic.oup.com'\n];\n\nconst CONTENT_MIN = 800;\nconst cutoff = (Date.now() / 1000) - 360;\n\nfunction extractDoi(url) {\n let m = url.match(/doi\\.org\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1]);\n m = url.match(/\\/doi\\/(10\\.\\d{4,}\\/[^\\s?#]+)/);\n if (m) return decodeURIComponent(m[1].replace(/[?#].*$/, ''));\n return null;\n}\n\nfunction hostname(url) {\n try { return new URL(url).hostname.replace(/^www\\./, ''); } catch { return ''; }\n}\n\nfunction badge(source, type) {\n if (source === 'unpaywall') return '

\ud83d\udcda Open access via Unpaywall

';\n if (type === 'science') return '

\ud83d\udcda Full text via Wallabag (not OA)

';\n return '

\ud83d\udcd6 Paywall bypass via Wallabag

';\n}\n\nconst entries = $('Get Miniflux Entries').first().json.entries || [];\n\nconst results = [];\n\nfor (const e of entries) {\n const pub = new Date(e.published_at).getTime() / 1000;\n if (pub < cutoff) continue;\n\n const host = hostname(e.url);\n const isScience = PAYWALL_SCIENCE.some(d => host === d || host.endsWith('.' + d));\n const isNews = PAYWALL_NEWS.some(d => host === d || host.endsWith('.' + d));\n const isShort = (e.content || '').replace(/<[^>]+>/g, '').trim().length < CONTENT_MIN;\n\n if (!isScience && !isNews && !isShort) continue;\n\n const type = isScience ? 'science' : isNews ? 'news' : 'short';\n let fetchUrl = e.url;\n let source = 'wallabag';\n\n // Try Unpaywall for scientific articles with a DOI\n if (isScience) {\n const doi = extractDoi(e.url);\n if (doi) {\n try {\n const up = await $helpers.httpRequest({\n method: 'GET',\n url: `https://api.unpaywall.org/v2/${encodeURIComponent(doi)}?email=fkrebs@nucli.de`,\n });\n if (up.is_oa && up.best_oa_location) {\n const oaUrl = up.best_oa_location.url || up.best_oa_location.url_for_pdf;\n if (oaUrl) { fetchUrl = oaUrl; source = 'unpaywall'; }\n }\n } catch (_) {}\n }\n }\n\n // Fetch full text via crawl4ai\n let wbContent = '';\n try {\n const cr = await $helpers.httpRequest({\n method: 'POST',\n url: 'http://mcp-crawl4ai:11235/md',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ url: fetchUrl, filter: 'fit' }),\n });\n if (cr.success && cr.markdown) wbContent = '

' + cr.markdown.replace(/\\n\\n/g, '

').replace(/\\n/g, '
') + '

';\n } catch (_) {}\n\n if (!wbContent) {\n // Mark title with failure emoji\n const failTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' \u274c';\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ title: failTitle }),\n });\n } catch (_) {}\n results.push({ json: { entry_id: e.id, title: failTitle, status: 'no_content', source, type } });\n continue;\n }\n\n const finalContent = badge(source, type) + wbContent;\n const titleEmoji = source === 'unpaywall' ? '\ud83d\udd13' : '\ud83d\udcd6';\n const newTitle = e.title.replace(/ [\ud83d\udd13\ud83d\udcd6\ud83d\udd12\u23f3\u274c]$/, '') + ' ' + titleEmoji;\n\n // Update Miniflux entry content + title\n try {\n await $helpers.httpRequest({\n method: 'PUT',\n url: `${MINIFLUX_URL}/v1/entries/${e.id}`,\n headers: { 'X-Auth-Token': MINIFLUX_TOKEN, 'Content-Type': 'application/json' },\n body: JSON.stringify({ content: finalContent, title: newTitle }),\n });\n results.push({ json: { entry_id: e.id, title: newTitle, status: 'updated', source, type } });\n } catch (err) {\n results.push({ json: { entry_id: e.id, title: e.title, status: 'update_error', error: String(err), source, type } });\n }\n}\n\nif (results.length === 0) return [{ json: { status: 'no_candidates', checked: entries.length } }];\nreturn results;" } } ], "connections": { "Every 5 Minutes": { - "main": [ - [ - { - "node": "Wallabag Token", - "type": "main", - "index": 0 - } - ] - ] - }, - "Wallabag Token": { "main": [ [ {