├── .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 |  8 | 9 | ### Ubutnu 17.10 Gnome 10 |  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'
t |