From 77bccc067c328a3bfd84bde3051492d3d65fbb7a Mon Sep 17 00:00:00 2001 From: jeena Date: Tue, 23 Apr 2013 20:20:50 +0200 Subject: [PATCH] initial commit --- README.md | 1 + feedthemonkey.py | 261 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+) create mode 100644 README.md create mode 100755 feedthemonkey.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..30c6868 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +ttrssl is a desktop client for TinyTinyRSS \ No newline at end of file diff --git a/feedthemonkey.py b/feedthemonkey.py new file mode 100755 index 0000000..87d08de --- /dev/null +++ b/feedthemonkey.py @@ -0,0 +1,261 @@ +#!/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_()) \ No newline at end of file