Skip to content

Commit

Permalink
1.26
Browse files Browse the repository at this point in the history
添加 “共享库” 功能
  • Loading branch information
cdhigh committed Sep 1, 2019
1 parent 5dc24c9 commit c809f3a
Show file tree
Hide file tree
Showing 49 changed files with 1,358 additions and 461 deletions.
6 changes: 0 additions & 6 deletions .gitignore
@@ -1,10 +1,4 @@
*.pyc
.DS_Store
Correio.py
oGlobo.py
mh_correio.gif
mh_globo.gif
cv_correio.jpg
cv_globo.jpg

.idea/
25 changes: 14 additions & 11 deletions apps/BaseHandler.py
Expand Up @@ -26,27 +26,30 @@
#import main

#URL请求处理类的基类,实现一些共同的工具函数
class BaseHandler:
def __init__(self):
if not main.session.get('lang'):
main.session.lang = self.browerlang()
set_lang(main.session.lang)
class BaseHandler(object):
def __init__(self, setLang=True):
if setLang:
if not main.session.get('lang'):
main.session.lang = self.browerlang()
set_lang(main.session.lang)

@classmethod
def logined(self):
return True if main.session.get('login') == 1 else False

@classmethod
def login_required(self, username=None):
def login_required(self, username=None, forAjax=False):
if (main.session.get('login') != 1) or (username and username != main.session.get('username')):
raise web.seeother(r'/login')
raise web.seeother(r'/needloginforajax' if forAjax else r'/login')

@classmethod
def getcurrentuser(self):
self.login_required()
u = KeUser.all().filter("name = ", main.session.username).get()
def getcurrentuser(self, forAjax=False):
if main.session.get('login') != 1 or not main.session.get('username'):
raise web.seeother(r'/needloginforajax' if forAjax else r'/login')

u = KeUser.all().filter("name = ", main.session.get('username', '')).get()
if not u:
raise web.seeother(r'/login')
raise web.seeother(r'/needloginforajax' if forAjax else r'/login')
return u

@classmethod
Expand Down
8 changes: 4 additions & 4 deletions apps/View/Adv.py
Expand Up @@ -67,7 +67,7 @@ class AdvArchive(BaseHandler):
def GET(self):
user = self.getcurrentuser()

return self.render('advarchive.html', "Share", current='advsetting', user=user, advcurr='share',
return self.render('advarchive.html', "Archive", current='advsetting', user=user, advcurr='archive',
savetoevernote=SAVE_TO_EVERNOTE, savetowiz=SAVE_TO_WIZ, savetopocket=SAVE_TO_POCKET,
savetoinstapaper=SAVE_TO_INSTAPAPER, ke_decrypt=ke_decrypt,
shareonxweibo=SHARE_ON_XWEIBO, shareontweibo=SHARE_ON_TWEIBO, shareonfacebook=SHARE_ON_FACEBOOK,
Expand Down Expand Up @@ -271,9 +271,9 @@ class AdvUploadCoverImageAjax(BaseHandler):
MAX_IMAGE_PIXEL = 1024
def POST(self):
ret = 'ok'
user = self.getcurrentuser(forAjax=True)
try:
x = web.input(coverfile={})
user = self.getcurrentuser()
file_ = x['coverfile'].file
if user and file_:
#将图像转换为JPEG格式,同时限制分辨率不超过1024
Expand All @@ -297,9 +297,9 @@ class AdvDeleteCoverImageAjax(BaseHandler):
__url__ = "/advdeletecoverimageajax"
def POST(self):
ret = {'status': 'ok'}
user = self.getcurrentuser(forAjax=True)
try:
confirmKey = web.input().get('action')
user = self.getcurrentuser()
if user and confirmKey == 'delete':
user.cover = None
user.put()
Expand Down Expand Up @@ -370,7 +370,7 @@ def POST(self, verType):
respDict['status'] = _('Request type[%s] unsupported') % verType
return json.dumps(respDict)

user = self.getcurrentuser()
user = self.getcurrentuser(forAjax=True)

username = web.input().get('username', '')
password = web.input().get('password', '')
Expand Down
293 changes: 293 additions & 0 deletions apps/View/Library.py
@@ -0,0 +1,293 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#A GAE web application to aggregate rss and send it to your kindle.
#Visit https://github.com/cdhigh/KindleEar for the latest version
import datetime, urlparse
try:
import json
except ImportError:
import simplejson as json

import web
from apps.BaseHandler import BaseHandler
from apps.dbModels import *
from lib.urlopener import URLOpener

#网友共享的订阅源数据
class SharedLibrary(BaseHandler):
__url__ = "/library"

def GET(self):
user = self.getcurrentuser()

#连接分享服务器获取数据
shared_data = []
tips = ''
opener = URLOpener()
url = urlparse.urljoin('http://kindleear.appspot.com/', SharedLibrarykindleearAppspotCom.__url__)
result = opener.open(url + '?key=kindleear.lucky!')
if result.status_code == 200 and result.content:
shared_data = json.loads(result.content)
else:
tips = _('Cannot fetch data from kindleear.appspot.com, status: ') + URLOpener.CodeMap(result.status_code)

return self.render('sharedlibrary.html', "Shared",
current='shared', user=user, shared_data=shared_data, tips=tips)

#分享了一个订阅源
def POST(self):
user = self.getcurrentuser(forAjax=True)
web.header('Content-Type', 'application/json')
webInput = web.input()
category = webInput.get('category', '')
title = webInput.get('title')
feedUrl = webInput.get("url")
isfulltext = bool(webInput.get('isfulltext', '').lower() == 'true')
creator = webInput.get('creator', '')

if not title or not feedUrl:
return json.dumps({'status': _("Title or Url is empty!")})

opener = URLOpener()
srvUrl = urlparse.urljoin('http://kindleear.appspot.com/', SharedLibrarykindleearAppspotCom.__url__)
data = {'category': category, 'title': title, 'url': feedUrl, 'creator': creator,
'isfulltext': 'true' if isfulltext else 'false', 'key': 'kindleear.lucky!'}
result = opener.open(srvUrl, data)
if result.status_code == 200 and result.content:
return result.content
else:
return json.dumps({'status': _('Cannot submit data to kindleear.appspot.com, status: %s' % URLOpener.CodeMap(result.status_code))})

class SharedLibraryMgr(BaseHandler):
__url__ = "/library/mgr/(.*)"

def POST(self, mgrType):
user = self.getcurrentuser(forAjax=True)
if mgrType == 'reportinvalid': #报告一个源失效了
web.header('Content-Type', 'application/json')
title = web.input().get('title', '')
feedUrl = web.input().get('url', '')

opener = URLOpener()
path = SharedLibraryMgrkindleearAppspotCom.__url__.split('/')
path[-1] = mgrType
srvUrl = urlparse.urljoin('http://kindleear.appspot.com/', '/'.join(path))
data = {'title': title, 'url': feedUrl, 'key': 'kindleear.lucky!'}
result = opener.open(srvUrl, data)
if result.status_code == 200 and result.content:
return result.content
else:
return json.dumps({'status': _('Cannot fetch data from kindleear.appspot.com, status: ') + URLOpener.CodeMap(result.status_code)})
else:
return json.dumps({'status': 'unknown command: %s' % mgrType})

#共享的订阅源的分类信息
class SharedLibraryCategory(BaseHandler):
__url__ = "/library/category"

def GET(self):
user = self.getcurrentuser(forAjax=True)
web.header('Content-Type', 'application/json')

#连接分享服务器获取数据
respDict = {'status':'ok', 'categories':[]}

opener = URLOpener()
url = urlparse.urljoin('http://kindleear.appspot.com/', SharedLibraryCategorykindleearAppspotCom.__url__)
result = opener.open(url + '?key=kindleear.lucky!')

if result.status_code == 200 and result.content:
respDict['categories'] = json.loads(result.content)
else:
respDict['status'] = _('Cannot fetch data from kindleear.appspot.com, status: ') + URLOpener.CodeMap(result.status_code)

return json.dumps(respDict)

#===========================================================================================================
# 以下函数仅为 kindleear.appspot.com 使用
#===========================================================================================================

#网友共享的订阅源数据(仅用于kindleear.appspot.com"官方"共享服务器)
class SharedLibrarykindleearAppspotCom(BaseHandler):
__url__ = "/kindleearappspotlibrary"

def __init__(self):
super(SharedLibrarykindleearAppspotCom, self).__init__(setLang=False)

def GET(self):
key = web.input().get('key', '') #避免爬虫消耗资源
if key != 'kindleear.lucky!':
return ''

web.header('Content-Type', 'application/json')

#本来想在服务器端分页的,但是好像CPU/数据库存取资源比带宽资源更紧张,所以干脆一次性提供给客户端,由客户端分页和分类
#如果后续发现这样不理想,也可以考虑修改为服务器端分页
qry = SharedRss.all().order('-subscribed').order('-created_time').fetch(limit=10000)
shared_data = []
for d in qry:
shared_data.append({'title':d.title, 'url':d.url, 'isfulltext':d.isfulltext, 'category':d.category})
return json.dumps(shared_data)

#网友分享了一个订阅链接
def POST(self):
web.header('Content-Type', 'application/json')
webInput = web.input()
key = webInput.get('key')
if key != 'kindleear.lucky!': #避免爬虫消耗资源
return ''

category = webInput.get('category', '')
title = webInput.get('title')
url = webInput.get("url")
isfulltext = bool(webInput.get('isfulltext', '').lower() == 'true')
creator = webInput.get('creator', '')

respDict = {'status':'ok', 'category':category, 'title':title, 'url':url, 'isfulltext':isfulltext, 'creator':creator}

if not title or not url:
respDict['status'] = _("Title or Url is empty!")
return json.dumps(respDict)

#将贡献者的网址加密
if creator:
parts = urlparse.urlparse(creator)
path = parts.path if parts.path else parts.netloc
if '.' in path:
pathArray = path.split('.')
if len(pathArray[0]) > 4:
pathArray[0] = pathArray[0][:2] + '**' + pathArray[0][-1]
else:
pathArray[0] = pathArray[0][0] + '**'
pathArray[1] = pathArray[1][0] + '**'
creator = '.'.join(pathArray)
elif len(path) > 4:
creator = path[:2] + '**' + path[-1]
else:
creator = path[0] + '**'

#判断是否存在,如果存在,则更新分类或必要的信息,同时还是返回成功
now = datetime.datetime.utcnow()
dbItem = SharedRss.all().filter('url = ', url).get()
prevCategory = ''
if dbItem:
dbItem.isfulltext = isfulltext
dbItem.invalid_report_days = 0
if category:
prevCategory = dbItem.category
dbItem.category = category
dbItem.put()
else:
SharedRss(title=title, url=url, category=category, isfulltext=isfulltext, creator=creator,
subscribed=1, created_time=now, invalid_report_days=0, last_invalid_report_time=now).put()

#更新分类信息,用于缓存
if category:
cItem = SharedRssCategory.all().filter('name = ', category).get()
if cItem:
cItem.last_updated = now
cItem.put()
else:
SharedRssCategory(name=category, last_updated=now).put()

if prevCategory:
catItem = SharedRss.all().filter('category = ', prevCategory).get()
if not catItem: #没有其他订阅源使用此分类了
sItem = SharedRssCategory.all().filter('name = ', prevCategory).get()
if sItem:
sItem.delete()

return json.dumps(respDict)

class SharedLibraryMgrkindleearAppspotCom(BaseHandler):
__url__ = "/kindleearappspotlibrary/mgr/(.*)"

def __init__(self):
super(SharedLibraryMgrkindleearAppspotCom, self).__init__(setLang=False)

def POST(self, mgrType):
if mgrType == 'reportinvalid': #报告一个源失效了
title = web.input().get('title')
url = web.input().get('url')
respDict = {'status':'ok', 'title':title, 'url':url}

if not url:
respDict['status'] = _("Url is empty!")
return json.dumps(respDict)

if not url.lower().startswith('http'):
url = 'http://' + url
respDict['url'] = url

#判断是否存在
dbItem = SharedRss.all().filter('url = ', url).get()
if not dbItem:
respDict['status'] = _("URL not found in database!")
return json.dumps(respDict)

#希望能做到“免维护”,在一定数量的失效报告之后,自动删除对应的源,这其实是要求不要有人恶作剧
now = datetime.datetime.utcnow()
delta = abs(now - dbItem.last_invalid_report_time)
deltaDays = delta.days

if deltaDays > 180:
dbItem.invalid_report_days = 1
elif delta.days >= 1:
dbItem.invalid_report_days += 1

if dbItem.invalid_report_days > 5:
category = dbItem.category
dbItem.delete()

#更新分类信息
allCategories = SharedRss.categories()
if category not in allCategories:
cItem = SharedRssCategory.all().filter('name = ', category).get()
if cItem:
cItem.delete()
else:
dbItem.last_invalid_report_time = now
dbItem.put()

return json.dumps(respDict)
elif mgrType == 'subscribedfromshared': #有用户订阅了一个共享库里面的链接
title = web.input().get('title')
url = web.input().get('url')
respDict = {'status':'ok', 'title':title, 'url':url}

if not url:
respDict['status'] = _("Url is empty!")
return json.dumps(respDict)

if not url.lower().startswith('http'):
url = 'http://' + url
respDict['url'] = url

#判断是否存在
dbItem = SharedRss.all().filter('url = ', url).get()
if dbItem:
dbItem.subscribed += 1
dbItem.put()
else:
respDict['status'] = _("URL not found in database!")

return json.dumps(respDict)

else:
return json.dumps({'status': 'unknown command: %s' % mgrType})

#网友共享的订阅源数据分类信息(仅用于kindleear.appspot.com"官方"共享服务器)
class SharedLibraryCategorykindleearAppspotCom(BaseHandler):
__url__ = "/kindleearappspotlibrarycategory"

def __init__(self):
super(SharedLibraryCategorykindleearAppspotCom, self).__init__(setLang=False)

def GET(self):
key = web.input().get('key', '') #避免爬虫消耗IO资源
if key != 'kindleear.lucky!':
return ''

web.header('Content-Type', 'application/json')
return json.dumps([item.name for item in SharedRssCategory.all().order('-last_updated')])

4 comments on commit c809f3a

@bookfere
Copy link
Contributor

@bookfere bookfere commented on c809f3a Sep 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这次更新后,因为删除了一些 Class 的原因,导致按钮的样式改变了,这是有意的修改吗?如果不是的话我提交一个新 PR,否则就不提交了。

更改之前:
home-p

更改之后:
home-a

更改之前:
my-p

更改之后:
my-a

@cdhigh
Copy link
Owner Author

@cdhigh cdhigh commented on c809f3a Sep 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我没有学过CSS和前端(做KindleEar时随便查查有什么属性之类的,然后凑成差不多的界面,有很多自己想做的排版怎么都做不出来,囧),也没有什么设计和所谓的审美基础。
之所以修改一些样式是之前我感觉"太圆"了,也不是什么严谨的判断。
不过感谢你完善了样式设计,你的审美应该比我好,如果你认为圆一点的设计更适合KindleEar,请提交一个issue,我合并。

@bookfere
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我把常操作的地方(如登录框、添加RSS输入框、添加订阅等按钮)设置成圆角,主要意图是使其和其它相对单调重复的元素(比如横平竖直的列表等)产生差异,这样视觉上会显得活泼一点,也比较容易让用户操作时快速识别这些特异化的元素。

在看了你的回复后,我又重新审视了一下这两种风格,反复对比了一下圆角和方形的视觉效果,也参考了其他人的意见,还是认为圆角的效果好一些。所以不介意的话,我重新提交一个 PR,恢复一下这几处样式。

@cdhigh
Copy link
Owner Author

@cdhigh cdhigh commented on c809f3a Sep 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以

Please sign in to comment.