#!/usr/bin/env python3 import argparse from enum import Enum from getpass import getpass from html.parser import HTMLParser import json import os import re import sys import xdg.BaseDirectory from tinytinypy import Connection APP_NAME = "tinytinypy" # For default config directories class OutputMode(Enum): # enum values JSON = ('json', False) TTS_READY = ('tts-ready', True) ONLY_URL = ('only-url', False) HEADLINES = ('headlines', False) # helpers @classmethod def get_mode_names(cls): return [e.mode_name for e in cls] @classmethod def parse_mode(cls, mode_name): for e in cls: if e.mode_name == mode_name: return e def __init__(self, mode_name, requires_content): self.mode_name = mode_name self.requires_content = requires_content def __str__(self): return self.mode_name def func_articles(server: Connection, args: argparse.Namespace): filtered = {key: value for key, value in args.__dict__.items() if key in ["feed_id", "cat_id", "limit", "skip", "view_mode", "since_id", "include_nested", "order_by"]} headlines = server.getHeadlines(show_content=args.output_mode.requires_content, **filtered) if args.output_mode == OutputMode.ONLY_URL: for h in headlines: print(h.url) elif args.output_mode == OutputMode.TTS_READY: for h in headlines: if h.content: parser = ContentTTSParser() parser.append_sentence(h.title) parser.feed(h.content) print(parser.extracted) else: print(h.title) elif args.output_mode == OutputMode.HEADLINES: for h in headlines: print(h.title) elif args.output_mode == OutputMode.JSON: print(json.dumps([h.toJson() for h in headlines])) else: raise Exception(f'Not implemented output mode "{args.output_mode}"') def configure_parser(): parser = argparse.ArgumentParser(description="Allows access to feeds and articles of a Tiny Tiny RSS instance using the API.") #parser.add_argument('--proto', '--protocol', dest='proto', default='https', choices=['http', 'https'], help="The protocol used to access the api, defaults to https") parser.add_argument('-H', '--host', '--url', dest='url', required=True, help="URL of the TT-RSS instace to access, examples: rss.example.com, example.com/tt-rss") parser.add_argument('-u', '--user', '--username', dest='user', required=True, help="Name of the user used to access the instance") parser.add_argument('-p', '--pass', '--password', dest='passwd', help="Password of the user used to access the instance") parser.add_argument('-P', '--pass-stdin', '--password-stdin', action='store_true', dest='passStdin', help="Read the password for the user used to access the instance from stdin") sub = parser.add_subparsers(help="Operations", dest='op', description="Operations available to send to server") #= Get Articles p = sub.add_parser('articles', help="Get articles") p.set_defaults(func=func_articles) p.add_argument('--feed-id', dest='feed_id', help="Only output articles for this feed, cannot be used with --cat-id") p.add_argument('--cat-id', dest='cat_id', help="Only output articles for feeds of this category, cannot be used with --feed-id") p.add_argument('--limit', dest='limit', type=int, help="Maximum count of articles") p.add_argument('--skip', '--offset', dest='skip', type=int, help="Skip this amount of feeds first") p.add_argument('--view-mode', '--filter', dest='view_mode', choices=['all_articles', 'unread', 'adaptive', 'marked', 'updated'], default='all_articles', help='Only show articles of certain type') p.add_argument('--output-mode', dest='output_mode', choices=OutputMode, type=OutputMode.parse_mode, default='json', help='Define how the received articles should be outputed, in most modes except json and *-full modes, one line equals a single article') return parser def parse(args): return configure_parser().parse_args(args=args) def main(): URL_REGEX = re.compile(r'^(?P[a-z]+://)?(?P[^:/ ]+)(:(?P\d+))?(?P.*)$') # Retrieve args from config and shell storedArgs = sys.argv.copy() storedArgs.pop(0) for d in xdg.BaseDirectory.load_config_paths(APP_NAME): with open(os.path.join(d, "config.txt")) as f: storedArgs = [line.strip() for line in f if line.strip()[0] != '#'] + storedArgs # Parse args args = parse(storedArgs) # Read Pass if stdin passwd = args.passwd if args.passStdin: passwd = getpass() # Check for pass if passwd is None: raise ValueError("Password is missing!") # TODO make more beautiful for user # Parse url urlM = URL_REGEX.match(args.url).groupdict() proto = urlM.get('proto') or 'https' host = urlM['host'] # Call api # TODO Support port with Connection(proto=proto, host=host) as server: server.login(args.user, passwd) args.func(server, args) if __name__ == "__main__": main()