#!/usr/bin/python3

import argparse
import configparser
import lib
import http.cookiejar
import json
import os
import pprint
import re
import urllib
import ssl
import sys
import traceback

#### CONFIG ####

config_global = configparser.ConfigParser()
config_global['DEFAULT'] = {
    'username': 'user',
    'password': 'password',
    'url': 'https://newsblur.com/',
    'configfile' : os.environ['HOME'] + '/.config/newsblur/login',
}
config_global.read(config_global['DEFAULT']['configfile'])
config = config_global['DEFAULT']
wnum  = 3 # max width of number column for saved feeds (0-999)
wids  = 7 # max width of number column for feed ids
wstar = 5 # max width of number column for starred stories

exitcode = 0
pp = pprint.PrettyPrinter(indent=4)

#### CONNECT ####

opener = urllib.request.build_opener()

# set up cookies
opener.add_handler(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))

# set up secure TLS
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations(capath="/etc/ssl/certs")
opener.add_handler(urllib.request.HTTPSHandler(context=context))

# print errors
def eprint(str):
    print("\033[31m"+str+"\033[0m",file=sys.stderr)

def ecompare(e,reason,filename,func,code=None):
    if not str(e.reason) == reason: return False
    lastframe = traceback.extract_tb(e.__traceback__)[-1]
    if filename == 'printme':
        eprint("printing filename: "+lastframe.filename)
    elif not lastframe.filename == filename:
        return False
    if func == 'printme':
        eprint("printing func: "+lastframe.name)
    elif lastframe.name != func:
        return False
    if code is not None and str(e.code) != code: return False
    return True

def nbtry(opener,*args,post=None):
    while True:
        try:
            if post:
                response = opener.open(*args,urllib.parse.urlencode(post).encode("utf-8"))
            else:
                response = opener.open(*args)
            jdata = json.loads(response.read().decode())
        except urllib.error.URLError as e:
            if ecompare(e,'[Errno 0] Error','/usr/lib/python3.7/urllib/request.py','do_open'):
                eprint('network error, trying again')
            elif ecompare(e,'Tunnel connection failed: 503 Forwarding failure','/usr/lib/python3.7/urllib/request.py','do_open'):
                eprint('tunnel fail, trying again')
            elif ecompare(e,'Request Time-out','/usr/lib/python3.7/urllib/request.py','http_error_default'):
                eprint('request timeout, trying again')
            else:
                frame = traceback.extract_tb(e.__traceback__)[-1]
                eprint("urlerror: "+str(e.errno)+" | "+str(e.strerror)+" | "+str(e.reason)+" | "+str(e)+" | "+pp.pformat(e)+" | "+frame.filename+" | "+frame.name+" -- trying again")
            continue
        except ConnectionError as e:
            eprint('connection error, trying again')
            continue
        except (http.client.HTTPException, http.client.IncompleteRead) as e:
            eprint('http exception, trying again')
            continue
        if (jdata['result'] != 'ok'):
            eprint("jdata error, trying again")
            continue
        if ('code' in jdata and jdata['code'] != 1):
            if 'errors' in jdata:
                for e in jdata['errors']['__all__']:
                    eprint('json error: '+e+' ... trying again')
                continue
        break
    return(jdata)

# log in
authdata = urllib.parse.urlencode({
	"username" : config["username"],
	"password" : config["password"],
}).encode()

if not nbtry(opener,config['url']+"api/login",authdata):
    print("login failed")
    sys.exit(1)


#### ROUTINES ####

def printw(str):
    print(str[0:int(lib.getTerminalSize()[0])])

def list_starred():
    maxw = int(lib.getTerminalSize()[0]/2 - wnum - 9 - 7)
    feeds = nbtry(opener,config['url']+"reader/feeds")
    for feed in feeds['starred_counts']:
        id = feed['feed_id']
        print("trying: "+str(id)+" "+str(feed['feed_address']));
        if str(id) in feeds['feeds']:
            feed_name = 'https://newsblur.com/site/' + str(id) + '/' + lib.urlify(feeds['feeds'][str(id)]['feed_title'])
        else:
            if (str(id) == 'None'): continue
            feed_name = 'https://newsblur.com/site/' + str(id)
        percent = " (" + "{:1.1f}".format(100 * feed['count'] / feeds['starred_count']) + "%) "
        print(str(feed['count']).rjust(wnum) + percent + feed_name[0:maxw]);

def dump_starred():
    maxw = int(lib.getTerminalSize()[0]/2 - wids - wstar - 9 - 7)
    for page in range(0,10000,1):
       sss = nbtry(opener,config['url']+"reader/starred_stories?page="+str(page))
       if len(sss['stories']) is None: break
       for i, ss in enumerate(sss['stories']):
           print(str(page*10+i+1).rjust(wstar)+" "+ss['story_date']+" https://newsblur.com/site/"+str(ss['story_feed_id']).ljust(wids)+" "+ss['story_permalink'])

def feed_archive(id):
    maxw = int(lib.getTerminalSize()[0])
    storynum = 0
    for page in range(1,100,1):
        for story in nbtry(opener,config['url']+"reader/feed/"+str(id)+"?page="+str(page))["stories"]:
            storynum += 1
            out = str(storynum).rjust(4)+" "+str(page).rjust(2)+" "+story["long_parsed_date"].ljust(13)+" " \
                  +story["story_permalink"]+" " \
                  +story["story_title"].replace("\n"," ").splitlines()[0];
            print(out[0:maxw])

delfeeds = [];

def recursive_folder_parse(obj):
    global delfeeds
    for id in obj:
        if isinstance(id, int):
            delfeeds.append(id);
            continue
        if isinstance(id, dict):
            for key in id.keys():
                recursive_folder_parse(id[key])

def mark_folder_read(name):
    global delfeeds
    if not name: return false
    feeds = nbtry(opener,config['url']+"reader/feeds")
    for folder in feeds["folders"]:
        if not isinstance(folder, dict): continue
        if name not in folder: continue
        recursive_folder_parse(folder[name])

    # build request lines
    line_limit = 8190
    requests = []
    for feed in delfeeds:
        if ((len(requests) == 0) or (requests[len(requests)].length > line_limit - 100)):
            requests.append(config['url']+"reader/mark_feed_as_read?");
        requests[len(requests)] += 'feed_id=' + str(id) + '&'
    for requeststring in requests:
        print(requeststring)
#        marking = nbtry(opener,requeststring)

def show_feed(id):
    print("https://newsblur.com/site/"+str(id)+"/")
    #print(nbtry(opener,config['url']+"rss_feeds/statistics/"+str(id)))
    info = nbtry(opener,config['url']+"reader/feeds")["feeds"][str(id)]
    print(info["feed_title"])
    print(str(info["subs"])+" subscribers")
    print(info["feed_link"])
    print(info["feed_address"])
    stories = nbtry(opener,config['url']+"reader/feed/"+str(id)+"?include_story_content=false")
    for story in stories["stories"]:
        #pp.pprint(story)
        printw("   "+story['short_parsed_date']+" "+story['story_permalink']+"  "+story['story_title'])

def add_feed(url,folder=''):
    if not url: return
    if folder:
        result = nbtry(opener,config['url']+"reader/add_url",post={"folder": folder,"url": url})
    else:
        result = nbtry(opener,config['url']+"reader/add_url",post={"url": url})
    if not result["feed"]:
        print("NO FEEDS FOUND")
        return 1
    if result["feed"]["id"]:
        show_feed(result["feed"]["id"])
        markread = nbtry(opener,config['url']+"reader/mark_feed_as_read",post={"feed_id": result["feed"]["id"]})
        if not markread["errors"] and (markread["result"] == "ok"):
            print("[marked read]")
        else:
            print(markread)

parser = argparse.ArgumentParser()
parser.add_argument('--list_starred', action='store_true', help='List feeds sorted by how many starred')
parser.add_argument('--dump_starred', action='store_true', help='Dump every starred item - expensive!')
parser.add_argument('--addfeed', help='Add a feed')
parser.add_argument('--folder', help='Folder to add it to')
args = parser.parse_args()

#### DO STUFF ####
try:
    if args.list_starred:
        list_starred();
    elif args.dump_starred:
        dump_starred();
    elif args.addfeed:
        if not args.folder: args.folder = ''
        exitcode = add_feed(args.addfeed, args.folder)
    else:
        print('no options given')
    if not nbtry(opener,config['url']+"api/logout"):
        print("logout failed")
        exitcode = 1
except KeyboardInterrupt:
    sys.exit(1)

sys.exit(exitcode)
