#!/usr/bin/env python3
#
# nwsflux
import miniflux
import urllib3
import logging
import os
import re
import shlex
import argparse
def get_local_feed(line: str) -> dict:
"""
Parse a line and return a constructed dict like miniflux's API.
"""
feed_url = shlex.split(line)[0]
title = shlex.split(line)[-1]
tag = shlex.split(line)[1]
if tag == title:
tag = 'all'
return {
'feed_url': feed_url,
'title': title,
'category': {
'title': tag
}
}
def get_local_feeds(filename: str) -> list:
"""
Read feeds from a newsboat url file.
"""
with open(filename, 'r') as f:
content = f.readlines()
r = re.compile('^http.*://')
content = list(filter(r.match, content))
return [get_local_feed(line) for line in content]
def delete_categories(client: miniflux.Client, local_cats: dict, remote_cats: dict):
"""
Remove remote categories that are absent in local file.
. Useless for now, miniflux's API return non-empty categories only!
"""
for remote in remote_cats:
if any(local['title'] == remote['title'] for local in local_cats):
continue
try:
logging.info(f'remove category: {remote["title"]}')
client.delete_category(remote['id'])
# ignores categories that are empty on remote
except miniflux.ClientError as e:
logging.error('can not remove non-empty category:'
f'{remote["title"]} {e}')
def create_categories(client: miniflux.Client, local_cats: dict, remote_cats: dict):
"""
Create categories present in local file and absent on remote.
"""
for local in local_cats:
if any(remote['title'] == local['title'] for remote in remote_cats):
continue
try:
logging.info(f'create category: {local["title"]}')
client.create_category(local['title'])
# ignores categories that are empty on remote
except miniflux.ClientError as e:
logging.error(f'remote category empty: {local["title"]} {e}')
def sync_categories(client: miniflux.Client, local_feeds: dict, remote_feeds: dict) -> dict:
local_cats = get_uniq_categories(local_feeds)
remote_cats = get_uniq_categories(remote_feeds)
delete_categories(client, local_cats, remote_cats)
create_categories(client, local_cats, remote_cats)
return client.get_categories()
def get_uniq_categories(buffer: list) -> list:
return list(map(dict, frozenset(
frozenset(x['category'].items()) for x in buffer
)))
def delete_feeds(client: miniflux.Client, local_feeds: dict, remote_feeds: dict):
"""
Remove remote feeds that are absent in local file.
"""
for remote in remote_feeds:
if any(local['feed_url'] == remote['feed_url'] for local in local_feeds):
continue
logging.info(f'remove feed: {remote["feed_url"]}')
client.delete_feed(remote['id'])
def create_feeds(client: miniflux.Client, local_feeds: dict, remote_feeds: dict):
"""
Create feeds that are present in local file and absent on remote.
"""
categories = client.get_categories()
for local in local_feeds:
if any(remote['feed_url'] == local['feed_url'] for remote in remote_feeds):
continue
logging.info(f'create feed: {local}')
category_id = next((x['id'] for x in categories if x['title'] == local['category']['title']), None)
try:
client.create_feed(local['feed_url'], category_id)
except miniflux.ClientError as e:
logging.error(e)
def sync_feeds(client: miniflux.Client, local_feeds: dict, remote_feeds: dict) -> dict:
"""
Synchronize all given feeds.
"""
delete_feeds(client, local_feeds, remote_feeds)
create_feeds(client, local_feeds, remote_feeds)
return client.get_feeds()
def parse():
"""
Parse command-line arguments.
"""
parser = argparse.ArgumentParser(description='Synchronize RSS feeds from'
'newsboat to an miniflux instance.')
parser.add_argument('-c', dest='config', type=str, required=True,
help='Newsboat url file')
parser.add_argument('-k', dest='key', type=str, required=True,
help='Miniflux API key')
parser.add_argument('-u', dest='url', type=str, required=True,
help='Miniflux url')
parser.add_argument('-d', dest='debug', action='store_true',
help='Enable debugging output')
parser.add_argument('-e', dest='export', action='store_true',
help='Export Miniflux OPML config to stdout')
return parser.parse_args()
def main():
"""
SSL verify.
"""
os.environ["CURL_CA_BUNDLE"] = ""
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
"""
Arguments.
"""
args = parse()
"""
Debugging.
"""
logging.basicConfig(level=logging.INFO)
if not args.debug:
logging.level = logging.NOTSET
"""
Synchronization.
"""
client = miniflux.Client(args.url, api_key=args.key)
local_feeds = get_local_feeds(args.config)
remote_feeds = client.get_feeds()
sync_categories(client, local_feeds, remote_feeds)
sync_feeds(client, local_feeds, remote_feeds)
"""
Export Miniflux to OPML.
"""
if args.export:
print(client.export_feeds())
if __name__ == '__main__':
main()