Skip to content

Adds docker features #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.old
**/bin
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
processed_entries.txt
rss2newsletter.egg-info
LICENSE
README.md
26 changes: 26 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: ci

on:
push:
branches:
- "main"

jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ vars.DOCKERHUB_USERNAME }}/rss2newsletter:latest
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ processed_entries.txt

*.swp
*.swo
*.old

rss2newsletter.egg-info
dist
6 changes: 6 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM python:3-alpine
WORKDIR /rss2newsletter
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "-u", "./rss2newsletter"]
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ options:
Program configuration file
(default: rss2newsletter.conf)
```
## Supported RSS tags in the newsletter template
You can design your own newsletter template by modifying the [newsletter template file](https://raw.githubusercontent.com/ElliotKillick/rss2newsletter/main/newsletter_template.html). Certain keywords in the template are automatically replaced by their corresponding RSS tags.

| **template keyword** | **RSS tag** |
| :---: | :---: |
| LINK_HERE | link |
| TITLE_HERE | title |
| SUMMARY_HERE | description |
| PUBLISHED_HERE | pubdate |
| CONTENT_HERE | content |
| AUTHOR_NAME_HERE | author (name) |
| AUTHOR_EMAIL_HERE | author (email) |
| TAGS_HERE | category (as comma separated string) |
| MEDIA_HERE | media (url) |

## Support the Author

Expand Down
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
rss2newsletter:
image: rss2newsletter
container_name: rss2newsletter
restart: unless-stopped
volumes:
- ./rss2newsletter.conf:/rss2newsletter/rss2newsletter.conf
- ./newsletter_template.html:/rss2newsletter/newsletter_template.html
- type: volume
source: rss2newsletter-data
target: /rss2newsletter/data

volumes:
rss2newsletter-data:
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
requests
feedparser
lxml
43 changes: 32 additions & 11 deletions rss2newsletter
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class rss2newsletter:
for new_entry in self.check_for_new_entries(
processed_entries_last_update, feed.entries
):
campaign_id = self.create_newsletter(new_entry.link, new_entry.title)
campaign_id = self.create_newsletter(new_entry)
send_successful = self.send_newsletter(campaign_id)
if send_successful:
self.update_processed_entries_file(new_entry.link)
Expand All @@ -111,8 +111,7 @@ class rss2newsletter:
feed = feedparser.parse(self.config["FEED"]["URL"])
# In case of failure
if hasattr(feed, "bozo_exception"):
print(f"Error fetching RSS from: {self.config['FEED']['URL']}")
return None
raise Exception(f"Error fetching RSS from: {self.config['FEED']['URL']}")

return feed

Expand Down Expand Up @@ -166,24 +165,46 @@ class rss2newsletter:
if entry.link not in proceseed_entries_last_update:
yield entry

def create_newsletter(self, link: str, title: str) -> int | None:
def create_newsletter(self, feed: feedparser.FeedParserDict) -> int | None:
"""Create newsletter with content and add campaign to Listmonk"""

print("Creating newsletter for:", title)
return self.create_campaign(title, self.create_content(link, title))
print("Creating newsletter for:", feed.title)
return self.create_campaign(feed.title, self.create_content(feed))

def create_content(self, link: str, title: str) -> str:
def create_content(self, feed: feedparser.FeedParserDict) -> str:
"""Create content to be used as body of newsletter"""

with open(
self.config["NEWSLETTER"]["TEMPLATE_FILE"], "r", encoding="utf-8"
) as f:
content = f.read()

content = content.replace("LINK_HERE", link)
content = content.replace("TITLE_HERE", title)

og_image = self.get_og_image(self.fetch_url(link))
if hasattr(feed, "link"):
content = content.replace("LINK_HERE", feed.link)
if hasattr(feed, "title"):
content = content.replace("TITLE_HERE", feed.title)
if hasattr(feed, "summary"):
content = content.replace("SUMMARY_HERE", feed.summary)
if hasattr(feed, "published_parsed"):
content = content.replace("PUBLISHED_HERE", time.strftime("%d-%m-%Y", feed.published_parsed))
if hasattr(feed, "content") and len(feed.content) > 0:
content = content.replace("CONTENT_HERE", feed.content[0].value)
if hasattr(feed, "author"):
email = feed.author.split(' ')[0]
content = content.replace("AUTHOR_EMAIL_HERE", email)
start_index_author = feed.author.find('(')
end_index_author = feed.author.find(')')
if start_index_author != -1 and end_index_author != -1:
author = feed.author[start_index_author + 1:end_index_author]
content = content.replace("AUTHOR_NAME_HERE", author)
if hasattr(feed, "tags"):
tags_string = ", ".join([tag.term for tag in feed.tags])
content = content.replace("TAGS_HERE", tags_string)
if hasattr(feed, "media_content") and len(feed.media_content) > 0:
content = content.replace("MEDIA_HERE", feed.media_content[0]["url"])

og_image = self.get_og_image(self.fetch_url(feed.link))
if og_image:
content = content.replace("IMAGE_HERE", og_image)
else:
Expand Down Expand Up @@ -245,7 +266,7 @@ class rss2newsletter:
json_data = {
"name": name,
"subject": name,
"lists": [1],
"lists": [int(self.config["LISTMONK"]["LIST_ID"])],
"content_type": "richtext",
"body": body,
"messenger": "email",
Expand Down
6 changes: 3 additions & 3 deletions rss2newsletter.conf
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
[FEED]
# Full URL to your website's feed
URL = https://elliotonsecurity.com/atom.xml
URL = YOUR_FEED_URL

# How often to check for new feed entries in seconds
POLL_INTERVAL = 300

# rss2newsletter uses this file to keep track of new feed entries
PROCESSED_ENTRIES_FILE = processed_entries.txt
PROCESSED_ENTRIES_FILE = data/processed_entries.txt


[LISTMONK]
# Full URL to listmonk server
URL = http://localhost:9000

# Credentials
USERNAME = ElliotKillick
USERNAME = YOUR_USERNAME
PASSWORD = YOUR_PASSWORD

# The ID of your "rss2newsletter" list (create this list in listmonk)
Expand Down