Skip to content

Commit

Permalink
add online reading feature
Browse files Browse the repository at this point in the history
  • Loading branch information
cdhigh committed Jun 1, 2024
1 parent 980eb26 commit d7169e7
Show file tree
Hide file tree
Showing 43 changed files with 2,717 additions and 745 deletions.
1 change: 1 addition & 0 deletions application/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def BeforeRequest():
g.version = appVer
g.now = datetime.datetime.utcnow
g.allowSignup = (app.config['ALLOW_SIGNUP'] == 'yes')
g.allowReader = app.config['EBOOK_SAVE_DIR']

connect_database()

Expand Down
9 changes: 6 additions & 3 deletions application/back_end/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ class KeUser(MyBaseModel): # kindleEar User
expires = DateTimeField(null=True) #超过了此日期后账号自动停止推送
created_time = DateTimeField(default=datetime.datetime.utcnow)

#email,sender,kindle_email,secret_key,enable_send,timezone,inbound_email,keep_in_email_days
#email,sender,kindle_email,secret_key,enable_send,timezone,inbound_email,
#keep_in_email_days,delivery_mode,webshelf_days
#sender: 可能等于自己的email,也可能是管理员的email
#delivery_mode: 推送模式:['email' | 'local' | 'email,local']
base_config = JSONField(default=JSONField.dict_default)

#device,type,title,title_fmt,author_fmt,mode,time_fmt,oldest_article,language
#device,type,title,title_fmt,author_fmt,mode,time_fmt,oldest_article,language,rm_links,
#rm_links: 去掉文本或图片上的超链接{'' | 'image' | 'text' | 'all'}
book_config = JSONField(default=JSONField.dict_default)

Expand All @@ -40,7 +42,8 @@ def cfg(self, item, default=None):
value = self.base_config.get(item, default)
if value is None:
return {'email': '', 'kindle_email': '', 'secret_key': '', 'timezone': 0,
'inbound_email': 'save,forward', 'keep_in_email_days': 1}.get(item, value)
'inbound_email': 'save,forward', 'keep_in_email_days': 1,
'delivery_mode': 'email,local', 'webshelf_days': 7}.get(item, value)
else:
return value
def set_cfg(self, item, value):
Expand Down
7 changes: 3 additions & 4 deletions application/back_end/send_mail_adpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
#https://cloud.google.com/appengine/docs/standard/python3/reference/services/bundled/google/appengine/api/mail
#https://cloud.google.com/appengine/docs/standard/python3/services/mail
import os, datetime, zipfile, base64
from ..utils import ke_decrypt, str_to_bool
from ..utils import str_to_bool, sanitize_filename
from ..base_handler import save_delivery_log
from .db_models import KeUser

#google.appengine will apply patch for os.env module
hideMailLocal = str_to_bool(os.getenv('HIDE_MAIL_TO_LOCAL'))
Expand Down Expand Up @@ -205,15 +204,15 @@ def save_mail_to_local(dest_dir, subject, body, attachments=None, html=None, **k
if not os.path.exists(mailDir):
os.makedirs(mailDir)

subject = subject.replace(':', '_').replace('/', '_').replace('\\', '_').replace('?', '_').replace('*', '_')
now = str(datetime.datetime.now().strftime('%H-%M-%S'))
if len(body) < 100 and not html and len(attachments) == 1:
filename, content = attachments[0]
b, ext = os.path.splitext(filename)
mailFilename = os.path.join(mailDir, f'{b}_{now}{ext}')
mailFilename = os.path.join(mailDir, f'{sanitize_filename(b)}_{now}{ext}')
with open(mailFilename, 'wb') as f:
f.write(content)
else:
subject = sanitize_filename(subject)
mailFilename = os.path.join(mailDir, f'{subject}_{now}.zip')
mailFile = zipfile.ZipFile(mailFilename, 'w')
mailFile.writestr('textbody.txt', body)
Expand Down
4 changes: 2 additions & 2 deletions application/lib/build_ebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def clearPrevDownloads(): #退出时清理临时文件
pass

for idx, url in enumerate(urls[:]):
if not sysTmpDir or not isinstance(url, str):
if not sysTmpDir or not isinstance(url, str): #如果没有使用临时目录则无法提取下载
urls[idx] = (title, url) if isinstance(url, str) else url
continue

Expand All @@ -63,7 +63,7 @@ def clearPrevDownloads(): #退出时清理临时文件
urls[idx] = (title, url)
default_log.warning(f'Prev download html failed: {url}: {e}')
else: #提取标题
match = re.search(r'<title>(.*?)</title>', resp.text, re.I|re.M|re.S)
match = re.search(r'<title[^>]*>(.*?)</title>', resp.text, re.I|re.M|re.S)
uTitle = match.group(1).strip() if match else title
urls[idx] = (uTitle, 'file://' + pt.name)

Expand Down
29 changes: 28 additions & 1 deletion application/lib/calibre/ebooks/conversion/plumber.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from polyglot.builtins import string_or_bytes

from filesystem_dict import FsDictStub
from application.utils import sanitize_filename, get_directory_size
from application.base_handler import save_delivery_log

DEBUG_README=b'''
This debug folder contains snapshots of the e-book as it passes through the
Expand Down Expand Up @@ -88,7 +90,6 @@ class Plumber:

#input_: 输入,编译好的recipes列表或包含html和相关图像的一个字典
#output: 输出文件绝对路径名,也可能是一个BytesIO
#user: KeUser 实例
def __init__(self, input_, output, input_fmt, output_fmt=None, abort_after_input_dump=False):
self.input_ = input_
self.output = output
Expand Down Expand Up @@ -552,6 +553,9 @@ def run(self):
self.dump_oeb(self.oeb, out_dir)
self.log.info('Processed HTML written to:{}'.format(out_dir))

#如果需要在线阅读,则需要保存到输出文件夹
self.save_oeb_if_need(self.oeb)

self.log.info('Creating %s...'%self.output_plugin.name)

#创建输出临时文件缓存
Expand All @@ -571,6 +575,29 @@ def run(self):
if not isinstance(self.output, io.BytesIO):
run_plugins_on_postprocess(self.output, self.output_fmt)

#如果需要在线阅读,则需要保存到输出文件夹
def save_oeb_if_need(self, oeb):
user = self.opts.user #type:ignore
oebDir = os.environ.get('EBOOK_SAVE_DIR')
if not (oebDir and ('local' in user.cfg('delivery_mode'))):
return

title = sanitize_filename(oeb.metadata.title[0].value or 'Untitled')
bookDir = _bookDir = os.path.join(oebDir, user.name, user.local_time('%Y-%m-%d'), title)
cnt = 0
while os.path.exists(bookDir): #一天可以保存多个同名推送
cnt += 1
bookDir = f'{_bookDir} [{cnt}]'
try:
os.makedirs(bookDir)
except Exception as e:
self.log.warning(f'Failed to save eBook due to dir creation error: {bookDir}: {e}')
return

self.dump_oeb(self.oeb, bookDir)
size = get_directory_size(bookDir)
save_delivery_log(user, title, size, status='ok', to=oebDir)

regex_wizard_callback = None
def set_regex_wizard_callback(f):
global regex_wizard_callback
Expand Down
2 changes: 1 addition & 1 deletion application/lib/calibre/web/feeds/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ def summaryOk(summary):

#获取原本的title
if not title:
match = re.search(r'<title>(.*?)</title>', html, re.I|re.M|re.S)
match = re.search(r'<title[^>]*>(.*?)</title>', html, re.I|re.M|re.S)
title = match.group(1).strip() if match else 'Untitled'

return self.update_or_add_title(summary, title)
Expand Down
2 changes: 1 addition & 1 deletion application/lib/html_json_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def html_json_extract(html: str, language: str, url: str) -> str:
for item in filtered(finalList, language)])

#获取原本的title
match = re.search(r'<title>(.*?)</title>', html, re.I|re.M|re.S)
match = re.search(r'<title[^>]*>(.*?)</title>', html, re.I|re.M|re.S)
title = match.group(1).strip() if match else ''
return f"""<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>{title}</title></head><body>{finalTxt}
Expand Down
2 changes: 1 addition & 1 deletion application/lib/justext_extract/debug/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ div#output_wrapper {
}

div.paragraph_details {
width: 240px;
width: 400px;
float: right;
display: none;
}
Expand Down
1 change: 0 additions & 1 deletion application/lib/recipe_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ def GenerateRecipeSource(title, feeds, user, isfulltext=False, language=None, ma
cover_url=None, base='BasicNewsRecipe'):
className = f'UserRecipe{int(time.time())}'
title = py3_repr(str(title).strip() or className)
print(title)
indent = ' ' * 8
feedTitles = []
feedsStr = []
Expand Down
3 changes: 2 additions & 1 deletion application/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from flask import Blueprint, render_template, send_from_directory, current_app
from .view import (login, admin, adv, deliver, library, library_offical, logs, setting, share,
subscribe, inbound_email, translator, extension)
subscribe, inbound_email, translator, extension, reader)
from .work import worker, url2book

bpHome = Blueprint('bpHome', __name__)
Expand Down Expand Up @@ -45,6 +45,7 @@ def register_routes(app):
app.register_blueprint(subscribe.bpSubscribe)
app.register_blueprint(translator.bpTranslator)
app.register_blueprint(extension.bpExtension)
app.register_blueprint(reader.bpReader)
app.register_blueprint(worker.bpWorker)
app.register_blueprint(url2book.bpUrl2Book)
app.register_blueprint(library_offical.bpLibraryOffical)
Expand Down
32 changes: 27 additions & 5 deletions application/static/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,34 @@ button {
}

.logo {
padding: .5em;
font-family: Palatino, STSong, SimSun, serif;
margin: 10px;
padding: 3px 20px;
font-family: "Oleo Script", Palatino, STSong, SimSun, system-ui, serif;
font-size: 1.5em;
text-transform: none;
font-weight: bold;
color: #222;
font-weight: bolder;
color: #ff5733;
border-radius: 50%;
border: 1px solid #ffddc1;
background-color: #ffddc1;
box-shadow: 2px 2px 5px #ff5733;
text-shadow: 0px 0px 1px rgba(0, 0, 0, 0.5);
}

.reader-link {
font-family: "Oleo Script", Palatino, STSong, SimSun, system-ui, serif;
font-size: 0.8em;
font-weight: normal;
padding: 3px;
margin-left: -30px;
margin-bottom: 15px;
border: 1px solid #c1e1ff;
text-transform: none;
background-color: #c1e1ff;
color: #3375ff;
padding: 5px 10px;
border-radius: 50%;
box-shadow: 0px 0px 3px #0078e7;
}

.home-menu {
Expand All @@ -123,7 +145,7 @@ button {

@media only screen and (min-width: 480px) {
.app-menu .pure-menu-list {
display: block!important;
display: block !important;
}
}

Expand Down
6 changes: 3 additions & 3 deletions application/static/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ function AskForSubscriptionInfo(id, account) {
//用户点击了删除自定义RSS按钮,如果按住ctrl键再点击删除,则不需要确认
function ShowDeleteCustomRssDialog(event, rssid, title, url, isfulltext) {
if (!(event.ctrlKey || event.metaKey)) {
let msg = i18n.areYouSureDelete.format(title);
let msg = i18n.areYouSureDelete + '\n' + title;
if (!('show' in HTMLDialogElement.prototype)) { //校验兼容性
if (confirm(msg)) {
DeleteCustomRss(rssid, title, url, isfulltext, false);
Expand Down Expand Up @@ -919,7 +919,7 @@ function OpenUploadRecipeDialog() {
//删除一个已经上传的Recipe
function DeleteUploadRecipe(id, title) {
let force = (event.ctrlKey || event.metaKey);
if (!force && !confirm(i18n.areYouSureDelete.format(title))) {
if (!force && !confirm(i18n.areYouSureDelete + '\n' + title)) {
return;
}

Expand Down Expand Up @@ -1194,7 +1194,7 @@ var AjaxFileUpload = {
///[start] admin.html
//删除一个账号
function DeleteAccount(name) {
if (!confirm(i18n.areYouSureDelete.format(name))) {
if (!confirm(i18n.areYouSureDelete + '\n' + name)) {
return;
}
MakeAjaxRequest("/account/delete", "POST", {name: name}, function (resp) {
Expand Down
59 changes: 59 additions & 0 deletions application/static/reader-inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//文档加载完成
document.addEventListener('DOMContentLoaded', function() {
var clientHeight = window.innerHeight || document.documentElement.clientHeight;
var scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
var data = {type: 'iframeLoaded', clientHeight: clientHeight, scrollHeight: scrollHeight};
window.parent.postMessage(data, "*");
document.addEventListener('click', function (event) {
var eventDict = clickEventToDict(event);
var data = {type: 'click', event: eventDict, href: '', clientHeight: clientHeight, scrollHeight: scrollHeight};
window.parent.postMessage(data, "*");
});
setupLinkListener(clientHeight, scrollHeight);
});

//将点击事件转换为字典,用于不同frame之间的消息传递
function clickEventToDict(event) {
return {
type: event.type,
//target: event.target.tagName,
//currentTarget: event.currentTarget.tagName,
//eventPhase: event.eventPhase,
//bubbles: event.bubbles,
//cancelable: event.cancelable,
//defaultPrevented: event.defaultPrevented,
//isTrusted: event.isTrusted,
//timestamp: event.timeStamp,
altKey: event.altKey,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
shiftKey: event.shiftKey,
button: event.button,
buttons: event.buttons,
clientX: event.clientX,
clientY: event.clientY,
pageX: event.pageX,
pageY: event.pageY,
screenX: event.screenX,
screenY: event.screenY,
offsetX: event.offsetX,
offsetY: event.offsetY,
movementX: event.movementX,
movementY: event.movementY
};
}

//点击iframe内部的链接后给父容器发送消息,让父容器决定是否打开链接并处理前进后退逻辑
function setupLinkListener(clientHeight, scrollHeight) {
var links = document.querySelectorAll('a');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function(event) {
event.stopPropagation();
event.preventDefault();
var href = event.target.getAttribute('href');
var eventDict = clickEventToDict(event);
var data = {type: 'click', href: href, event: eventDict, clientHeight: clientHeight, scrollHeight: scrollHeight};
window.parent.postMessage(data, '*');
});
}
}
Loading

0 comments on commit d7169e7

Please sign in to comment.