added necessary files
This commit is contained in:
parent
77bccc067c
commit
477666f6f9
6 changed files with 10086 additions and 1 deletions
|
@ -1 +1 @@
|
|||
ttrssl is a desktop client for TinyTinyRSS
|
||||
Feed the Monkey is a desktop client for TinyTinyRSS.
|
BIN
feedmonkey.png
Normal file
BIN
feedmonkey.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 189 KiB |
9793
feedmonkey.xpm
Normal file
9793
feedmonkey.xpm
Normal file
File diff suppressed because it is too large
Load diff
261
feedthemonkey
Executable file
261
feedthemonkey
Executable file
|
@ -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 """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ttrssl</title>
|
||||
<script type="text/javascript">
|
||||
function $(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function setArticle(article) {
|
||||
window.scrollBy(0,0);
|
||||
|
||||
if(article) {
|
||||
$("date").innerHTML = (new Date(parseInt(article.updated, 10) * 1000)).toLocaleString();
|
||||
$("title").innerHTML = article.title;
|
||||
$("title").href = article.link;
|
||||
$("title").title = article.link;
|
||||
$("feed_title").innerHTML = article.feed_title;
|
||||
$("author").innerHTML = "";
|
||||
if(article.author && article.author.length > 0)
|
||||
$("author").innerHTML = "– " + article.author
|
||||
$("article").innerHTML = article.content;
|
||||
} else {
|
||||
$("date").innerHTML = "";
|
||||
$("title").innerHTML = "";
|
||||
$("title").href = "";
|
||||
$("title").title = "";
|
||||
$("feed_title").innerHTML = "";
|
||||
$("author").innerHTML = "";
|
||||
$("article").innerHTML = "No unread articles found to display.";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: "Ubuntu", "Lucida Grande","Tahoma";
|
||||
padding: 1em 2em 1em 2em;
|
||||
}
|
||||
h1 {
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
header {
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 1px solid #aaa;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
header p {
|
||||
color: #aaa;
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
a {
|
||||
color: #772953;
|
||||
text-decoration: none;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
article {
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<p><span id="feed_title"></span> <span id="author"></span></p>
|
||||
<h1><a id="title" href=""></a></h1>
|
||||
<p><timedate id="date"></timedate></p>
|
||||
</header>
|
||||
<article id="article"></article>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
|
||||
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_())
|
12
feedthemonkey.desktop
Normal file
12
feedthemonkey.desktop
Normal file
|
@ -0,0 +1,12 @@
|
|||
[Desktop Entry]
|
||||
Version=0.1.0
|
||||
Comment=A desktop client for the TinyTinyRSS feed reader.
|
||||
Exec=/usr/bin/feedthemonkey
|
||||
GenericName=Feed Reader
|
||||
Icon=feedthemonkey
|
||||
Name=Feed the Monkey
|
||||
NoDisplay=false
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Network;Qt
|
19
setup.py
Normal file
19
setup.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env python2
|
||||
|
||||
import os
|
||||
from distutils.core import setup
|
||||
|
||||
setup(
|
||||
name = "feedthemonkey",
|
||||
version = "0.1.0",
|
||||
author = "Jeena Paradies",
|
||||
author_email = "spam@jeenaparadies.net",
|
||||
url = "http://jabs.nu/feedthemonkey",
|
||||
license = "BSD license",
|
||||
packages = ['feedthemonkey'],
|
||||
scripts = ["feedthemonkey"],
|
||||
data_files=[
|
||||
('/usr/share/applications', ["feedthemonkey.desktop"]),
|
||||
('/usr/share/pixmaps', ["feedthemonkey.xpm"])
|
||||
]
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue