Skip to content

Commit f917e12

Browse files
authored
Merge pull request #22 from selfagency/dev
2 parents edf56a7 + 205a920 commit f917e12

File tree

7 files changed

+32612
-7652
lines changed

7 files changed

+32612
-7652
lines changed

.github/workflows/feedbot.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,29 @@ jobs:
77
rss-to-slack:
88
runs-on: ubuntu-latest
99
steps:
10+
- name: Retrieve cache
11+
uses: actions/cache@v2
12+
with:
13+
path: ~/slackfeedbot
14+
key: slackfeedbot-cache
1015
- name: NYT
1116
uses: 'selfagency/slackfeedbot@dev'
1217
with:
1318
rss: 'https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml'
1419
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
15-
interval: 15
20+
cache_dir: ~/slackfeedbot
1621
unfurl: false
1722
- name: LAT
1823
uses: 'selfagency/slackfeedbot@dev'
1924
with:
2025
rss: 'https://www.latimes.com/rss2.0.xml'
2126
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
22-
interval: 15
27+
cache_dir: ~/slackfeedbot
2328
unfurl: false
2429
- name: WaPo
2530
uses: 'selfagency/slackfeedbot@dev'
2631
with:
2732
rss: 'https://feeds.washingtonpost.com/rss/homepage'
2833
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
29-
interval: 15
34+
cache_dir: ~/slackfeedbot
3035
unfurl: false

README.md

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@ Push RSS feed updates to Slack via GitHub Actions
1717
- `rss` is an RSS feed URL.
1818
- `slack_webhook` is the URL of your Slack webhook (this can and probably
1919
should be a repository or organization secret).
20+
- `cache_dir` is the folder in which you want to cache RSS data to prevent
21+
publishing duplicates (e.g., `~/slackfeedbot`), or alternately...
2022
- `interval` is the number of minutes between runs of the parent workflow, as
21-
specified in the `cron` section of the `schedule` workflow trigger.
23+
specified in the `cron` section of the `schedule` workflow trigger (may
24+
publish duplicates due to post pinning).
2225
- `unfurl` tells Slack to show the [Open Graph](https://ogp.me/) preview. If
2326
set to `false` the title, date, short description, and a link to the feed item
2427
will be posted. Defaults to `false` because it's kind of flaky.
2528

29+
## Examples
30+
31+
### With cache folder
32+
2633
```
2734
name: SlackFeedBot
2835
on:
@@ -32,23 +39,89 @@ jobs:
3239
rss-to-slack:
3340
runs-on: ubuntu-latest
3441
steps:
42+
- name: Retrieve cache
43+
uses: actions/cache@v2
44+
with:
45+
path: ~/slackfeedbot
46+
key: slackfeedbot-cache
3547
- name: NYT
36-
uses: 'selfagency/slackfeedbot@v1'
48+
uses: 'selfagency/[email protected]'
49+
with:
50+
rss: 'https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml'
51+
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
52+
cache_dir: '~/slackfeedbot'
53+
```
54+
55+
### With interval
56+
57+
```
58+
name: SlackFeedBot
59+
on:
60+
schedule:
61+
- cron: '*/15 * * * *'
62+
jobs:
63+
rss-to-slack:
64+
runs-on: ubuntu-latest
65+
steps:
66+
- name: NYT
67+
uses: 'selfagency/[email protected]'
3768
with:
3869
rss: 'https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml'
3970
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
4071
interval: 15
72+
```
73+
74+
### Unfurl URLs
75+
76+
```
77+
name: FeedBot
78+
on:
79+
schedule:
80+
- cron: '*/15 * * * *'
81+
jobs:
82+
rss-to-slack:
83+
runs-on: ubuntu-latest
84+
steps:
85+
- name: Retrieve cache
86+
uses: actions/cache@v2
87+
with:
88+
path: ~/slackfeedbot
89+
key: slackfeedbot-cache
90+
- name: NYT
91+
uses: 'selfagency/[email protected]'
92+
with:
93+
rss: 'https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml'
94+
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
95+
cache_dir: '~/slackfeedbot'
4196
unfurl: true
97+
```
98+
99+
### Multiple feeds
100+
101+
```
102+
name: FeedBot
103+
on:
104+
schedule:
105+
- cron: '*/15 * * * *'
106+
jobs:
107+
rss-to-slack:
108+
runs-on: ubuntu-latest
109+
steps:
110+
- name: Retrieve cache
111+
uses: actions/cache@v2
112+
with:
113+
path: ~/slackfeedbot
114+
key: slackfeedbot-cache
42115
- name: LAT
43-
uses: 'selfagency/slackfeedbot@v1'
116+
uses: 'selfagency/feedbot@v1.2.0'
44117
with:
45118
rss: 'https://www.latimes.com/rss2.0.xml'
46119
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
47-
interval: 15
120+
cache_dir: '~/slackfeedbot'
48121
- name: WaPo
49-
uses: 'selfagency/slackfeedbot@v1'
122+
uses: 'selfagency/feedbot@v1.2.0'
50123
with:
51124
rss: 'https://feeds.washingtonpost.com/rss/homepage'
52125
slack_webhook: ${{ secrets.SLACK_WEBHOOK }}
53-
interval: 15
126+
cache_dir: '~/slackfeedbot'
54127
```

action.js

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
import core from '@actions/core';
2+
import { createHash } from 'crypto';
23
import dayjs from 'dayjs';
4+
import { readFile, writeFile } from 'fs';
35
import { compile } from 'html-to-text';
46
import fetch from 'node-fetch';
57
import { parse } from 'rss-to-json';
8+
import TurndownService from 'turndown';
9+
import { promisify } from 'util';
610

11+
const read = promisify(readFile);
12+
const write = promisify(writeFile);
13+
const { debug, setFailed, getInput } = core;
14+
const turndownService = new TurndownService();
715
const html2txt = compile({
816
wordwrap: 120
917
});
1018

11-
const { debug, setFailed, getInput } = core;
19+
function hash(string) {
20+
return createHash('sha256').update(string).digest('hex');
21+
}
1222

1323
const validate = () => {
1424
if (!getInput('rss') || !getInput('rss').startsWith('http')) {
@@ -19,8 +29,12 @@ const validate = () => {
1929
throw new Error('No Slack webhook or invalid webhook specified');
2030
}
2131

22-
if (!getInput('interval') || parseInt(getInput('interval')).toString() === 'NaN') {
23-
throw new Error('No interval or invalid interval specified');
32+
if (!getInput('interval') && !getInput('cache_dir')) {
33+
throw new Error('No interval or cache folder specified');
34+
}
35+
36+
if (getInput('interval') && parseInt(getInput('interval')).toString() === 'NaN') {
37+
throw new Error('Invalid interval specified');
2438
}
2539
};
2640

@@ -54,9 +68,12 @@ const run = async () => {
5468
validate();
5569

5670
const rssFeed = getInput('rss');
71+
const rssFeedUrl = new URL(rssFeed);
5772
const slackWebhook = getInput('slack_webhook');
5873
const interval = parseInt(getInput('interval'));
5974
const unfurl = getInput('unfurl').toString() === 'true';
75+
const cacheDir = getInput('cache_dir');
76+
const cachePath = `${cacheDir}/${rssFeedUrl.hostname.replace(/\./g, '_')}.json`;
6077

6178
debug(`Retrieving ${rssFeed}`);
6279
const rss = await parse(rssFeed);
@@ -65,15 +82,32 @@ const run = async () => {
6582
debug('Checking for feed items');
6683
if (rss?.items?.length) {
6784
debug(`Selecting items posted in the last ${interval} minutes`);
68-
const toSend = rss.items.filter(item => dayjs(item.created).isAfter(dayjs().subtract(interval, 'minute')));
85+
86+
let toSend = [];
87+
let published = [];
88+
if (cacheDir) {
89+
debug(`Retrieving previously published entries`);
90+
published = JSON.stringify(await read(cachePath, 'utf8'));
91+
92+
toSend = rss.items.filter(item => {
93+
return !published.find(pubbed => pubbed === hash(JSON.stringify(item.title + item.description)));
94+
});
95+
} else {
96+
toSend = rss.items.filter(item => {
97+
return dayjs(item.created).isAfter(dayjs().subtract(interval, 'minute'));
98+
});
99+
}
69100

70101
const blocks = toSend.map(item => {
71102
let text = '';
72103

73104
if (!unfurl) {
74105
if (item.title) text += `*${html2txt(item.title)}*\n`;
75106
if (item.description) {
76-
const description = item.description.replace(/[Rr]ead more/g, '…').replace(/\n/g, ' ');
107+
const description = turndownService
108+
.turndown(item.description)
109+
.replace(/[Rr]ead more/g, '…')
110+
.replace(/\n/g, ' ');
77111
text += `${description}\n`;
78112
}
79113
if (item.link) text += `<${item.link}|Read more>`;
@@ -112,6 +146,14 @@ const run = async () => {
112146
}
113147
});
114148
debug(res);
149+
150+
if (cacheDir) {
151+
debug(`Writing cache to ${cachePath}`);
152+
await write(
153+
cachePath,
154+
JSON.stringify([...published, ...toSend.map(item => hash(JSON.stringify(item.title + item.description)))])
155+
);
156+
}
115157
}
116158
} else {
117159
throw new Error('No feed items found');

action.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ inputs:
1111
slack_webhook:
1212
description: 'Slack Webhook URL'
1313
required: true
14+
cache_dir:
15+
description: 'Cache folder'
16+
required: true
1417
interval:
1518
description: 'Minutes between workflow runs'
16-
required: true
19+
required: false
1720
unfurl:
1821
description: 'Unfurl links'
1922
required: false

0 commit comments

Comments
 (0)