#!/usr/bin/env python2 import sys, os, json, tempfile, urllib2, urllib, json from PyQt4 import QtGui, QtCore, QtWebKit, QtNetwork from threading import Thread settings = QtCore.QSettings("jabs.nu", "ttrssl") class MainWindow(QtGui.QMainWindow): def __init__(self): QtGui.QMainWindow.__init__(self) 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())) session_id = self.get("session_id") server_url = self.get("server_url") if not (session_id and server_url): session_id = sys.argv[2] server_url = sys.argv[1] self.put("session_id", session_id) self.put("session_id", server_url) self.tinyTinyRSS = TinyTinyRSS(self, server_url, session_id) self.content = Content(self) self.setCentralWidget(self.content) self.content.evaluateJavaScript("setArticle()") self.content.reload() 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) 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.setPage(WebPage(self.wb)) 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_close = QtGui.QShortcut("Ctrl+W", self, activated=lambda: container.close()) self.do_show_next = QtGui.QShortcut("Space", self, activated=lambda: self.showNext()) self.do_show_previous = QtGui.QShortcut("Backspace", self, activated=lambda: self.showPrevious()) self.do_show_previous_k = QtGui.QShortcut("k", self, activated=lambda: self.showPrevious()) self.do_show_next_j = QtGui.QShortcut("j", self, activated=lambda: self.showNext()) self.do_open_current = QtGui.QShortcut("Return", self, activated=lambda: self.openCurrent()) self.do_reload = QtGui.QShortcut("r", self, activated=lambda: self.reload()) self.do_quit = QtGui.QShortcut("Ctrl+q", self, activated=lambda: container.close()) self.zoomIn = QtGui.QShortcut("Ctrl++", self, activated=lambda: self.wb.setZoomFactor(self.wb.zoomFactor() + 0.2)) self.zoomOut = QtGui.QShortcut("Ctrl+-", self, activated=lambda: self.wb.setZoomFactor(self.wb.zoomFactor() - 0.2)) self.zoomOne = QtGui.QShortcut("Ctrl+0", self, activated=lambda: self.wb.setZoomFactor(1)) 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): self.unread_articles = self.app.tinyTinyRSS.getUnreadFeeds() self.index = 0 self.setUnreadCount() if len(self.unread_articles) > 0: self.showNext() def showNext(self): if len(self.unread_articles) > self.index: if self.index > 0: previous = self.unread_articles[self.index - 1] self.app.tinyTinyRSS.setArticleRead(previous["id"]) next = self.unread_articles[self.index] self.setArticle(next) self.setUnreadCount() self.index += 1 else: if self.index > 0: previous = self.unread_articles[self.index - 1] self.app.tinyTinyRSS.setArticleRead(previous["id"]) self.setUnreadCount() def showPrevious(self): if self.index > 0: self.index -= 1 previous = self.unread_articles[self.index] self.setArticle(previous) self.setUnreadCount() 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) unread = length - self.index self.app.setWindowTitle(" (" + str(unread) + "/" + str(length) + ")") if unread < 1: self.evaluateJavaScript("setArticle()") def templateString(self): return """ ttrssl

""" class TinyTinyRSS: def __init__(self, app, server_url, session_id): self.app = app self.server_url = server_url self.session_id = session_id def doOperation(self, operation, options=None): url = self.server_url + "/api/" default_options = {'sid': self.session_id, 'op': operation} if options: options = dict(default_options.items() + options.items()) else: options = default_options json_string = json.dumps(options) req = urllib2.Request(url) fd = urllib2.urlopen(req, json_string) body = "" while 1: data = fd.read(1024) if not len(data): break body += data return json.loads(body)["content"] def getUnreadFeeds(self): return self.doOperation("getHeadlines", {"show_excerpt":False, "view_mode":"unread", "show_content":True, "feed_id": -3}) def setArticleRead(self, article_id): l = lambda: self.doOperation("updateArticle", {'article_ids':article_id, 'mode': 0, 'field': 2}) t = Thread(target=l) t.start() if __name__ == "__main__": app = QtGui.QApplication(sys.argv) wb = MainWindow() wb.show() sys.exit(app.exec_())