From 30fb3b4a4136e1510055d5e74ef26e60b876c2fe Mon Sep 17 00:00:00 2001 From: Simon Lecoq <22963968+lowlighter@users.noreply.github.com> Date: Sun, 21 Mar 2021 12:14:05 +0100 Subject: [PATCH] Add RSS plugin (#192) --- package-lock.json | 19 +++++++++++ package.json | 1 + source/app/metrics/utils.mjs | 3 +- source/app/mocks/api/axios/get/rss.mjs | 22 ++++++++++++ source/app/web/statics/app.placeholder.js | 12 +++++++ source/plugins/rss/README.md | 23 +++++++++++++ source/plugins/rss/index.mjs | 34 +++++++++++++++++++ source/plugins/rss/metadata.yml | 30 +++++++++++++++++ source/plugins/rss/tests.yml | 6 ++++ source/templates/classic/partials/_.json | 1 + source/templates/classic/partials/rss.ejs | 35 ++++++++++++++++++++ source/templates/classic/style.css | 12 +++++++ source/templates/repository/partials/_.json | 3 +- source/templates/repository/partials/rss.ejs | 35 ++++++++++++++++++++ 14 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 source/app/mocks/api/axios/get/rss.mjs create mode 100644 source/plugins/rss/README.md create mode 100644 source/plugins/rss/index.mjs create mode 100644 source/plugins/rss/metadata.yml create mode 100644 source/plugins/rss/tests.yml create mode 100644 source/templates/classic/partials/rss.ejs create mode 100644 source/templates/repository/partials/rss.ejs diff --git a/package-lock.json b/package-lock.json index dd1e39c5..5a2d33d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "open-graph-scraper": "^4.7.1", "prismjs": "^1.23.0", "puppeteer": "^8.0.0", + "rss-parser": "^3.12.0", "simple-git": "^2.36.0", "svgo": "^2.2.0", "twemoji-parser": "^13.0.0", @@ -9475,6 +9476,15 @@ "rimraf": "bin.js" } }, + "node_modules/rss-parser": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.12.0.tgz", + "integrity": "sha512-aqD3E8iavcCdkhVxNDIdg1nkBI17jgqF+9OqPS1orwNaOgySdpvq6B+DoONLhzjzwV8mWg37sb60e4bmLK117A==", + "dependencies": { + "entities": "^2.0.3", + "xml2js": "^0.4.19" + } + }, "node_modules/rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -19408,6 +19418,15 @@ "glob": "^7.1.3" } }, + "rss-parser": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.12.0.tgz", + "integrity": "sha512-aqD3E8iavcCdkhVxNDIdg1nkBI17jgqF+9OqPS1orwNaOgySdpvq6B+DoONLhzjzwV8mWg37sb60e4bmLK117A==", + "requires": { + "entities": "^2.0.3", + "xml2js": "^0.4.19" + } + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", diff --git a/package.json b/package.json index d6f2d1dd..1ab8d0c0 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "open-graph-scraper": "^4.7.1", "prismjs": "^1.23.0", "puppeteer": "^8.0.0", + "rss-parser": "^3.12.0", "simple-git": "^2.36.0", "svgo": "^2.2.0", "twemoji-parser": "^13.0.0", diff --git a/source/app/metrics/utils.mjs b/source/app/metrics/utils.mjs index d071c046..ea639a79 100644 --- a/source/app/metrics/utils.mjs +++ b/source/app/metrics/utils.mjs @@ -11,9 +11,10 @@ import twemojis from "twemoji-parser" import jimp from "jimp" import opengraph from "open-graph-scraper" + import rss from "rss-parser" //Exports - export {fs, os, paths, url, util, processes, axios, puppeteer, git, opengraph} + export {fs, os, paths, url, util, processes, axios, puppeteer, git, opengraph, rss} /**Returns module __dirname */ export function __module(module) { diff --git a/source/app/mocks/api/axios/get/rss.mjs b/source/app/mocks/api/axios/get/rss.mjs new file mode 100644 index 00000000..8f0083f6 --- /dev/null +++ b/source/app/mocks/api/axios/get/rss.mjs @@ -0,0 +1,22 @@ +/**Mocked data */ + export default function({faker, url, options, login = faker.internet.userName()}) { + //Stackoverflow api + if (/^https:..example.org.rss$/.test(url)) { + console.debug(`metrics/compute/mocks > mocking rss feed result > ${url}`) + return ({ + status:200, + data:{ + items:new Array(30).fill(null).map(_ => ({ + title:faker.lorem.sentence(), + link:faker.internet.url(), + content:faker.lorem.paragraphs(), + contentSnippet:faker.lorem.paragraph(), + isoDate:faker.date.recent(), + })), + title:faker.lorem.sentence(), + description:faker.lorem.paragraph(), + link:url, + }, + }) + } + } \ No newline at end of file diff --git a/source/app/web/statics/app.placeholder.js b/source/app/web/statics/app.placeholder.js index 1e00621a..9e3eae4c 100644 --- a/source/app/web/statics/app.placeholder.js +++ b/source/app/web/statics/app.placeholder.js @@ -230,6 +230,18 @@ favorites:distribution(7).map((value, index, array) => ({name:faker.lorem.word(), color:faker.internet.color(), value, size:faker.random.number(1000000), x:array.slice(0, index).reduce((a, b) => a + b, 0)})) } }) : null), + //Languages + ...(set.plugins.enabled.rss ? ({ + rss:{ + source:faker.lorem.words(), + description:faker.lorem.paragraph(), + link:options["rss.source"], + feed:new Array(Number(options["rss.limit"])).fill(null).map(_ => ({ + title:faker.lorem.sentence(), + date:faker.date.recent() + })), + } + }) : null), //Habits ...(set.plugins.enabled.habits ? ({ habits:{ diff --git a/source/plugins/rss/README.md b/source/plugins/rss/README.md new file mode 100644 index 00000000..d451f452 --- /dev/null +++ b/source/plugins/rss/README.md @@ -0,0 +1,23 @@ +### 🗼 Rss feed + +The *rss* plugin displays items from a specified RSS feed. + + + +
+ + +
+ +#### ℹ️ Examples workflows + +[➡️ Available options for this plugin](metadata.yml) + +```yaml +- uses: lowlighter/metrics@latest + with: + # ... other options + plugin_rss: yes + plugin_rss_source: https://news.ycombinator.com/rss # RSS feed + plugin_rss_limit: 6 # Limit to 6 items +``` \ No newline at end of file diff --git a/source/plugins/rss/index.mjs b/source/plugins/rss/index.mjs new file mode 100644 index 00000000..860cd320 --- /dev/null +++ b/source/plugins/rss/index.mjs @@ -0,0 +1,34 @@ + +//Setup + export default async function({login, q, imports, data, account}, {enabled = false} = {}) { + //Plugin execution + try { + //Check if plugin is enabled and requirements are met + if ((!enabled)||(!q.rss)) + return null + + //Load inputs + let {source, limit} = imports.metadata.plugins.rss.inputs({data, account, q}) + if (!source) + throw {error:{message:"A RSS feed is required"}} + + //Load rss feed + const {title, description, link, items} = await (new imports.rss()).parseURL(source) //eslint-disable-line new-cap + const feed = items.map(({title, isoDate:date}) => ({title, date:new Date(date)})) + + //Limit feed + if (limit > 0) { + console.debug(`metrics/compute/${login}/plugins > rss > keeping only ${limit} items`) + feed.splice(limit) + } + + //Results + return {source:title, description, link, feed} + } + //Handle errors + catch (error) { + if (error.error?.message) + throw error + throw {error:{message:"An error occured", instance:error}} + } + } \ No newline at end of file diff --git a/source/plugins/rss/metadata.yml b/source/plugins/rss/metadata.yml new file mode 100644 index 00000000..2e058cd6 --- /dev/null +++ b/source/plugins/rss/metadata.yml @@ -0,0 +1,30 @@ +name: "🗼 Rss feed" +cost: N/A +categorie: social +index: 6.5 +supports: + - user + - organization + - repository +inputs: + + # Enable or disable plugin + plugin_rss: + description: Display RSS feed + type: boolean + default: no + + # RSS feed source + plugin_rss_source: + description: RSS feed source + type: string + default: "" + + # Number of items to display + # Set to 0 to disable limitations + plugin_rss_limit: + description: Maximum number of items to display + type: number + default: 4 + min: 0 + max: 30 \ No newline at end of file diff --git a/source/plugins/rss/tests.yml b/source/plugins/rss/tests.yml new file mode 100644 index 00000000..51f70b03 --- /dev/null +++ b/source/plugins/rss/tests.yml @@ -0,0 +1,6 @@ +- name: Rss plugin (default) + uses: lowlighter/metrics@latest + with: + token: NOT_NEEDED + plugin_rss: yes + plugin_source: https://example.org/rss \ No newline at end of file diff --git a/source/templates/classic/partials/_.json b/source/templates/classic/partials/_.json index 50ea4af3..18c62c73 100644 --- a/source/templates/classic/partials/_.json +++ b/source/templates/classic/partials/_.json @@ -13,6 +13,7 @@ "music", "nightscout", "posts", + "rss", "tweets", "isocalendar", "stars", diff --git a/source/templates/classic/partials/rss.ejs b/source/templates/classic/partials/rss.ejs new file mode 100644 index 00000000..c1d5f6b3 --- /dev/null +++ b/source/templates/classic/partials/rss.ejs @@ -0,0 +1,35 @@ +<% if (plugins.rss) { %> +
+

+ + <%= plugins.rss?.source ?? "" %> RSS feed +

+
+
+ <% if (plugins.rss.error) { %> +
+ + <%= plugins.rss.error.message %> +
+ <% } else { %> + <% if (plugins.rss.feed.length) { %> + <% for (const {title, date} of plugins.rss.feed) { %> +
+ +
+
<%= title %>
+
<%= f.date(new Date(date), {dateStyle:"short"}) %>
+
+
+ <% } %> + <% } else { %> +
+ + Empty RSS feed +
+ <% } %> + <% } %> +
+
+
+<% } %> \ No newline at end of file diff --git a/source/templates/classic/style.css b/source/templates/classic/style.css index c4b870ab..e3ebbc1d 100644 --- a/source/templates/classic/style.css +++ b/source/templates/classic/style.css @@ -904,6 +904,18 @@ stroke-width: 6; } +/* RSS feed */ + .rss { + align-items: flex-start; + } + .rss .infos { + margin-bottom: 3px; + } + .rss .infos .date { + font-size: 10px; + color: #666666; + } + /* Fade animation */ .af { opacity: 0; diff --git a/source/templates/repository/partials/_.json b/source/templates/repository/partials/_.json index 89b92521..617b17d4 100644 --- a/source/templates/repository/partials/_.json +++ b/source/templates/repository/partials/_.json @@ -9,5 +9,6 @@ "people", "activity", "contributors", - "licenses" + "licenses", + "rss" ] \ No newline at end of file diff --git a/source/templates/repository/partials/rss.ejs b/source/templates/repository/partials/rss.ejs new file mode 100644 index 00000000..c1d5f6b3 --- /dev/null +++ b/source/templates/repository/partials/rss.ejs @@ -0,0 +1,35 @@ +<% if (plugins.rss) { %> +
+

+ + <%= plugins.rss?.source ?? "" %> RSS feed +

+
+
+ <% if (plugins.rss.error) { %> +
+ + <%= plugins.rss.error.message %> +
+ <% } else { %> + <% if (plugins.rss.feed.length) { %> + <% for (const {title, date} of plugins.rss.feed) { %> +
+ +
+
<%= title %>
+
<%= f.date(new Date(date), {dateStyle:"short"}) %>
+
+
+ <% } %> + <% } else { %> +
+ + Empty RSS feed +
+ <% } %> + <% } %> +
+
+
+<% } %> \ No newline at end of file