├── .gitignore ├── LICENSE ├── README.md ├── app.py ├── appicon.png ├── db.py ├── db.sqlite ├── feed.ico ├── gui.py ├── routes.py ├── static ├── css │ ├── bootstrap.min.css │ └── styles.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── images │ ├── ajax-loader.gif │ └── icon-48.png └── js │ ├── bootstrap.js │ └── jquery-1.11.0.min.js └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | dist 4 | build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Saeed Moqadam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### PyFladesk Rss Reader 2 | 3 | this is a demo app made by [PyFladesk](https://github.com/smoqadam/PyFladesk). 4 | RSS links save in sqlite database and using AJAX to fetch RSS data. 5 | 6 | ### Windows 7 7 | ![capture](https://cloud.githubusercontent.com/assets/1223848/12180868/3a54e622-b594-11e5-96d5-acd33424d676.JPG) 8 | 9 | ### Ubutnu 17.10 Gnome 10 | ![pyfladeskrssreader](https://user-images.githubusercontent.com/1223848/36587379-4b15d3d4-189a-11e8-99c3-92c503af5009.png) 11 | 12 | for make an executable file with `pyinstaller` you can use the spec file includes in repository. 13 | 14 | ## Requirements 15 | - [PyFladesk 1.0](https://github.com/smoqadam/PyFladesk) 16 | - feedparser 17 | - sqlite3 18 | 19 | ## Contributing 20 | 21 | If you want to change styles or add some fearures feel free and do it, then send a PR. 22 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from gui import init_gui 3 | 4 | app = Flask(__name__) 5 | 6 | from routes import * 7 | 8 | if __name__ == '__main__': 9 | init_gui(app) -------------------------------------------------------------------------------- /appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/appicon.png -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 as sql 2 | 3 | 4 | def dict_factory(cursor, row): 5 | d = {} 6 | for idx, col in enumerate(cursor.description): 7 | d[col[0]] = row[idx] 8 | return d 9 | 10 | class DB: 11 | 12 | def __init__(self): 13 | self.con = sql.connect('db.sqlite') 14 | self.con.row_factory = dict_factory 15 | self.con.execute('CREATE TABLE IF NOT EXISTS "feeds" ("_id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE , "url" VARCHAR, "title" VARCHAR);') 16 | self.con.commit() 17 | self.cursor = self.con.cursor() 18 | 19 | 20 | def execute(self,sql,param=()): 21 | self.cursor.execute(sql,param) 22 | self.con.commit() 23 | self.close() 24 | 25 | def close(self): 26 | self.con.close() 27 | 28 | def insert(self,url,title): 29 | self.execute("INSERT INTO feeds (url,title) VALUES (?,?)",(url,title)) 30 | return self.cursor.lastrowid 31 | 32 | def delete(self,id): 33 | self.execute("DELETE FROM feeds WHERE _id = {}".format(id)) 34 | 35 | def selectall(self,where='1=1'): 36 | 37 | self.cursor.execute("SELECT * FROM feeds WHERE {}".format(where)) 38 | return self.cursor.fetchall() 39 | 40 | def selectone(self,where='1=1'): 41 | self.cursor.execute("SELECT * FROM feeds WHERE {}".format(where)) 42 | return self.cursor.fetchone() 43 | self.close() 44 | 45 | -------------------------------------------------------------------------------- /db.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/db.sqlite -------------------------------------------------------------------------------- /feed.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/feed.ico -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt5 import QtCore, QtWidgets, QtGui, QtWebEngineWidgets 3 | 4 | 5 | class ApplicationThread(QtCore.QThread): 6 | def __init__(self, application, port=5000): 7 | super(ApplicationThread, self).__init__() 8 | self.application = application 9 | self.port = port 10 | 11 | def __del__(self): 12 | self.wait() 13 | 14 | def run(self): 15 | self.application.run(port=self.port, threaded=True) 16 | 17 | 18 | class WebPage(QtWebEngineWidgets.QWebEnginePage): 19 | def __init__(self, root_url): 20 | super(WebPage, self).__init__() 21 | self.root_url = root_url 22 | 23 | def home(self): 24 | self.load(QtCore.QUrl(self.root_url)) 25 | 26 | def acceptNavigationRequest(self, url, kind, is_main_frame): 27 | """Open external links in browser and internal links in the webview""" 28 | ready_url = url.toEncoded().data().decode() 29 | is_clicked = kind == self.NavigationTypeLinkClicked 30 | if is_clicked and self.root_url not in ready_url: 31 | QtGui.QDesktopServices.openUrl(url) 32 | return False 33 | return super(WebPage, self).acceptNavigationRequest(url, kind, is_main_frame) 34 | 35 | 36 | def init_gui(application, port=5000, width=300, height=400, 37 | window_title="PyFladesk", icon="appicon.png", argv=None): 38 | if argv is None: 39 | argv = sys.argv 40 | 41 | # Application Level 42 | qtapp = QtWidgets.QApplication(argv) 43 | webapp = ApplicationThread(application, port) 44 | webapp.start() 45 | qtapp.aboutToQuit.connect(webapp.terminate) 46 | 47 | # Main Window Level 48 | window = QtWidgets.QMainWindow() 49 | window.resize(width, height) 50 | window.setWindowTitle(window_title) 51 | window.setWindowIcon(QtGui.QIcon(icon)) 52 | 53 | # WebView Level 54 | webView = QtWebEngineWidgets.QWebEngineView(window) 55 | window.setCentralWidget(webView) 56 | 57 | # WebPage Level 58 | page = WebPage('http://localhost:{}'.format(port)) 59 | page.home() 60 | webView.setPage(page) 61 | 62 | window.show() 63 | return qtapp.exec_() 64 | -------------------------------------------------------------------------------- /routes.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request 2 | from db import DB 3 | import feedparser 4 | import json 5 | import sys 6 | 7 | 8 | app = Flask(__name__) 9 | 10 | 11 | @app.route('/',methods=['GET', 'POST']) 12 | def index(): 13 | db = DB() 14 | feeds = db.selectall() 15 | return render_template('index.html',feeds=feeds) 16 | 17 | 18 | 19 | 20 | @app.route('/add',methods=['POST']) 21 | def add(): 22 | output = {"title":"",'id':'',"response":""} 23 | url = request.form['url'] 24 | feed = feedparser.parse(url) 25 | try: 26 | title = feed['feed']['title'] 27 | 28 | db = DB() 29 | output['id'] = db.insert(url,title) 30 | 31 | output['title'] = title 32 | output['response'] = 'ok' 33 | except IndexError : 34 | output['response'] = 'Wrong feed address' 35 | except KeyError : 36 | output['response'] = 'Wrong feed address' 37 | 38 | return json.dumps(output) 39 | 40 | 41 | 42 | @app.route('/fetch',methods=['POST']) 43 | def fetch(): 44 | try: 45 | url = request.form['url'] 46 | feed = feedparser.parse(url) 47 | output = {'response':'','result':''} 48 | body = '' 49 | for post in feed.entries: 50 | body += u'
' 51 | body += u'

{}

'.format(post.link,post.title) 52 | body += u'
{}
'.format(post.summary) 53 | body += u'
' 54 | output['response'] = 'ok' 55 | output['result'] = body.encode('utf8') 56 | output['result'] = output['result'].decode('utf8') 57 | return json.dumps(output, ensure_ascii=False) 58 | except: 59 | print( "Unexpected error:", sys.exc_info()[0]) 60 | raise 61 | 62 | 63 | 64 | 65 | @app.route('/delete',methods=['POST']) 66 | def delete(): 67 | id = request.form['id'] 68 | output = {'response':'','result':'','ID':id} 69 | # return json.dumps(output) 70 | db = DB() 71 | db.delete(id) 72 | output['response'] = 'ok' 73 | return json.dumps(output) 74 | -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - Simple Sidebar HTML Template (http://startbootstrap.com) 3 | * Code licensed under the Apache License v2.0. 4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0. 5 | */ 6 | 7 | /* Toggle Styles */ 8 | 9 | #wrapper { 10 | padding-left: 0; 11 | -webkit-transition: all 0.5s ease; 12 | -moz-transition: all 0.5s ease; 13 | -o-transition: all 0.5s ease; 14 | transition: all 0.5s ease; 15 | } 16 | 17 | #wrapper.toggled { 18 | padding-left: 250px; 19 | } 20 | 21 | #sidebar-wrapper { 22 | z-index: 1000; 23 | position: fixed; 24 | left: 250px; 25 | width: 0; 26 | height: 100%; 27 | margin-left: -250px; 28 | overflow-y: auto; 29 | background: #333; 30 | -webkit-transition: all 0.5s ease; 31 | -moz-transition: all 0.5s ease; 32 | -o-transition: all 0.5s ease; 33 | transition: all 0.5s ease; 34 | color:#eee; 35 | font-family: "Segoe UI" 36 | } 37 | 38 | #wrapper.toggled #sidebar-wrapper { 39 | width: 250px; 40 | } 41 | 42 | #page-content-wrapper { 43 | width: 100%; 44 | position: absolute; 45 | padding: 15px; 46 | } 47 | 48 | #wrapper.toggled #page-content-wrapper { 49 | position: absolute; 50 | margin-right: -250px; 51 | } 52 | 53 | /* Sidebar Styles */ 54 | .sidebar-nav { 55 | position: absolute; 56 | top: 0; 57 | width: 250px; 58 | margin: 0; 59 | padding: 0; 60 | list-style: none; 61 | 62 | } 63 | 64 | 65 | ul.site-lists{ 66 | list-style: none; 67 | padding:0; 68 | } 69 | 70 | ul.site-lists li{ 71 | overflow: hidden; 72 | text-overflow: ellipsis 73 | 74 | } 75 | ul.site-lists li a { 76 | text-decoration: none; 77 | color: #eee; 78 | white-space: nowrap; 79 | white-space: #f00; 80 | padding:5px; 81 | } 82 | 83 | ul.site-lists li a:hover { 84 | text-decoration: underline; 85 | } 86 | 87 | 88 | #sidebar-wrapper .row{ 89 | margin:10px 0; 90 | padding:10px 0; 91 | } 92 | 93 | #sidebar-wrapper .add-rss{ 94 | margin-top: 30px; 95 | } 96 | 97 | .post{ 98 | width:100%; 99 | border:1px solid #ccc; 100 | border-radius: 3px; 101 | margin:5px 0; 102 | padding:10px; 103 | } 104 | .post img{ 105 | max-width: 100% 106 | } 107 | 108 | .footer { 109 | height: 40px; 110 | position: fixed; 111 | bottom: 0px; 112 | width: 100%; 113 | background-color: #F5F5F5; 114 | line-height: 40px; 115 | padding-left: 10px; 116 | 117 | } 118 | 119 | .loading{ 120 | 121 | height: 20px; 122 | position: fixed; 123 | margin-left: 50%; 124 | top:0; 125 | padding-left:18px; 126 | font-weight: bold; 127 | color:#f92343; 128 | width:100%; 129 | background: url(../images/ajax-loader.gif) no-repeat left; 130 | display: none; 131 | /*background-color:#eee;*/ 132 | } 133 | 134 | #menu-toggle{ 135 | margin-top: -5px; 136 | } 137 | 138 | 139 | a.delete span{ 140 | color:#ff5100; 141 | font-size: 10px; 142 | } 143 | a.delete span:hover{ 144 | color:#ff1100; 145 | } 146 | @media(min-width:768px) { 147 | .footer{ 148 | padding-left: 260px; 149 | } 150 | 151 | 152 | #wrapper { 153 | padding-left: 250px; 154 | } 155 | 156 | #wrapper.toggled { 157 | padding-left: 0; 158 | } 159 | 160 | #sidebar-wrapper { 161 | width: 250px; 162 | } 163 | 164 | #wrapper.toggled #sidebar-wrapper { 165 | width: 0; 166 | } 167 | 168 | #page-content-wrapper { 169 | padding: 20px; 170 | position: relative; 171 | } 172 | 173 | #wrapper.toggled #page-content-wrapper { 174 | position: relative; 175 | margin-right: 0; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/static/images/ajax-loader.gif -------------------------------------------------------------------------------- /static/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smoqadam/PyFladesk-rss-reader/762503a74c8e886033d9d2fc543ff2b9816c800b/static/images/icon-48.png -------------------------------------------------------------------------------- /static/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$backdrop=this.isShown=null,this.scrollbarWidth=0,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.2.0",c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var c=this,d=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(d),this.isShown||d.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.$body.addClass("modal-open"),this.setScrollbar(),this.escape(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var d=a.support.transition&&c.$element.hasClass("fade");c.$element.parent().length||c.$element.appendTo(c.$body),c.$element.show().scrollTop(0),d&&c.$element[0].offsetWidth,c.$element.addClass("in").attr("aria-hidden",!1),c.enforceFocus();var e=a.Event("shown.bs.modal",{relatedTarget:b});d?c.$element.find(".modal-dialog").one("bsTransitionEnd",function(){c.$element.trigger("focus").trigger(e)}).emulateTransitionEnd(300):c.$element.trigger("focus").trigger(e)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.$body.removeClass("modal-open"),this.resetScrollbar(),this.escape(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(300):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keyup.dismiss.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;if(this.$backdrop=a('