Compare commits

..

No commits in common. "main" and "v0.0.1" have entirely different histories.
main ... v0.0.1

2 changed files with 18 additions and 49 deletions

View file

@ -1,3 +0,0 @@
## RSS Notifier
A python script which checks an RSS feed for new entries and sends
notifications to any libnotify-compatible Linux Desktop when it finds them.

64
rss_notifier.py Executable file → Normal file
View file

@ -1,5 +1,3 @@
#!/usr/bin/env nix-shell
#! nix-shell -i python -p python3 libnotify python3Packages.beautifulsoup4 python3Packages.lxml python3Packages.requests
from requests import get
from sqlite3 import Connection as SQL, Cursor
from contextlib import contextmanager
@ -8,33 +6,27 @@ from sys import argv, stderr
from dataclasses import dataclass
from typing import *
from subprocess import Popen, PIPE, run
from os import execvp, environ, makedirs
from os import execvp, environ
from json import dumps, loads
from pathlib import Path
@dataclass
class Feed:
id: int
url: str
title: Optional[str] = None
content: Optional[Soup] = None
def fetch_content(self):
def fetch(self):
res = get(self.url)
res.raise_for_status()
self.content = Soup(res.text, features="xml")
if title_tag := self.content.find('title'):
self.title = title_tag.text
else:
print(f'warning: no title for feed {self.url!r}')
return self
def entries(self):
if content := self.content:
return content.find_all('entry')
else:
return self.fetch_content().entries()
return self.fetch().entries()
@classmethod
def from_record(cls, record):
@ -50,7 +42,7 @@ class Feed:
return hash(self.id)
def to_dict(self):
return {'id': self.id, 'url': self.url, 'title': self.title}
return {'id': self.id, 'url': self.url}
def to_json(self) -> str:
return dumps(self.to_dict())
@ -58,7 +50,7 @@ class Feed:
@classmethod
def from_json(cls, text: str) -> 'Feed':
data = loads(text)
return cls(id=data['id'], url=data['url'], title=data['title'])
return cls(id=data['id'], url=data['url'])
ACTIONS = ['--action', 'open=Open link', '--action', 'read=Mark read']
@ -73,16 +65,10 @@ class FeedEntry:
@classmethod
def select_all(cls, db_connection) -> List['FeedEntry']:
query = '''
select entry.*, feed.url, feed.title
from entries entry
join feeds feed
on feed.id = entry.feed_id;
'''
feed_entries = db_connection.cursor().execute(query).fetchall()
feed_entries = db_connection.cursor().execute("select entry.*, feed.url from entries entry join feeds feed on feed.id = entry.feed_id;").fetchall()
return set(
FeedEntry(id, Feed(feed_id, feed_url, feed_title), upstream_id, title, link, read)
for id, feed_id, upstream_id, title, link, read, feed_url, feed_title
FeedEntry(id, Feed(feed_id, feed_url), upstream_id, title, link, read)
for id, feed_id, upstream_id, title, link, read, feed_url,
in feed_entries
)
@ -97,10 +83,7 @@ class FeedEntry:
def to_json(self) -> str:
assert list(self.__dict__.keys()) == "id feed upstream_id title link read".split()
return dumps({
k: v.to_dict() if k == 'feed' else v
for k, v in self.__dict__.items()
})
return dumps({ k: v.to_dict() if k == 'feed' else v for k, v in self.__dict__.items() })
@classmethod
def from_json(cls, data) -> 'FeedEntry':
@ -135,7 +118,7 @@ class FeedEntry:
self.upstream_id,
self.title,
self.link,
self.read
self.mark_read
)
)
@ -150,13 +133,9 @@ class RSSNotifier:
def create_tables(self):
db = self.db_connection.cursor()
db.execute('''
create table if not exists feeds (
id integer primary key autoincrement,
url text unique not null,
title text
);
''')
db.execute(
'create table if not exists feeds (id integer primary key autoincrement, url text)'
)
db.execute('''
create table if not exists entries (
id integer primary key autoincrement,
@ -175,9 +154,9 @@ class RSSNotifier:
return map(Feed, feeds)
def add_feed(self, url: str, mark_read: bool):
feed = Feed(-1, url).fetch_content()
feed = Feed(-1, url).fetch()
cursor = self.db_connection.cursor()
cursor.execute("insert into feeds (url, title) values (?, ?)", (url, feed.title))
cursor.execute("insert into feeds (url) values (?)", (url,))
feed.id = cursor.lastrowid
for entry in feed.entries():
FeedEntry.from_rss(entry, feed, mark_read).insert(cursor)
@ -187,7 +166,7 @@ class RSSNotifier:
def parse_args(self, args=None):
mark_read = True
if args is None:
args = argv[1:]
args = argv
feeds_to_add = []
try:
while True:
@ -262,7 +241,7 @@ class RSSNotifier:
'--expire-time', '0',
'--app-name', 'RSS Notifier',
*ACTIONS,
f'New RSS Story from "{entry.feed.title}": {entry.title}'
f'New RSS Story: {entry.title}'
],
stdout=PIPE,
stderr=PIPE
@ -282,11 +261,4 @@ class RSSNotifier:
if __name__ == "__main__":
db_path = environ.get("RSS_NOTIFIER_DATABASE_LOCATION")
if db_path is None:
db_path = Path(environ.get("HOME")) / ".local/state/rss-notifier/db.sqlite3"
else:
db_path = Path(db_path)
if not db_path.parent.exists():
makedirs(db_path.parent)
RSSNotifier(db_path).parse_args()
RSSNotifier(environ.get("RSS_NOTIFIER_DATABASE_LOCATION") or Path(environ.get("HOME")) / ".local/state/rss-notifier/db.sqlite3").parse_args()