You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

114 lines
5.0 KiB
Python

#!/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, args):
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<proto>[a-z]+://)?(?P<host>[^:/ ]+)(:(?P<port>\d+))?(?P<path>.*)$')
# 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()