#!/usr/bin/env python2 try: import urllib.request as urllib2 except: import urllib2 import sys, os, json, tempfile, urllib, json from PyQt4 import QtGui, QtCore, QtWebKit, QtNetwork from threading import Thread from sys import platform as _platform settings = QtCore.QSettings("jabs.nu", "feedthemonkey") class MainWindow(QtGui.QMainWindow): def __init__(self): QtGui.QMainWindow.__init__(self) self.setWindowIcon(QtGui.QIcon("feedmonkey")) self.addAction(QtGui.QAction("Full Screen", self, checkable=True, toggled=lambda v: self.showFullScreen() if v else self.showNormal(), shortcut="F11")) self.history = self.get("history", []) self.restoreGeometry(QtCore.QByteArray.fromRawData(settings.value("geometry").toByteArray())) self.restoreState(QtCore.QByteArray.fromRawData(settings.value("state").toByteArray())) self.initUI() session_id = self.get("session_id") server_url = self.get("server_url") if not (session_id and server_url): self.authenticate() else: self.initApp() def initUI(self): self.list = List(self) self.content = Content(self) self.splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self) self.splitter.setHandleWidth(1) self.splitter.addWidget(self.list) self.splitter.addWidget(self.content) self.splitter.restoreState(settings.value("splitterSizes").toByteArray()); self.splitter.splitterMoved.connect(self.splitterMoved) self.setCentralWidget(self.splitter) def mkAction(name, connect, shortcut=None): action = QtGui.QAction(name, self) action.triggered.connect(connect) if shortcut: action.setShortcut(shortcut) return action mb = self.menuBar() fileMenu = mb.addMenu("&File") fileMenu.addAction(mkAction("&Close", self.close, "Ctrl+W")) fileMenu.addAction(mkAction("&Log Out", self.logOut)) fileMenu.addSeparator() fileMenu.addAction(mkAction("&Exit", self.close, "Ctrl+Q")) actionMenu = mb.addMenu("&Action") actionMenu.addAction(mkAction("&Reload", self.content.reload, "R")) actionMenu.addAction(mkAction("Show &Starred", self.content.showStarred, "*")) actionMenu.addAction(mkAction("Set &Starred", self.content.setStarred, "S")) actionMenu.addAction(mkAction("Set &Unread", self.content.setUnread, "U")) actionMenu.addAction(mkAction("&Next", self.content.showNext, "J")) actionMenu.addAction(mkAction("&Previous", self.content.showPrevious, "K")) actionMenu.addAction(mkAction("&Open in Browser", self.content.openCurrent, "N")) viewMenu = mb.addMenu("&View") viewMenu.addAction(mkAction("Zoom &In", lambda: self.content.wb.setZoomFactor(self.content.wb.zoomFactor() + 0.2), "Ctrl++")) viewMenu.addAction(mkAction("Zoom &Out", lambda: self.content.wb.setZoomFactor(self.content.wb.zoomFactor() - 0.2), "Ctrl+-")) viewMenu.addAction(mkAction("&Reset", lambda: self.content.wb.setZoomFactor(1), "Ctrl+0")) windowMenu = mb.addMenu("&Window") windowMenu.addAction(mkAction("Reset to Default", self.resetSplitter, "Ctrl+D")) helpMenu = mb.addMenu("&Help") helpMenu.addAction(mkAction("&About", lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://jabs.nu/feedthemonkey", QtCore.QUrl.TolerantMode)) )) def initApp(self): session_id = self.get("session_id") server_url = self.get("server_url") self.tinyTinyRSS = TinyTinyRSS(self, server_url, session_id) self.content.evaluateJavaScript("setArticle('loading')") self.content.reload() self.show() def closeEvent(self, ev): settings.setValue("geometry", self.saveGeometry()) settings.setValue("state", self.saveState()) return QtGui.QMainWindow.closeEvent(self, ev) def put(self, key, value): "Persist an object somewhere under a given key" settings.setValue(key, json.dumps(value)) settings.sync() def get(self, key, default=None): "Get the object stored under 'key' in persistent storage, or the default value" v = settings.value(key) return json.loads(unicode(v.toString())) if v.isValid() else default def setWindowTitle(self, t): super(QtGui.QMainWindow, self).setWindowTitle("Feed the Monkey" + t) def splitterMoved(self, pos, index): settings.setValue("splitterSizes", self.splitter.saveState()); def resetSplitter(self): sizes = self.splitter.sizes() top = sizes[0] bottom = sizes[1] sizes[0] = 200 sizes[1] = bottom + top - 200 self.splitter.setSizes(sizes) def authenticate(self): dialog = Login() def callback(): server_url = str(dialog.textServerUrl.text()) user = str(dialog.textName.text()) password = str(dialog.textPass.text()) session_id = TinyTinyRSS.login(server_url, user, password) if session_id: self.put("session_id", session_id) self.put("server_url", server_url) self.initApp() else: self.authenticate() dialog.accepted.connect(callback) dialog.exec_() def logOut(self): self.hide() self.content.evaluateJavaScript("setArticle('logout')") self.tinyTinyRSS.logOut() self.tinyTinyRSS = None self.put("session_id", None) self.put("server_url", None) self.authenticate() class List(QtGui.QTableWidget): def __init__(self, container): QtGui.QTableWidget.__init__(self) self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) self.app = container self.itemSelectionChanged.connect(self.rowSelected) self.setShowGrid(False) def initHeader(self): self.clear() self.setColumnCount(5) self.setHorizontalHeaderLabels(("*", "Feed", "Title", "Date", "Author")) self.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.ResizeToContents) self.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch) self.horizontalHeader().setResizeMode(2, QtGui.QHeaderView.Stretch) self.horizontalHeader().setResizeMode(3, QtGui.QHeaderView.ResizeToContents) self.horizontalHeader().setResizeMode(4, QtGui.QHeaderView.ResizeToContents) self.verticalHeader().hide() def setItems(self, articles): self.initHeader() self.setRowCount(len(articles)) row = 0 for article in articles: if "marked" in article: starred = QtGui.QTableWidgetItem("*" if article["marked"] else "") starred.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.setItem(row, 0, starred) if "feed_title" in article: feed_title = QtGui.QTableWidgetItem(article["feed_title"]) feed_title.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.setItem(row, 1, feed_title) if "title" in article: title = QtGui.QTableWidgetItem(article["title"]) title.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.setItem(row, 2, title) if "updated" in article: date = QtCore.QDateTime.fromTime_t(article["updated"]).toString(QtCore.Qt.SystemLocaleShortDate) d = QtGui.QTableWidgetItem(date) d.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.setItem(row, 3, d) if "author" in article: author = QtGui.QTableWidgetItem(article["author"]) author.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.setItem(row, 4, author) row += 1 self.selectRow(0) def rowSelected(self): indexes = self.selectedIndexes() if len(indexes) > 0: row = indexes[0].row() self.app.content.showIndex(row) def updateRead(self): for row, article in enumerate(self.app.content.unread_articles): for x in xrange(0,5): item = self.item(row, x) font = item.font() font.setBold(article["unread"]) item.setFont(font) def setStarred(self, index, starred): widget = self.itemAt(index, 0) widget.setText("*" if starred else "") class Content(QtGui.QWidget): def __init__(self, container): QtGui.QWidget.__init__(self) self.app = container self.index = 0 self.wb = QtWebKit.QWebView(titleChanged=lambda t: container.setWindowTitle(t)) self.wb.page().setLinkDelegationPolicy(QtWebKit.QWebPage.DelegateAllLinks) self.wb.linkClicked.connect(lambda url: self.openLink(url)) self.setLayout(QtGui.QVBoxLayout(spacing=0)) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.wb) self.do_show_next = QtGui.QShortcut(QtCore.Qt.Key_Right, self, activated=self.showNext) self.do_show_previous = QtGui.QShortcut(QtCore.Qt.Key_Left, self, activated=self.showPrevious) self.do_open = QtGui.QShortcut("Return", self, activated=self.openCurrent) self.wb.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True) self.wb.settings().setIconDatabasePath(tempfile.mkdtemp()) self.wb.setHtml(self.templateString()) self.unread_articles = [] def openLink(self, url): QtGui.QDesktopServices.openUrl(url) def reload(self): w = WorkerThread(self.app, self._reload) self.connect(w, QtCore.SIGNAL("reload_done()"), self.reload_done) w.start() def showStarred(self): w = WorkerThread(self.app, self._showStarred) self.connect(w, QtCore.SIGNAL("reload_done()"), self.reload_done) w.start() def setUnread(self): article = self.unread_articles[self.index] article["unread"] = True article["set_unread"] = True self.app.list.updateRead() self.app.tinyTinyRSS.setArticleRead(article["id"], False) def setStarred(self): article = self.unread_articles[self.index] article["marked"] = not article["marked"] self.app.tinyTinyRSS.setArticleStarred(article["id"], article["marked"]) self.app.list.setStarred(self.index, article["marked"]) def _reload(self): self.unread_articles = self.app.tinyTinyRSS.getUnreadFeeds() self.index = -1 def _showStarred(self): self.unread_articles = self.app.tinyTinyRSS.getStarredFeeds() self.index = -1 def reload_done(self): self.setUnreadCount() self.app.list.setItems(self.unread_articles) def showIndex(self, index): if self.index > -1: previous = self.unread_articles[self.index] if not "set_unread" in previous or not previous["set_unread"]: self.app.tinyTinyRSS.setArticleRead(previous["id"]) previous["unread"] = False else: previous["set_unread"] = False self.app.list.updateRead() self.index = index current = self.unread_articles[self.index] self.setArticle(current) self.setUnreadCount() def showNext(self): if self.index + 1 < len(self.unread_articles): self.app.list.selectRow(self.index + 1) def showPrevious(self): if self.index > 0: self.app.list.selectRow(self.index - 1) def openCurrent(self): current = self.unread_articles[self.index] url = QtCore.QUrl(current["link"]) self.openLink(url) def setArticle(self, article): func = u"setArticle({});".format(json.dumps(article)) self.evaluateJavaScript(func) def evaluateJavaScript(self, func): return self.wb.page().mainFrame().evaluateJavaScript(func) def setUnreadCount(self): length = len(self.unread_articles) i = 0 if self.index > 0: i = self.index unread = length - i self.app.setWindowTitle(" (" + str(unread) + "/" + str(length) + ")") if unread < 1: self.evaluateJavaScript("setArticle('empty')") def templateString(self): html=""" ttrssl

""" return html # string.replace(html, "