From 6f044f05c80874cba90780856cd6c81b6e53ce21 Mon Sep 17 00:00:00 2001 From: DrMxrcy Date: Tue, 28 Feb 2023 00:03:10 -0500 Subject: [PATCH] Add RSS --- .gitignore | 1 + apps/rss/config.json | 16 ++++++ apps/rss/data/storage/feeds.txt | 3 ++ apps/rss/docker-compose.yml | 26 +++++++++ apps/rss/metadata/description.md | 89 +++++++++++++++++++++++++++++++ apps/rss/metadata/logo.jpg | Bin 0 -> 15272 bytes 6 files changed, 135 insertions(+) create mode 100644 apps/rss/config.json create mode 100644 apps/rss/data/storage/feeds.txt create mode 100644 apps/rss/docker-compose.yml create mode 100644 apps/rss/metadata/description.md create mode 100644 apps/rss/metadata/logo.jpg diff --git a/.gitignore b/.gitignore index 5e3c552a..382433b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ package-lock.json github.secrets +.DS_Store diff --git a/apps/rss/config.json b/apps/rss/config.json new file mode 100644 index 00000000..77ae5419 --- /dev/null +++ b/apps/rss/config.json @@ -0,0 +1,16 @@ +{ + "$schema": "../schema.json", + "name": "RSS", + "port": 8141, + "available": true, + "exposable": true, + "id": "rss", + "tipi_version": 1, + "version": "v1.2.0", + "categories": ["utilities, media"], + "description": "A simple twitter-feed-style RSS aggregator written in PHP, Laravel, Inertia.js, Tailwind and Vue.js", + "short_desc": "A simple, opinionated, RSS feed aggregator.", + "author": "https://github.com/ssddanbrown", + "source": "https://github.com/ssddanbrown/rss", + "form_fields": [] +} diff --git a/apps/rss/data/storage/feeds.txt b/apps/rss/data/storage/feeds.txt new file mode 100644 index 00000000..47958518 --- /dev/null +++ b/apps/rss/data/storage/feeds.txt @@ -0,0 +1,3 @@ +https://noted.lol/rss Notedā­[#9233B1] #self-hosting +https://www.linuxserver.io/blog.rss Linux-Server[#0078b9] #self-hosting +https://blog.networkprofile.org/rss Network-Profile[#D48D27] #self-hosting \ No newline at end of file diff --git a/apps/rss/docker-compose.yml b/apps/rss/docker-compose.yml new file mode 100644 index 00000000..5345da8d --- /dev/null +++ b/apps/rss/docker-compose.yml @@ -0,0 +1,26 @@ +version: "2" + +services: + rss: + image: ghcr.io/ssddanbrown/rss:v1.2.0 + container_name: rss + environment: + - APP_NAME=Tipi-RSS + - APP_FEED_UPDATE_FREQUENCY=35 + - APP_LOAD_POST_THUMBNAILS=true + volumes: + - ${APP_DATA_DIR}/data/storage:/app/storage + ports: + - "${APP_PORT}:80" + restart: unless-stopped + labels: + traefik.enable: ${APP_EXPOSED} + traefik.http.routers.rss.rule: Host(`${APP_DOMAIN}`) + traefik.http.routers.rss.entrypoints: websecure + traefik.http.routers.rss.service: rss + traefik.http.routers.rss.tls.certresolver: myresolver + traefik.http.services.rss.loadbalancer.server.port: 80 + networks: + - tipi_main_network + + diff --git a/apps/rss/metadata/description.md b/apps/rss/metadata/description.md new file mode 100644 index 00000000..e4b7863d --- /dev/null +++ b/apps/rss/metadata/description.md @@ -0,0 +1,89 @@ +# RSS + +A simple, opinionated, RSS feed aggregator. + +# Edit the feeds.txt file. + +## Feed Configuration + +Feed configuration is handled by a plaintext file on the host system. +By default, using our docker image, this configuration would be located in a `feeds.txt` file within the path you mounted to `/app/storage`. + +The format of this file can be seen below: + +```txt +https://feed.url.com/feed.xml feed-name #tag-a #tag-b +https://example.com/feed.xml Example #updates #news + +# Lines starting with a hash are considered comments. +# Empty lines are fine and will be ignored. + +# Underscores in names will be converted to spaces. +https://example.com/feed-b.xml News_Site #news + +# Feed color can be set using square brackets after the name. +# The color must be a CSS-compatible color value. +https://example.com/feed-c.xml Blue_News[#0078b9] #news #blue +``` + +## Features + +The following features are built into the application: + +- Supports RSS and ATOM formats. +- Regular auto-fetching of RSS feeds. + - Every hour by default, configurable down to 5 mins. +- Custom feed names and colors. +- Feed-based tags for categorization. +- 3 different post layout modes (card, list, compact). +- Fetching of page open-graph images. +- Feeds managed via a single plaintext file. +- System-based dark/light theme. +- Post title/description search. +- Ready-to-use docker image. +- Mobile screen compatible. +- Built-in support to prune old post data. + +## Limitations + +The below possibly expected features are missing from this application. +This is not a list of planned features. Please see the [Low Maintenance Project](#low-maintenance-project) section below for more info. + +- No import of full post/article content. +- No feed management via the UI. +- No user system or user management system. +- No authentication or authorization built-in. +- No customization, extension or plugin system. +- No organisation upon simple feed-level tagging. +- Error handling is limited and will likely not alert clearly upon issue. + +Upon the above, it's quite likely you'll come across issues. This project was created to meet a personal need while learning some new technologies. Much of the logic is custom written instead of using battle-tested libraries. + +## Screenshots + + + + + + + + + + + +
+ Card View + + + List View + + + Compact View + + + Dark Mode + +
+ + + diff --git a/apps/rss/metadata/logo.jpg b/apps/rss/metadata/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a0f440b84f1a366c549203fe4f0b5ba86410ea12 GIT binary patch literal 15272 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 XMWO|o7E|I7t-tO4f9i7VVBr4&fIPpx literal 0 HcmV?d00001