import urwid import yaml import warnings import subprocess import os from concurrent.futures import ThreadPoolExecutor from API import Fetcher from Cache import Cache from Render import Render from FileCache import FileCache import Utils warnings.filterwarnings("ignore") class LeftPane(urwid.ListBox): def __init__(self, categories): super().__init__(self) items = [urwid.AttrMap(urwid.Columns([ (16, urwid.Text(category[4])), urwid.Text(category[1]), (5, urwid.Text(str(category[2])))]), (category[0], category[1], category[2]), "reveal focus") for category in categories] walker = urwid.SimpleListWalker(items) self.body = walker self.categoryPosition = 0 self.isCategoryView = True self.currentCategory = "" def processAttrMap(self, attrMap): res = attrMap[None] if res == "favorite": return attrMap["attrs"] else: return res def fill(self, items, is_category_view): def getAttrs(item): if is_category_view is True: return (item[0], item[1], item[2]) else: return (item[0], item[1]) if is_category_view is True: items = [ urwid.AttrMap( urwid.Columns( [(16, urwid.Text(items[0][4])), urwid.Text(items[0][1]), (5, urwid.Text(str(items[0][2])))]), {None: "favorite", "attrs": getAttrs(items[0])}, "reveal focus"), * [urwid.AttrMap( urwid.Columns([(16, urwid.Text(item[4])), urwid.Text(item[1]), (5, urwid.Text(str(item[2])))]), getAttrs(item), "reveal focus") for item in items[1:]]] else: items = [urwid.AttrMap(urwid.Columns([ (16, urwid.Text(item[4])), urwid.Text(item[1]), (5, urwid.Text(str(item[2])))]), getAttrs(item), "reveal focus") for item in items] walker = urwid.SimpleListWalker(items) self.body = walker focus_widget, idx = self.get_focus() if self.isCategoryView: try: self.currentCategory = focus_widget.attr_map[None][0] except BaseException: self.currentCategory = None def findById(self, id): idx = 0 for idx, item in zip(range(len(self.body)), self.body): if item.attr_map[None][0] == id: break return idx def setArticlesPaneTitle(self, text): tui.rightBox.set_title(text) def setCategoryArticles(self, attrMap): Utils.writeLog(attrMap) itemId = attrMap[0] name = attrMap[1] tui.articles = tui.cache.getArticlesFromCategory(itemId, tui.show_read) tui.articleView.fill(tui.articles, True) self.setArticlesPaneTitle(name) def setFeedArticles(self, attrMap): itemId = attrMap[0] tui.articles = tui.cache.getArticlesFromFeed(itemId, tui.show_read) if tui.articles is not None: tui.articleView.fill(tui.articles, False) self.setArticlesPaneTitle(attrMap[1]) def keypress(self, size, key): # noqa if key in ("j", "down"): item_size = len(self.body) try: focus_widget, idx = self.get_focus() if idx < item_size - 1: idx = idx + 1 self.set_focus(idx) focus_widget, idx = self.get_focus() if self.isCategoryView: self.currentCategory = self.processAttrMap(focus_widget.attr_map)[0] self.setCategoryArticles(self.processAttrMap(focus_widget.attr_map)) else: self.setFeedArticles(focus_widget.attr_map[None]) except BaseException: pass return elif key in ("k", "up"): try: focus_widget, idx = self.get_focus() if idx > 0: idx = idx - 1 self.set_focus(idx) focus_widget, idx = self.get_focus() if self.isCategoryView: self.currentCategory = self.processAttrMap(focus_widget.attr_map)[0] self.setCategoryArticles(self.processAttrMap(focus_widget.attr_map)) else: self.setFeedArticles(focus_widget.attr_map[None]) except BaseException: pass return elif key in ("l", "right"): self.stepInto() return elif key in ("h", "left"): self.stepOut() return elif key in ("r"): self.markAsRead() return return super().keypress(size, key) def stepOut(self): if not self.isCategoryView: self.isCategoryView = True self.fill(tui.categories, True) tui.leftBox.set_title("Categories") self.set_focus(self.categoryPosition) focus_widget, idx = self.get_focus() self.setCategoryArticles(self.processAttrMap(focus_widget.attr_map)) def stepInto(self): try: if self.isCategoryView and self.currentCategory != "Favorites": self.isCategoryView = False focus_widget, idx = self.get_focus() self.categoryPosition = idx categoryId = self.processAttrMap(focus_widget.attr_map)[0] categoryName = self.processAttrMap(focus_widget.attr_map)[1] feeds = tui.cache.getFeeds(categoryId, tui.show_read) self.fill(feeds, False) focus_widget, idx = self.get_focus() self.setFeedArticles(focus_widget.attr_map[None]) tui.leftBox.set_title(categoryName) except BaseException: pass def markAsRead(self): try: focus_widget, idx = self.get_focus() if self.processAttrMap(focus_widget.attr_map)[0] == "Favorites": return del self.body[idx] if idx > 0: idx -= 1 self.set_focus(idx) tui.cache.markStreamAsRead(self.processAttrMap(focus_widget.attr_map)[0]) tui.categories = tui.cache.getCategories(tui.show_read) focus_widget, idx = self.get_focus() if focus_widget is None and not self.isCategoryView: self.stepOut() return if self.isCategoryView: self.currentCategory = self.processAttrMap(focus_widget.attr_map)[0] self.setCategoryArticles(self.processAttrMap(focus_widget.attr_map)) else: self.setFeedArticles(focus_widget.attr_map[None]) except BaseException: pass class RightPane(urwid.ListBox): def __init__(self, items): super().__init__(self) walker = urwid.SimpleListWalker([]) self.body = walker self.isList = True self.articlePosition = 0 self.article = None self.chunkNumber = 0 def fill(self, articles, isCategoryView): def makeColumns(article): if tui.show_read: style_text = "item_read" style_feed = "feed_read" else: style_text = "item" style_feed = "feed" title = article[1] if isCategoryView: title = [(style_text, title), (style_feed, " — " + article[-1])] cols = [ (16, urwid.Text([(style_text, article[3])])), urwid.Text([(style_text, title)]), ] return cols items = [ urwid.AttrMap( urwid.Columns(makeColumns(article)), article[0], {"feed_read": "reveal focus", "feed": "reveal focus", None: "reveal focus", "item": "reveal focus", "item_read": "reveal focus"}) for article in articles] walker = urwid.SimpleListWalker(items) self.body = walker def getArticle(self, articleId): article = [item for item in tui.fetcher.articles[tui.feedView.currentCategory] if item["id"] == articleId][0] return article def setArticleTitle(self): tui.rightBox.set_title(self.article.title + " (" + str(self.article.currentPageNumber) + "/" + str(self.article.numberOfPages) + ")") def keypress(self, size, key): # noqa if key in ("j", "down"): if not self.isList: walker = urwid.SimpleListWalker([urwid.Text(self.article.scrollDown())]) self.body = walker self.setArticleTitle() item_size = len(self.body) try: focus_widget, idx = self.get_focus() if idx < item_size - 1: idx = idx + 1 self.set_focus(idx) except BaseException: pass return elif key in ("k", "up"): if not self.isList: walker = urwid.SimpleListWalker([urwid.Text(self.article.scrollUp())]) self.body = walker self.setArticleTitle() try: focus_widget, idx = self.get_focus() if idx > 0: idx = idx - 1 self.set_focus(idx) except BaseException: pass return elif key in ("f"): if self.isList is True: article_widget, article_idx = self.get_focus() articleId = article_widget.attr_map[None] tui.cache.toggleArticleStarred(articleId) tui.categories = tui.cache.getCategories(tui.show_read) item_widget, item_idx = tui.feedView.get_focus() if tui.feedView.isCategoryView: tui.feedView.fill(tui.categories, tui.feedView.isCategoryView) tui.feedView.set_focus(item_idx) if item_idx == 0: tui.feedView.setCategoryArticles(('Favorites', 'Favorites')) elif key in ("r"): if self.isList is True: feeds = [] article_widget, article_idx = self.get_focus() articleId = article_widget.attr_map[None] tui.cache.toggleArticleStatus(articleId) item_widget, item_idx = tui.feedView.get_focus() itemAttrMap = item_widget.attr_map[None] if tui.feedView.isCategoryView: tui.feedView.setCategoryArticles(itemAttrMap) else: tui.feedView.setFeedArticles(itemAttrMap) if article_idx > 0: article_idx -= 1 tui.categories = tui.cache.getCategories(tui.show_read) if tui.feedView.isCategoryView: feeds = tui.categories else: feeds = tui.cache.getFeeds(tui.feedView.currentCategory, tui.show_read) tui.feedView.fill(feeds, tui.feedView.isCategoryView) new_idx = tui.feedView.findById(itemAttrMap[0]) try: tui.feedView.set_focus(new_idx) focus_widget, idx = tui.feedView.get_focus() if tui.feedView.isCategoryView: tui.feedView.currentCategory = tui.feedView.processAttrMap(focus_widget.attr_map)[0] tui.feedView.setCategoryArticles(tui.feedView.processAttrMap(focus_widget.attr_map)) else: tui.feedView.setFeedArticles(focus_widget.attr_map[None]) try: self.set_focus(article_idx) except BaseException: pass except BaseException: pass return elif key in ("l", "right"): if self.isList is True: self.isList = False self.chunkNumber = 0 try: focus_widget, idx = self.get_focus() self.articlePosition = idx articleId = focus_widget.attr_map[None] self.article = Render(*tui.cache.getArticle(articleId, tui.feedView.currentCategory == "Favorites"), tui.cache.getArticleLinks(articleId)) walker = urwid.SimpleListWalker([urwid.Text(self.article.firstPage)]) self.body = walker self.setArticleTitle() except BaseException: pass return elif key in ("c"): if self.isList is False: self.article.loadComments() walker = urwid.SimpleListWalker([urwid.Text(self.article.firstPage)]) self.body = walker return elif key in ("h", "left"): try: if self.isList is False: self.isList = True self.fill(tui.articles, tui.feedView.isCategoryView) focusFeed, idx = tui.feedView.get_focus() tui.rightBox.set_title(tui.feedView.processAttrMap(focusFeed.attr_map)[1]) self.set_focus(self.articlePosition) except BaseException: pass return elif key == "m": if self.isList is False and len(self.article.links) > 0: top = Links(self.article.links) tui.createOverlay(urwid.LineBox(top), len(self.article.links)+2) return elif key == "v": if self.isList is False: subprocess.Popen(['firefox', '-new-tab', self.article.url]) return return super().keypress(size, key) class Links(urwid.ListBox): def __init__(self, links): super().__init__(self) items = [urwid.AttrMap(urwid.Columns([urwid.Text(link)]), (link), "reveal focus") for link in links] walker = urwid.SimpleFocusListWalker(items) self.body = walker def parseLink(self, link): ext = Utils.checkPic(link.split(".")[-1]).lower() if ext: fc = FileCache(link, ext) fp = fc.fetch() if fp is not None: os.system('nohup feh ' + fp + ' /dev/null 2>&1 &') elif Utils.checkStreamingVideo(link): tui.destroyOverlay() os.system( 'nohup mpv --ytdl-format="bestvideo[height<=720]+bestaudio/best[height<=720]" "' + link + '" /dev/null 2>&1 &') else: subprocess.Popen(['firefox', '-new-tab', link]) def keypress(self, size, key): if key in ("j", "down"): focus_widget, idx = self.get_focus() item_size = len(self.body) if idx < item_size - 1: idx = idx + 1 self.set_focus(idx) return elif key in ("k", "up"): focus_widget, idx = self.get_focus() if idx > 0: idx = idx - 1 self.set_focus(idx) return elif key in ("l", "right"): focus_widget, idx = self.get_focus() link = focus_widget.attr_map[None] self.parseLink(link) elif key in ("q", "esc"): tui.destroyOverlay() return class TUI(urwid.Frame): @classmethod def create(cls): tui = cls() palette = [("linebox", "bold", "dark cyan", "standout"), ("text", "dark cyan", "dark cyan"), ("favorite", "dark green", "black"), ("feed", "bold", "black"), ("feed_read", "bold, dark blue", "black"), ("item", "white", "black"), ('item_read', 'dark blue', 'black'), ('reveal focus', 'black', 'dark cyan', 'standout')] loop = urwid.MainLoop( tui, palette, event_loop=urwid.AsyncioEventLoop(), unhandled_input=tui.unhandled_input, ) tui.loop = loop return tui def __init__(self): with open(os.path.expanduser('~') + '/.config/inomnibus/config.yml', 'r') as file: config = yaml.safe_load(file) URL = config["server"]["URL"] token = config["server"]["token"] self.show_read = 0 self.overlay = None self.fetcher = Fetcher(URL, token) self.cache = Cache(self.fetcher) self.categories = self.cache.getCategories(self.show_read) self.leftPaneItems = {} self.activePane = False self.executor = ThreadPoolExecutor(max_workers=1) self.loop = None self.feedView = LeftPane([]) self.articleView = RightPane([]) self.leftBox = urwid.LineBox(self.feedView, title="Categories") self.rightBox = urwid.LineBox(self.articleView, title="Articles") self.views = urwid.Columns( [(56, self.leftBox), self.rightBox]) self.container = urwid.Pile([self.views]) self.body = self.container self.articles = [] super().__init__(self.body) def initialize_panes(self): try: self.feedView.fill(self.cache.getCategories(self.show_read), True) try: self.feedView.set_focus(1) except BaseException: pass focus_widget, idx = self.feedView.get_focus() item = self.feedView.processAttrMap(focus_widget.attr_map)[0] name = self.feedView.processAttrMap(focus_widget.attr_map)[1] self.articles = self.cache.getArticlesFromCategory(item, self.show_read) self.articleView.fill(self.articles, True) self.feedView.setArticlesPaneTitle(name) except BaseException: pass def run(self): self.initialize_panes() self.loop.run() self.executor.shutdown(wait=False) def createOverlay(self, element, h): self.overlay = urwid.Overlay( element, self.body, align="center", width=("relative", 50), valign="middle", height=h) self.body = self.overlay def destroyOverlay(self): self.body = self.overlay.bottom_w del(self.overlay) def unhandled_input(self, key): if key == "tab": self.activePane = not self.activePane self.views.set_focus(int(self.activePane)) if self.activePane: tui.articleView.isList = True elif key == "q": raise urwid.ExitMainLoop() elif key == "S": olb = urwid.ListBox(urwid.SimpleListWalker([urwid.Text("")])) overlay = urwid.Overlay( urwid.LineBox(olb), self.body, align="center", width=("relative", 50), valign="middle", height=3) self.body = overlay articles = tui.cache.refresh() for text in articles: olb.body = urwid.SimpleListWalker([urwid.Text(text)]) tui.loop.entering_idle() self.body = overlay.bottom_w self.initialize_panes() elif key == "R": self.show_read = int(not self.show_read) focus_widget, idx = self.feedView.get_focus() if self.feedView.isCategoryView: if focus_widget.attr_map.get("attrs") is not None: attr_map = focus_widget.attr_map["attrs"] else: attr_map = focus_widget.attr_map[None] self.feedView.setCategoryArticles(attr_map) else: self.feedView.setFeedArticles(focus_widget.attr_map[None]) self.categories = tui.cache.getCategories(self.show_read) feeds = tui.cache.getFeeds(self.feedView.currentCategory, self.show_read) if self.feedView.isCategoryView: self.feedView.fill(self.categories, True) else: self.feedView.fill(feeds, False) try: self.feedView.set_focus(idx) except BaseException: pass return tui = TUI.create() tui.run()