import urwid import yaml import asyncio import warnings import subprocess import os from concurrent.futures import ThreadPoolExecutor from API import Fetcher from Cache import Cache from Render import Article, 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 setArticlesPaneTitle(self, text): tui.rightBox.set_title(text) def getArticlesFromCategory(self, category, number=0): tui.fetcher.articlesFromCategory(category, str(number)) articles = tui.fetcher.articles[category] return articles def getArticlesFromFeed(self, feed): return tui.fetcher.articlesFromFeed(feed, self.currentCategory) async def setCategoryArticles(self, attrMap): itemId = attrMap[0] name = attrMap[1] tui.articles = tui.cache.getArticlesFromCategory(itemId) tui.articleView.fill(tui.articles) self.setArticlesPaneTitle(name) def setFeedArticles(self, attrMap): itemId = attrMap[0] tui.articles = tui.cache.getArticlesFromFeed(itemId) if tui.articles is not None: tui.articleView.fill(tui.articles) self.setArticlesPaneTitle(attrMap[1]) def keypress(self, size, key): if key in ("j", "down"): item_size = len(self.body) 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] asyncio.create_task(self.setCategoryArticles(focus_widget.attr_map[None])) else: self.setFeedArticles(focus_widget.attr_map[None]) return elif key in ("k", "up"): 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] asyncio.create_task(self.setCategoryArticles(focus_widget.attr_map[None])) else: self.setFeedArticles(focus_widget.attr_map[None]) return elif key in ("l", "right"): 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) self.fill(feeds, False) focus_widget, idx = self.get_focus() self.setFeedArticles(focus_widget.attr_map[None]) tui.leftBox.set_title(categoryName) 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() asyncio.create_task(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): items = [ urwid.AttrMap( urwid.Columns( [(2, urwid.Text("")), (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) focus_widget, idx = self.get_focus() if idx < item_size - 1: idx = idx + 1 self.set_focus(idx) return elif key in ("k", "up"): if not self.isList: walker = urwid.SimpleListWalker([urwid.Text(self.article.scrollUp())]) self.body = walker self.setArticleTitle() focus_widget, idx = self.get_focus() if idx > 0: idx = idx - 1 self.set_focus(idx) return elif key in ("l", "right"): if self.isList is True: self.isList = False self.chunkNumber = 0 focus_widget, idx = self.get_focus() self.articlePosition = idx articleId = focus_widget.attr_map[None] self.article = Article(self.getArticle(articleId)) walker = urwid.SimpleListWalker([urwid.Text(self.article.firstPage)]) self.body = walker self.setArticleTitle() return elif key in ("h", "left"): 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) 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 == "q": 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.overlay = None self.fetcher = Fetcher(URL, token) self.cache = Cache(self.fetcher) self.categories = self.cache.getCategories() 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(), 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.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 == "r": 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() tui = TUI.create() tui.run()