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 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 = "" focus_widget, idx = self.get_focus() if self.isCategoryView: try: self.currentCategory = focus_widget.attr_map[None][0] except BaseException: self.currentCategory = None 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]) 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 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): itemId = attrMap[0] name = attrMap[1] tui.articles = tui.cache.getArticlesFromCategory(itemId, tui.show_read) tui.articleView.fill(tui.articles) 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) 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 = focus_widget.attr_map[None][0] self.setCategoryArticles(focus_widget.attr_map[None]) 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 = focus_widget.attr_map[None][0] self.setCategoryArticles(focus_widget.attr_map[None]) else: self.setFeedArticles(focus_widget.attr_map[None]) except BaseException: pass return elif key in ("l", "right"): try: if self.isCategoryView: self.isCategoryView = False focus_widget, idx = self.get_focus() self.categoryPosition = idx categoryId = focus_widget.attr_map[None][0] categoryName = focus_widget.attr_map[None][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 return elif key in ("h", "left"): 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(focus_widget.attr_map[None]) return return super().keypress(size, key) 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): status = "" if tui.show_read: status = "R" items = [ urwid.AttrMap( urwid.Columns( [(2, urwid.Text(status)), (16, urwid.Text(article[3])), urwid.Text(article[1])]), article[0], "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 ("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 = focus_widget.attr_map[None][0] tui.feedView.setCategoryArticles(focus_widget.attr_map[None]) 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.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) focusFeed, idx = tui.feedView.get_focus() tui.rightBox.set_title(focusFeed.attr_map[None][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 = link.split(".")[-1] if Utils.checkPic(ext.lower()): os.system('nohup feh ' + link + ' /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", "dark blue", "black"), ("text", "dark cyan", "dark cyan"), ('header', 'white', '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) focus_widget, idx = self.feedView.get_focus() item = focus_widget.attr_map[None][0] name = focus_widget.attr_map[None][1] self.articles = self.cache.getArticlesFromCategory(item, self.show_read) self.articleView.fill(self.articles) 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 == "f": 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: self.feedView.setCategoryArticles(focus_widget.attr_map[None]) 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, self.show_read) else: self.feedView.fill(feeds, self.show_read) try: self.feedView.set_focus(idx) except BaseException: pass return tui = TUI.create() tui.run()