#! /usr/bin/python3
import curses
from html.parser import HTMLParser
from mastodon import Mastodon
def initColours():
# 0: White on black
curses.init_pair(1, 4, 0) # 1: Blue on black
curses.init_pair(2, 1, 0) # 2: Red on black
curses.init_pair(3, 6, 0) # 3: Cyan on black
curses.init_pair(4, 2, 0) # 4: Green on black
def newLine(ncwin, lines=1):
cy, cx = ncwin.getyx()
# my, mx = ncwin.getmaxyx()
try:
ncwin.move(cy + lines, 0)
except curses.error:
pass
def bump(win):
my, mx = win.getmaxyx()
cy, cx = win.getyx()
if cx < mx and cx != 0:
try:
win.move(cy, cx + 1)
except curses.error:
pass
def typeset(text, win, attr, colour):
my, mx = win.getmaxyx()
line = 0
position = 0
strlen = len(text)
while position < (strlen):
cy, cx = win.getyx()
if cx == mx:
if cy == my:
raise curses.error("Out of space")
rem = (mx - cx) + 0
nsp = text.find(' ', position)
if (strlen - position <= rem):
win.addnstr(text[position:], rem,
attr | curses.color_pair(colour))
position = strlen
elif (nsp == -1 or nsp - position > rem): # and not strlen - position <= rem:
if cx == 0:
win.addnstr(text[position:], rem, attr |
curses.color_pair(colour))
position = position + rem
else:
newLine(win)
else:
win.addnstr(text[position:], nsp - position,
attr | curses.color_pair(colour))
position = max(nsp, 0) + 1
bump(win)
def printPost(win, post, parser):
# win.addstr(post["account"]["acct"], curses.A_BOLD)
typeset("@{}".format(post["account"]["acct"]), win, curses.A_BOLD, 0)
if (post["reblog"] is not None):
# win.addstr(" boosted ")
typeset(" boosted ", win, 0, 0)
# win.addstr(post["reblog"]["account"]["acct"], curses.A_BOLD)
typeset("@{}".format(post["reblog"]["account"]["acct"]), win,
curses.A_BOLD, 0)
# win.addstr(":")
typeset(":", win, 0, 0)
newLine(win)
if (post["spoiler_text"] != ""):
# win.addstr(post["spoiler_text"], curses.A_UNDERLINE)
typeset(post["spoiler_text"], win, curses.A_UNDERLINE, 0)
newLine(win)
parser.feed(post["content"])
if parser.openp:
newLine(win)
for att in post["media_attachments"]:
# typeset("[IMG: {}, <{}>]".format(att["alt"], att["src"]))
typeset("[IMG: {}]".format(att["description"] if
att["description"] is not None
else "(Alt text missing)"),
win, 0, 4)
newLine(win)
if (post["reblog"] is not None):
for att in post["reblog"]["media_attachments"]:
# typeset("[IMG: {}, <{}>]".format(att["alt"], att["src"]))
typeset("[IMG: {}]".format(att["description"] if
att["description"] is not None
else "(Alt text missing)"),
win, 0, 4)
newLine(win)
botstring = "{} @ {} UTC".format(post["visibility"],
post["created_at"
].strftime(
"%Y-%m-%d %H:%M:%S"))
cy, cx = win.getyx()
my, mx = win.getmaxyx()
# win.addstr(cy, mx - len(botstring), botstring, curses.A_UNDERLINE)
win.move(cy, mx - len(botstring))
typeset(botstring, win, curses.A_UNDERLINE, 0)
newLine(win, 1)
def printAtLevel(stdscr, col1, col2, posts, parser, level):
col1.clear()
col2.clear()
col1.move(0, 0)
col2.move(0, 0)
printPost(col1, posts[level], parser)
newLine(col1)
printPost(col1, posts[(level + 1) % len(posts)], parser)
try:
col2.addstr(str(posts[level]["content"]))
col2.addstr(str(posts[level]["media_attachments"]))
col2.addstr(str(posts[level].keys()))
except curses.error:
pass
stdscr.noutrefresh()
col1.noutrefresh()
col2.noutrefresh()
curses.doupdate()
class PostParser(HTMLParser):
def __init__(self, ncwin, defncatt):
HTMLParser.__init__(self)
self.win = ncwin
self.defncatt = defncatt
self.curatt = defncatt
self.openp = False
self.colour = 0
self.colstack = []
def handle_starttag(self, tag, attrs):
if tag == 'p':
self.curatt = self.defncatt
self.colstack = []
self.colour = 0
# newLine(self.win)
elif tag == 'strong' or tag == 'b':
self.curatt = self.curatt ^ curses.A_REVERSE
elif tag == 'em' or tag == 'i':
self.curatt = self.curatt ^ curses.A_BOLD
elif tag == 'br':
newLine(self.win)
elif tag == 'a':
self.colstack.append(self.colour)
self.colour = 1
elif tag == 'code':
self.colstack.append(self.colour)
self.colour = 3
elif tag == 'img':
typeset("[IMG: {}, <{}>]".format(att["alt"], att["src"]), self.win,
0, 4)
def handle_endtag(self, tag):
if tag == 'p':
newLine(self.win, 2)
self.openp = False
elif tag == 'strong' or tag == 'b':
self.curatt = self.curatt ^ curses.A_REVERSE
elif tag == 'em' or tag == 'i':
self.curatt = self.curatt ^ curses.A_BOLD
elif tag == 'a':
self.colour = self.colstack.pop()
elif tag == 'code':
self.colour = self.colstack.pop()
def handle_data(self, data):
try:
# self.win.addstr(data, self.curatt | curses.color_pair(self.colour))
typeset(data, self.win, self.curatt, self.colour)
except curses.error:
pass
self.openp = True
# print(mastodon.timeline()[0]["content"])
def main(stdscr):
initColours()
mastodon = Mastodon(
access_token = 'd100.club_usercred.secret',
api_base_url = 'd100.club'
)
posts = mastodon.timeline()
stdscr.clear()
# stdscr.addstr("test")
curses.halfdelay(10)
my, mx = stdscr.getmaxyx()
c1w = c2w = (mx - 3) // 2
if (mx % 2) != 0:
c2w = c2w - 1
colw1 = curses.newwin(my - 2, c1w, 1, 1)
colw2 = curses.newwin(my - 2, c2w, 1, 1 + c1w + 2)
stdscr.border()
# colw1.border()
# colw2.border()
parser = PostParser(colw1, 0)
# colw1.move(0,0)
# stdscr.addstr(0, 0, 'test')
# colw2.addstr(0, 0, 'test2')
# colw1.addstr(0, 0, 'test3')
i = 'c'
q = 0
printAtLevel(stdscr, colw1, colw2, posts, parser, q)
con = True
while con:
i = stdscr.getch()
if (i == ord('q')):
con = False
elif (i == ord('j')):
q = (q + 1) % len(posts)
printAtLevel(stdscr, colw1, colw2, posts, parser, q)
elif (i == ord('k')):
q = (q - 1) % len(posts)
printAtLevel(stdscr, colw1, colw2, posts, parser, q)
curses.wrapper(main)