diff --git a/feedmonkey.png b/feedmonkey.png deleted file mode 100644 index f8f536d..0000000 Binary files a/feedmonkey.png and /dev/null differ diff --git a/feedthemonkey b/feedthemonkey deleted file mode 100755 index 87d08de..0000000 --- a/feedthemonkey +++ /dev/null @@ -1,261 +0,0 @@ -#!/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 diff --git a/feedthemonkey.desktop b/feedthemonkey.desktop index 792da0d..f0746e0 100644 --- a/feedthemonkey.desktop +++ b/feedthemonkey.desktop @@ -3,7 +3,7 @@ Version=0.1.0 Comment=A desktop client for the TinyTinyRSS feed reader. Exec=/usr/bin/feedthemonkey GenericName=Feed Reader -Icon=feedthemonkey +Icon=feedthemonkey.py Name=Feed the Monkey NoDisplay=false StartupNotify=true diff --git a/feedthemonkey.py b/feedthemonkey.py index 87d08de..cfed60f 100755 --- a/feedthemonkey.py +++ b/feedthemonkey.py @@ -9,28 +9,56 @@ settings = QtCore.QSettings("jabs.nu", "ttrssl") 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): - 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) - + if not (session_id and server_url): + self.authenticate() + else: + self.initApp() + + def initUI(self): self.content = Content(self) self.setCentralWidget(self.content) + menubar = self.menuBar() + + reloadAction = QtGui.QAction("&Reload", self) + reloadAction.setStatusTip("Load new data") + reloadAction.setShortcut("r") + reloadAction.triggered.connect(self.content.reload) + + logOutAction = QtGui.QAction("&Log Out", self) + logOutAction.setStatusTip("Log out from this entity") + logOutAction.triggered.connect(self.logOut) + + exitAction = QtGui.QAction("&Exit", self) + exitAction.setShortcut("Ctrl+Q") + exitAction.setStatusTip("Exit Feed the Monkey") + exitAction.triggered.connect(self.close) + + fileMenu = menubar.addMenu("&File") + fileMenu.addAction(reloadAction) + fileMenu.addAction(logOutAction) + fileMenu.addSeparator() + fileMenu.addAction(exitAction) + + 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()") self.content.reload() - def closeEvent(self, ev): settings.setValue("geometry", self.saveGeometry()) settings.setValue("state", self.saveState()) @@ -49,6 +77,36 @@ class MainWindow(QtGui.QMainWindow): def setWindowTitle(self, t): super(QtGui.QMainWindow, self).setWindowTitle("Feed the Monkey" + t) + 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.tinyTinyRSS.logOut() + self.tinyTinyRSS = None + self.put("session_id", None) + self.put("server_url", None) + self.authenticate() + class Content(QtGui.QWidget): @@ -223,8 +281,11 @@ class Content(QtGui.QWidget): class TinyTinyRSS: def __init__(self, app, server_url, session_id): self.app = app - self.server_url = server_url - self.session_id = session_id + if server_url and session_id: + self.server_url = server_url + self.session_id = session_id + else: + self.app.authenticate() def doOperation(self, operation, options=None): url = self.server_url + "/api/" @@ -253,6 +314,64 @@ class TinyTinyRSS: t = Thread(target=l) t.start() + def logOut(self): + self.doOperation("logout") + + @classmethod + def login(self, server_url, user, password): + url = server_url + "/api/" + options = {"op": "login", "user": user, "password": password} + 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 + + body = json.loads(body)["content"] + + if body.has_key("error"): + msgBox = QtGui.QMessageBox() + msgBox.setText(body["error"]) + msgBox.exec_() + return None + + return body["session_id"] + + +class Login(QtGui.QDialog): + def __init__(self): + QtGui.QDialog.__init__(self) + self.setWindowIcon(QtGui.QIcon("feedmonkey.png")) + self.setWindowTitle("Feed the Monkey - Login") + + self.label = QtGui.QLabel(self) + self.label.setText("Please specify a server url, a username and a password.") + + self.textServerUrl = QtGui.QLineEdit(self) + self.textServerUrl.setPlaceholderText("http://example.com/ttrss/") + self.textServerUrl.setText("http://") + + self.textName = QtGui.QLineEdit(self) + self.textName.setPlaceholderText("username") + + self.textPass = QtGui.QLineEdit(self) + self.textPass.setEchoMode(QtGui.QLineEdit.Password); + self.textPass.setPlaceholderText("password") + + self.buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok) + self.buttons.accepted.connect(self.accept) + + layout = QtGui.QVBoxLayout(self) + layout.addWidget(self.label) + layout.addWidget(self.textServerUrl) + layout.addWidget(self.textName) + layout.addWidget(self.textPass) + layout.addWidget(self.buttons) + if __name__ == "__main__": app = QtGui.QApplication(sys.argv) diff --git a/setup.py b/setup.py index 9dffe9c..2eed3ff 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( url = "http://jabs.nu/feedthemonkey", license = "BSD license", packages = ['feedthemonkey'], - scripts = ["feedthemonkey"], + scripts = ["feedthemonkey.py"], data_files=[ ('/usr/share/applications', ["feedthemonkey.desktop"]), ('/usr/share/pixmaps', ["feedthemonkey.xpm"])