import urwid import yaml import asyncio import warnings import subprocess import os from concurrent.futures import ThreadPoolExecutor from API import Fetcher from DB import DB from Render import Article 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["date"])), urwid.Text(category["name"]), (5, urwid.Text(str(category["count"])))]), (category["id"], category["name"], category["count"]), "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: self.currentCategory = focus_widget.attr_map[None][0] 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] number = attrMap[2] name = attrMap[1] await tui.fetcher.articlesFromCategoryAsync(itemId, str(number)) focus_widget, idx = self.get_focus() currentCategory = focus_widget.attr_map[None][0] if itemId == currentCategory: tui.articles = tui.fetcher.articles[currentCategory] tui.articleView.fill(tui.articles) self.setArticlesPaneTitle(name) def setFeedArticles(self, attrMap): itemId = attrMap[0] tui.articles = tui.fetcher.articlesFromFeed(itemId, self.currentCategory) 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 and tui.fetcher.checkCategory(self.currentCategory): 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.fetcher.feedsFromCategory(categoryId) feedItems = [urwid.AttrMap(urwid.Columns([(16, urwid.Text(tui.fetcher.unreadCounts[feed["id"]][2])), urwid.Text(feed["title"]), (5, urwid.Text(str(tui.fetcher.unreadCounts[feed["id"]][0])))]), (feed["id"], feed["title"]), "reveal focus") for feed in feeds] walker = urwid.SimpleListWalker(feedItems) self.body = walker 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 items = [ urwid.AttrMap( urwid.Columns( [(16, urwid.Text(category["date"])), urwid.Text(category["name"]), (5, urwid.Text(str(category["count"])))]), (category["id"], category["name"], category["count"]), "reveal focus") for category in tui.fetcher.categories] walker = urwid.SimpleFocusListWalker(items) self.body = walker tui.leftBox.set_title("Categories") self.set_focus(self.categoryPosition) focus_widget, idx = self.get_focus() tui.articles = self.getArticlesFromCategory(focus_widget.attr_map[None][0]) tui.articleView.fill(tui.articles) self.setArticlesPaneTitle(self.currentCategory[13:]) 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(Utils.timestampToDate(article["timestampUsec"]))), urwid.Text(article["title"])]), article["id"], "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.DB = DB(self.fetcher) self.DB.refresh() self.leftPaneItems = {} self.activePane = False self.executor = ThreadPoolExecutor(max_workers=1) self.loop = None self.feedView = LeftPane(self.fetcher.categories) 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 run(self): focus_widget, idx = self.feedView.get_focus() item = focus_widget.attr_map[None][0] name = focus_widget.attr_map[None][1] self.fetcher.articlesFromCategory(item, str(focus_widget.attr_map[None][2])) self.articles = tui.fetcher.articles[item] self.articleView.fill(self.articles) self.feedView.setArticlesPaneTitle(name) 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.fetcher.fetch() for text in articles: olb.body = urwid.SimpleListWalker([urwid.Text(text)]) tui.loop.entering_idle() self.body = overlay.bottom_w tui = TUI.create() tui.run()