5jrm~yn^yS2jO^oIo>){uT~P`2w74Ip6Xzmto|}dpa06<6MV-PY@zgL>P>A?-c4fP&u3WqyTw6Sb247T21mP#rH={G%D*NW0isQ34OogA=NZ(kuRyK%Me`LL
zM!X00HvEWH=mxU?nsDnte}L_%cpqxuL)Z%6LtUm%kW+CngZ!N+z#+twFd28C5;=}a
z{2Xe$I@H8BQRCi1)&F;p=U0Rd_HAm$p&A6ubR16H&Eol}6)r(dyasj4Yw#_+h)QGy
zUnyO_ji|e^4VAzVRDvgZP*B59P!nFW247kIZPeLD0s$C_j-v-pwyD$t7qn;l%Pou{B6vObU#W&3QTn@=An_zLNITw}CLR3O8qbA;p+S{Gj4G*K*{Tp*IdJpwuCD53!Kx^iA2^6%lOpL`sJ-}k*N1j`NsrWV~
z;d#`A-=i*Xs}cUs&pcG3<){gkqTZsln1j1f?dwoKZ`H2Dlr-_pa%TX;@haR4?JWOq1q*(66tF7nW&>3fGx2Io8U}T|M?yTby#8*6?iXk
zB`UFF7=xFQpRw+HjKiGK{)8j28S!{jf+eVdpGK{85vu<(i#OTx*DdyTQc&jmP!0cv
zO6WXlz)$V@7v>LGO?{Iwe!ByxJ8}p!@D3{RP7m|HJ24OSop=?O;WgBb%o^M9>#yfl
zQHY{qv-u_}^LJ1yJA#ezl=%^A3qQw(BQbBIRvz()Z!=WCSc{WT?K_~$4CYOk$KUxi
z&&Ayik1w869PE@kr+j*F^33vJ>CE87nWdAbme1&1vml{en`RT|&Ym-KMzFki;?%O@
z)hn{%Yu?Su2&?%zCov?lPcSpHZ_nN}t$K}*NW8La%k>Sf1v}MMF28(er3S29zvXIG
z^|k8PI@gr;|8?u&wdZ$Ssor?`!1CZj4Wqfs`(L1dlic(|Du_ir^b{L#4r#0VS-e=A@J^kG0-uvGB+~s}$?Auf2$?huN
zjmT=N9DgQOajrRLgem&}pZN`)3npBE)o~Gq;Bxa_tN)|Lo3RG%+b!N_mYRRH_D}uT
zbLS}3;eoGE6WqmS_y9FfXd~yUVk9cJhNy&Eq1rp3`uDIn6E)6ARHEZBfD^DYF2J#(Bjpol^nvh_%+7hGi-w~O`U6pBTxy<
zM?L>0YW%m*(@NG;&<>Ph6dp&d^k1m0et^pOKUf!oct``(L&dF8iT5;Y^EE9
z8Yd5xz#PK
z>a$S^OvPF_)8Ylq+5ZSC7Ez%IS7A18MP0flxD9_mW&S6!lOLcG*o|800gS}sn28s0
z6h^b15Hkz2h;vZm
zZ9%Pg2WsMjsJn3$m!tPH1#Ril0IwFVLT&Xf)ER$_O7L^kim#&*yJJ2;?c7u3-nnYb
zQV*k03B;oMCt?E}h#GG+GM?wAP|%8Jp;kBt^rnl-q5AJYt?)zh
z2nG?KMos*M)n7&}>=r74hgh5WT@deJr9ToQ~W>HyZU8%ta+!i2ZRRdV25%1!ekg)WkL0`ez!6Rfz*w4cnl0
zrlYmTp%Nc}dOicSfQhJnGf)Z5L-kvRx@)UY^KNd-{%gxis8D7nQ4@ZNdhjb$Lf>F-
ze2ChKF75oIipOB$G}M_skD7Qa^2WM}s0kNg2o_>TTy633cI>|r_>2m5ynmSAUm7j-npJS$vBt^7xDq$$L?BNJP36sGO#b^q7vJTskj~W34VaOoWU$#i3L!JdHpHS=0@XSd;|H<
zo#B7a;bV)tcJgoaDC|Z3V${UPuq9r`SbTzgumj6fzX=$R8&C;cL^joZi%EL_8+7p-
zvN4T@>8P#WkG!JpD*g?BMBRy_UHuiDNA1LSsDAZ$=Xzi~Dv?R3_O-|x=#F6)-b7v2
znC?oP^B+nfK*dzlRxd|&ScfF(4qyV_w77AMe^jZcBN>cgn2Dh{26abru_jKzS~weZ
z>)$}_ToDE_zuQS+8t%sW*o0;1z3zr}FcmdH4yt`TYT((ZJL945%p%m8E=PU1{)~FA
z)I5die;##5E}<7k;U5$N7|y5DZ6M+Tp0nl#kPK3l32`
zH|~8LgSzdHP)8EMIqHZSp>{Spf&EuS3>8{gZ`4W#o7t#@UP3)E0ktEuQ7c<)u0TEa
z9%`ZusD!uT(kjjsqx$#YmC{0=L+xO)M?no~R*_+GHmc)ji+^QKGGDRwIj99JKpn*r
zYkv=Qht{EvWS`ZaLiPLH^sZXrE;gp&5h|guL_dK@RNNHXV>I%{xgppF=b;8%kLtGr
zS*+WOov|GCI@ja-t?xu4s{a_|we;LXt5}L!$!d!?nY&RB9=7rJHcM-J%;mQ7QL|cp^o`y@G%N6V$B&D}Ndx>94n$2n0(HA*<8Iu9N+gS~lP=xM
zs5>zQmB2z&f^VVvtw$xaW0-Z=YYm67ISrqow*C&Pqf7Cvj@pSjsDav`Cg_1xF{x-}
zcs)-onbfG~OQ@-5T94OJ?FHsa)Ce1`eY?fG%!8=@$IY{-!@h=1@qxuPQvD2@rE;ix
zpp{j0Hxp0^^+hF=gPJ%OHQ)^7x7odp>h~eW<0q*1?NE~j
zd$yGdO|TO+;71mpK}~oGH9$G)upXPW)BRtScBtnvQT=jJw{oV%#i-{#!Vo-*mE(F8
zs!~yI4fjz4JTjk|wFddOs0oJ9o`y;|19i(rqMn;$E<{aGXl_C6}02DdtermS>{|o`j+JJ8Xpo*anMH{k}l0{1$2_?qMzFcTXz|H1ZwN
z#C1_CYh-a8YJlFzE9Qn-eG%#su0PQ}8eT?Aip!#;mFP!Uxt#K~4V1Boj
zf+j3Q-QG*s6@xSUOyf`!q@Z4-krg=G5wVrYW=ZOHXPB`^*9;~N-_
zC(u*jHieZK%sZp4S%U#AMqQFJRDw5ATlzC<1+8B22TDh^Pexu0S77lO)K1(pgNOME
zw!tRU_Zr6jD}ih(^t${OHPBAfS$&E+k~3HfFPJyX`xr|7_ZA0b`_G4?{wdQL)t-Xn
z;4(2BH=xGboz4EM;xHAJ6IsIr>v#i|$hTJi2(_YT7=cm4{lE0Zpq@`c^&e<)Iwlck
zq87FkBXJw@i|I-|3QFK9>gOyZ$NyDmipsD9YT$U(O8TG%NVj;5wdYzq6*b;$RKEgL
zLTgatt+V#+rdLW~4-I9gj`LphFG)UjBK{ba`8^zmK_mR{#INyf;tx+W02cz#`uix1K^xDz>3A+-n}i$|Er^pdP$x@l90!ijgJD8U_cK>}uAq
zQPGvyh~U6$d2?PZIuYBTq$0Lc)sR4k{FntDOCl4RSE-&5h>hvpGqxnY*VvGfJ$+X+
xXmM}TU+x|$tk}HW-6>sm=g6je3%8fAEeH&skUM4Ogp!wYBL6o)(TtI&{tK;3e5wEd
diff --git a/application/translations/zh/LC_MESSAGES/messages.po b/application/translations/zh/LC_MESSAGES/messages.po
index eafc01c5..9c1269bc 100644
--- a/application/translations/zh/LC_MESSAGES/messages.po
+++ b/application/translations/zh/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: KindleEar v3.0.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2024-04-06 20:42-0300\n"
+"POT-Creation-Date: 2024-04-09 11:48-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language: zh\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.14.0\n"
-#: application/templates/admin.html:3 application/templates/base.html:146
+#: application/templates/admin.html:3 application/templates/base.html:149
msgid "Admin"
msgstr "账号管理"
@@ -192,22 +192,18 @@ msgid "Email or Username"
msgstr "邮箱或用户名"
#: application/templates/adv_archive.html:56
-#: application/templates/adv_archive.html:72 application/templates/base.html:47
+#: application/templates/adv_archive.html:72 application/templates/base.html:49
#: application/templates/home.html:16 application/templates/login.html:25
#: application/templates/setting.html:222 application/templates/signup.html:25
#: application/templates/user_account.html:19
msgid "Password"
msgstr "密码"
-#: application/templates/adv_archive.html:59 application/templates/base.html:57
+#: application/templates/adv_archive.html:59
+#: application/templates/adv_archive.html:75 application/templates/base.html:59
msgid "Verify"
msgstr "校验"
-#: application/templates/adv_archive.html:75
-#: application/templates/book_translator.html:94
-msgid "Test"
-msgstr "测试"
-
#: application/templates/adv_archive.html:78
msgid "client_id"
msgstr "client_id"
@@ -380,343 +376,360 @@ msgid "Click to back"
msgstr "点击直接返回"
#: application/templates/base.html:23
+msgid "Delete (Ctrl for no confirm)"
+msgstr "删除 (按住Ctrl无确认)"
+
+#: application/templates/base.html:24
msgid "View Source Code"
msgstr "查看源代码"
-#: application/templates/base.html:24
+#: application/templates/base.html:25
msgid "Subscribe (Deliver Separately)"
msgstr "订阅 (单独推送)"
-#: application/templates/base.html:25
+#: application/templates/base.html:26
msgid "Subscribe"
msgstr "订阅"
-#: application/templates/base.html:26
+#: application/templates/base.html:27
msgid "Cannot add this custom rss, Error:"
msgstr "无法添加这个自定义RSS,错误描述:"
-#: application/templates/base.html:27
+#: application/templates/base.html:28
msgid "Cannot delete this feed, Error:"
msgstr "无法删除此订阅,错误描述:"
-#: application/templates/base.html:28
+#: application/templates/base.html:29
msgid "Fulltext"
msgstr "全文RSS"
-#: application/templates/base.html:29 application/templates/base.html:36
+#: application/templates/base.html:30 application/templates/base.html:38
msgid "Share"
msgstr "分享"
-#: application/templates/base.html:30
+#: application/templates/base.html:31
msgid "Are you sure to delete ({0})?"
msgstr "您确认要删除 ({0}) 吗?"
-#: application/templates/base.html:31
+#: application/templates/base.html:32
+msgid "Are you sure to REMOVE ALL CUSTOM RSS?"
+msgstr ""
+
+#: application/templates/base.html:33
msgid "Share links, share happiness"
msgstr "分享链接,分享快乐"
-#: application/templates/base.html:32
+#: application/templates/base.html:34
msgid "Category"
msgstr "类别"
-#: application/templates/base.html:33 application/templates/setting.html:129
+#: application/templates/base.html:35 application/templates/setting.html:129
msgid "Language"
msgstr "语言"
-#: application/templates/base.html:34
+#: application/templates/base.html:36
msgid ""
"Please write a category in text field if the one you wish is not in the "
"list."
msgstr "如果您需要的类别不在列表中,可以在文本框中直接输入。"
-#: application/templates/base.html:35
+#: application/templates/base.html:37
msgid "Cancel"
msgstr "取消"
-#: application/templates/base.html:37
+#: application/templates/base.html:39
msgid "Language code invalid"
msgstr "语言代码不合法"
-#: application/templates/base.html:38
+#: application/templates/base.html:40
msgid "Thank you for sharing."
msgstr "谢谢您的分享。"
-#: application/templates/base.html:39
+#: application/templates/base.html:41
msgid "Close"
msgstr "关闭"
-#: application/templates/base.html:40
+#: application/templates/base.html:42
msgid "Unsubscribe"
msgstr "退订"
-#: application/templates/base.html:41
+#: application/templates/base.html:43
msgid "Cannot subscribe this recipe, Error:"
msgstr "无法订阅这个Recipe,错误描述:"
-#: application/templates/base.html:42
+#: application/templates/base.html:44
msgid "Are you sure to Unsubscribe ({0})?"
msgstr "您确认要取消订阅 ({0}) 吗?"
-#: application/templates/base.html:43
+#: application/templates/base.html:45
msgid "Cannot unsubscribe this recipe, Error:"
msgstr "无法取消订阅这个Recipe,错误描述:"
-#: application/templates/base.html:44
+#: application/templates/base.html:46
msgid "The recipe is already subscribed."
msgstr "这个Recipe已经被订阅了。"
-#: application/templates/base.html:45
+#: application/templates/base.html:47
msgid "Website login lnformation"
msgstr "网站登录信息"
-#: application/templates/base.html:46
+#: application/templates/base.html:48
msgid "Account"
msgstr "账号"
-#: application/templates/base.html:48
+#: application/templates/base.html:50
msgid "Submit"
msgstr "提交"
-#: application/templates/base.html:49
+#: application/templates/base.html:51
msgid ""
"If any field is left blank, the server will clear the saved login "
"information."
msgstr "如果任意文本框留空白,则此登录信息将从服务器删除。"
-#: application/templates/base.html:50
+#: application/templates/base.html:52
msgid "Cannot set the website login information, Error:"
msgstr "无法设置登录信息,错误描述:"
-#: application/templates/base.html:51
+#: application/templates/base.html:53
msgid "Choose a recipe file to upload"
msgstr "选择一个Recipe文件上传"
-#: application/templates/base.html:52
+#: application/templates/base.html:54
msgid "Congratulations"
msgstr "恭喜"
-#: application/templates/base.html:53
+#: application/templates/base.html:55
msgid "Thanks"
msgstr "谢谢"
-#: application/templates/base.html:54
+#: application/templates/base.html:56
msgid ""
"Your recipe has been uploaded, and it can be found in the Library "
"section. If you dont see it, please make sure to switch to the correct "
-"language ({0})."
-msgstr "您的Recipe已经上传,可以在本页面的 \"新闻源\" 区段找到。如果没有,请确认您已经切换到正确的语种 ({0})。"
+"language."
+msgstr "您的Recipe已经上传,可以在本页面的 \"新闻源\" 区段找到。如果没有,请确认您已经切换到正确的语种。"
-#: application/templates/base.html:55
+#: application/templates/base.html:57
msgid "Your recipe have been deleted."
msgstr "您的Recipe已经被删除。"
-#: application/templates/base.html:56
+#: application/templates/base.html:58
msgid "Kindleify Selection"
msgstr "选择内容发送到Kindle"
-#: application/templates/base.html:58
+#: application/templates/base.html:60
msgid "Verified"
msgstr "已校验"
-#: application/templates/base.html:59 application/view/login.py:87
+#: application/templates/base.html:61 application/view/login.py:87
#: application/view/share.py:157
msgid "The username does not exist or password is wrong."
msgstr "用户名不存在或密码错误。"
-#: application/templates/base.html:60
+#: application/templates/base.html:62
msgid "The file you chosen is not an acceptable type."
msgstr "您选择的文件不是可以接受的类型。"
-#: application/templates/base.html:61
+#: application/templates/base.html:63
msgid "The file have been uploaded successfully."
msgstr "文件已经成功上传。"
-#: application/templates/base.html:62
+#: application/templates/base.html:64
msgid "This feed has been successfully subscribed."
msgstr "已经成功订阅此新闻源。"
-#: application/templates/base.html:63
+#: application/templates/base.html:65
msgid "Thank you for your feedback, this feed will be reviewed soon."
msgstr "谢谢您的反馈,此新闻源将很快会被检视和确认。"
-#: application/templates/base.html:64
+#: application/templates/base.html:66
msgid "Are you confirming to share the recipe ({0})?"
msgstr "您确认要分享这个Recipe ({0}) 吗?"
-#: application/templates/base.html:65
+#: application/templates/base.html:67
msgid "[All]"
msgstr "[所有分类]"
-#: application/templates/base.html:66
+#: application/templates/base.html:68
msgid "[By Time]"
msgstr "[按时间]"
-#: application/templates/base.html:67
+#: application/templates/base.html:69
msgid "[Uncategoried]"
msgstr "[未分类]"
-#: application/templates/base.html:68
+#: application/templates/base.html:70
msgid "There are no links found."
msgstr "没有找到任何链接。"
-#: application/templates/base.html:69
+#: application/templates/base.html:71
msgid "Invalid report"
msgstr "报告新闻源已失效"
-#: application/templates/base.html:70
+#: application/templates/base.html:72
msgid "Are you confirming that this link is invalid or off the cloud?"
msgstr "您确认这个链接已经失效或无法链接吗?"
-#: application/templates/base.html:71
+#: application/templates/base.html:73
msgid "Customize delivery time"
msgstr "自定义推送时间"
-#: application/templates/base.html:72 application/templates/setting.html:49
+#: application/templates/base.html:74 application/templates/setting.html:49
msgid "Delivery days"
msgstr "推送日"
-#: application/templates/base.html:73 application/templates/setting.html:51
+#: application/templates/base.html:75 application/templates/setting.html:51
msgid "Mon"
msgstr "一"
-#: application/templates/base.html:74 application/templates/setting.html:53
+#: application/templates/base.html:76 application/templates/setting.html:53
msgid "Tue"
msgstr "二"
-#: application/templates/base.html:75 application/templates/setting.html:55
+#: application/templates/base.html:77 application/templates/setting.html:55
msgid "Wed"
msgstr "三"
-#: application/templates/base.html:76 application/templates/setting.html:57
+#: application/templates/base.html:78 application/templates/setting.html:57
msgid "Thu"
msgstr "四"
-#: application/templates/base.html:77 application/templates/setting.html:59
+#: application/templates/base.html:79 application/templates/setting.html:59
msgid "Fri"
msgstr "五"
-#: application/templates/base.html:78 application/templates/setting.html:61
+#: application/templates/base.html:80 application/templates/setting.html:61
msgid "Sat"
msgstr "六"
-#: application/templates/base.html:79 application/templates/setting.html:63
+#: application/templates/base.html:81 application/templates/setting.html:63
msgid "Sun"
msgstr "日"
-#: application/templates/base.html:80
+#: application/templates/base.html:82
msgid "Delivery times"
msgstr "推送时间"
-#: application/templates/base.html:81
+#: application/templates/base.html:83
msgid "The customized delivery time for the recipe has been successfully saved."
msgstr "这个Recipe的自定义推送时间已经设定成功。"
-#: application/templates/base.html:82
+#: application/templates/base.html:84
msgid "The account have been deleted."
msgstr "这个账号已经被删除。"
-#: application/templates/base.html:83 application/view/share.py:147
+#: application/templates/base.html:85 application/view/share.py:147
msgid "The username or password is empty."
msgstr "用户名或密码为空。"
-#: application/templates/base.html:84 application/view/admin.py:86
+#: application/templates/base.html:86 application/view/admin.py:86
#: application/view/admin.py:166 application/view/admin.py:205
#: application/view/login.py:229 application/view/login.py:289
msgid "The two new passwords are dismatch."
msgstr "两个密码不匹配。"
-#: application/templates/base.html:85
+#: application/templates/base.html:87
msgid "Password changed successfully."
msgstr "修改密码成功。"
-#: application/templates/base.html:86
+#: application/templates/base.html:88
msgid "Account added successfully."
msgstr "添加账号成功。"
-#: application/templates/base.html:87 application/view/login.py:139
+#: application/templates/base.html:89 application/view/login.py:139
msgid "login required"
msgstr "需要登录"
-#: application/templates/base.html:88
+#: application/templates/base.html:90
msgid "Upload cover files successfully."
msgstr "上传封面图像成功。"
-#: application/templates/base.html:89
+#: application/templates/base.html:91
msgid ""
"Total size of the files you selected exceeds 16MB. Please reduce the "
"image resolution or upload in batches."
msgstr "您选择的文件总和超过16MB,请减小图像分辨率或分批上传。"
-#: application/templates/base.html:90
+#: application/templates/base.html:92
#: application/templates/book_translator.html:3
#: application/templates/book_translator.html:17
msgid "Translator"
msgstr "书籍翻译器"
-#: application/templates/base.html:91
+#: application/templates/base.html:93
msgid "Upl"
msgstr "Upl"
-#: application/templates/base.html:92
+#: application/templates/base.html:94
msgid "Sep"
msgstr "Sep"
-#: application/templates/base.html:93
+#: application/templates/base.html:95
msgid "Log"
msgstr "Log"
-#: application/templates/base.html:94
+#: application/templates/base.html:96
msgid "Emb"
msgstr "Emb"
-#: application/templates/base.html:95
+#: application/templates/base.html:97
msgid ""
"The test email has been successfully sent to the following addresses. "
"Please check your inbox or spam folder to confirm its delivery. Depending"
" on your email server, there may be a slight delay."
msgstr "测试邮件已经成功发送到以下地址, 请打开收件箱或垃圾箱确认是否到达,根据服务器不同,可能稍有延迟。"
-#: application/templates/base.html:96
+#: application/templates/base.html:98
msgid "Translating..."
msgstr "正在翻译..."
-#: application/templates/base.html:97
+#: application/templates/base.html:99
msgid "The configuration validation is correct."
msgstr "配置校验正确。"
-#: application/templates/base.html:119 application/templates/home.html:12
+#: application/templates/base.html:100 application/templates/logs.html:22
+#: application/templates/logs.html:67 application/templates/my.html:17
+#: application/templates/setting.html:98 application/templates/setting.html:99
+#: application/templates/setting.html:100
+#: application/templates/setting.html:101
+#: application/templates/setting.html:125
+msgid "Title"
+msgstr "书籍标题"
+
+#: application/templates/base.html:122 application/templates/home.html:12
msgid "Logout"
msgstr "退出"
-#: application/templates/base.html:121 application/templates/home.html:17
+#: application/templates/base.html:124 application/templates/home.html:17
#: application/templates/login.html:3 application/templates/login.html:19
#: application/templates/login.html:29
msgid "Login"
msgstr "登录"
-#: application/templates/base.html:123 application/templates/signup.html:3
+#: application/templates/base.html:126 application/templates/signup.html:3
#: application/templates/signup.html:19 application/templates/signup.html:43
msgid "Signup"
msgstr "注册"
-#: application/templates/base.html:143 application/templates/home.html:11
+#: application/templates/base.html:146 application/templates/home.html:11
#: application/templates/my.html:3
msgid "Feeds"
msgstr "我的订阅"
-#: application/templates/base.html:144 application/templates/setting.html:3
+#: application/templates/base.html:147 application/templates/setting.html:3
msgid "Settings"
msgstr "设置"
-#: application/templates/base.html:145 application/templates/logs.html:3
+#: application/templates/base.html:148 application/templates/logs.html:3
msgid "Logs"
msgstr "投递日志"
-#: application/templates/base.html:147
+#: application/templates/base.html:150
msgid "Advanced"
msgstr "高级设置"
-#: application/templates/base.html:148 application/templates/library.html:3
+#: application/templates/base.html:151 application/templates/library.html:3
msgid "Shared"
msgstr "网友分享"
@@ -804,6 +817,10 @@ msgstr "原文"
msgid "Translation"
msgstr "译文"
+#: application/templates/book_translator.html:94
+msgid "Test"
+msgstr "测试"
+
#: application/templates/change_password.html:15
msgid "Old password"
msgstr "原密码"
@@ -881,14 +898,6 @@ msgstr "只显示最后10条日志"
msgid "Time"
msgstr "时间"
-#: application/templates/logs.html:22 application/templates/logs.html:67
-#: application/templates/my.html:17 application/templates/setting.html:98
-#: application/templates/setting.html:99 application/templates/setting.html:100
-#: application/templates/setting.html:101
-#: application/templates/setting.html:125
-msgid "Title"
-msgstr "书籍标题"
-
#: application/templates/logs.html:23 application/templates/logs.html:68
msgid "Size"
msgstr "附件大小"
@@ -926,8 +935,8 @@ msgid "Library"
msgstr "新闻源"
#: application/templates/my.html:57
-msgid "Upload your custom recipe"
-msgstr "上传您的 Recipe"
+msgid "Upload custom recipe"
+msgstr "上传自定义 Recipe"
#: application/templates/my.html:70
msgid "Subscription to selected recipe successful."
@@ -1133,8 +1142,8 @@ msgstr "账号"
msgid "Never expire"
msgstr "永久有效"
-#: application/view/admin.py:51 application/view/adv.py:376
-#: application/view/setting.py:96 application/view/subscribe.py:245
+#: application/view/admin.py:51 application/view/adv.py:373
+#: application/view/setting.py:96 application/view/subscribe.py:250
msgid "Settings Saved!"
msgstr "恭喜,保存成功!"
@@ -1250,29 +1259,29 @@ msgstr "tumblr"
msgid "Open in browser"
msgstr "在浏览器打开"
-#: application/view/adv.py:378
+#: application/view/adv.py:375
msgid "The format is invalid."
msgstr "格式非法。"
-#: application/view/adv.py:411
+#: application/view/adv.py:408
msgid "Authorization Error!
{}"
msgstr "申请授权过程失败!
{}"
-#: application/view/adv.py:434
+#: application/view/adv.py:431
msgid "Success authorized by Pocket!"
msgstr "已经成功获得Pocket的授权!"
-#: application/view/adv.py:440
+#: application/view/adv.py:437
msgid ""
"Failed to request authorization of Pocket!
See details "
"below:
{}"
msgstr "申请Pocket授权失败!
错误信息参考如下:
{}"
-#: application/view/adv.py:462
+#: application/view/adv.py:459
msgid "The Instapaper service encountered an error. Please try again later."
msgstr "Instapaper服务器异常,请稍候再试。"
-#: application/view/adv.py:475
+#: application/view/adv.py:472
msgid "Request type [{}] unsupported"
msgstr "不支持你请求的命令类型 [{}]"
@@ -1293,10 +1302,10 @@ msgstr "没有需要推送的Recipe。"
msgid "Cannot fetch data from {}, status: {}"
msgstr "无法从 {} 获取数据,状态: {}"
-#: application/view/library.py:51 application/view/subscribe.py:195
-#: application/view/subscribe.py:221 application/view/subscribe.py:276
-#: application/view/subscribe.py:305 application/view/subscribe.py:425
-#: application/view/subscribe.py:453 application/view/subscribe.py:460
+#: application/view/library.py:51 application/view/subscribe.py:200
+#: application/view/subscribe.py:226 application/view/subscribe.py:281
+#: application/view/subscribe.py:310 application/view/subscribe.py:430
+#: application/view/subscribe.py:458 application/view/subscribe.py:465
msgid "The recipe does not exist."
msgstr "此Recipe不存在。"
@@ -1482,7 +1491,7 @@ msgstr "他加禄语"
msgid "Hausa"
msgstr "豪萨语"
-#: application/view/share.py:54 application/view/subscribe.py:317
+#: application/view/share.py:54 application/view/subscribe.py:322
msgid "Unknown command: {}"
msgstr "未知命令:{}"
@@ -1533,53 +1542,54 @@ msgstr "标题或URL为空。"
msgid "Failed to fetch the recipe."
msgstr "抓取Recipe失败。"
-#: application/view/subscribe.py:125 application/view/subscribe.py:387
+#: application/view/subscribe.py:125 application/view/subscribe.py:392
msgid "Failed to save the recipe. Error:"
msgstr "保存Recipe失败。错误:"
-#: application/view/subscribe.py:157
+#: application/view/subscribe.py:161
msgid "The Rss does not exist."
msgstr "此RSS不存在。"
-#: application/view/subscribe.py:204 application/view/subscribe.py:261
-#: application/view/subscribe.py:280 application/view/subscribe.py:362
+#: application/view/subscribe.py:209 application/view/subscribe.py:266
+#: application/view/subscribe.py:285 application/view/subscribe.py:367
msgid "This recipe has not been subscribed to yet."
msgstr "此Recipe尚未被订阅。"
-#: application/view/subscribe.py:239
+#: application/view/subscribe.py:244
msgid "The api key is required."
msgstr "需要填写api key."
-#: application/view/subscribe.py:284
+#: application/view/subscribe.py:289
msgid "The text is empty."
msgstr "文本为空。"
-#: application/view/subscribe.py:343
+#: application/view/subscribe.py:348
msgid "You can only delete the uploaded recipe."
msgstr "您只能删除你自己上传的Recipe。"
-#: application/view/subscribe.py:347
+#: application/view/subscribe.py:352
msgid "The recipe have been subscribed, please unsubscribe it before delete."
msgstr "此Recipe已经被订阅,请先取消订阅然后再删除。"
-#: application/view/subscribe.py:374
+#: application/view/subscribe.py:379
msgid "Can not read uploaded file, Error:"
msgstr "无法读取上传的文件,错误:"
-#: application/view/subscribe.py:382
+#: application/view/subscribe.py:387
msgid ""
"Failed to decode the recipe. Please ensure that your recipe is saved in "
"utf-8 encoding."
msgstr "解码Recipe失败,请确保您的Recipe为utf-8编码。"
-#: application/view/subscribe.py:402
+#: application/view/subscribe.py:407
msgid "The recipe is already in the library."
msgstr "此Recipe已经在新闻源中。"
-#: application/view/subscribe.py:432
+#: application/view/subscribe.py:437
msgid "The login information for this recipe has been cleared."
msgstr "此Recipe的网站登录信息已经被删除。"
-#: application/view/subscribe.py:436
+#: application/view/subscribe.py:441
msgid "The login information for this recipe has been saved."
msgstr "此Recipe的网站登录信息已经保存。"
+
diff --git a/application/view/adv.py b/application/view/adv.py
index a762638b..4c318b8e 100644
--- a/application/view/adv.py
+++ b/application/view/adv.py
@@ -173,11 +173,8 @@ def AdvImportPost():
return adv_render_template('adv_import.html', 'import', user=user, tips=str(e))
for o in walkOpmlOutline(rssList):
- title, url, isfulltext = xml_unescape(o.text), unquote(xml_unescape(o.xmlUrl)), o.isFulltext #isFulltext为非标准属性
- if isfulltext:
- isfulltext = str_to_bool(isfulltext)
- else:
- isfulltext = defaultIsFullText
+ title, url, isfulltext = xml_unescape(o.text or o.title), xml_unescape(o.xmlUrl), o.isFulltext #isFulltext为非标准属性
+ isfulltext = str_to_bool(isfulltext) if isfulltext else defaultIsFullText
if not url.startswith('http'):
url = ('https:/' if url.startswith('/') else 'https://') + url
@@ -235,8 +232,9 @@ def AdvExport():
date += '+{:02d}00'.format(user.timezone) if (user.timezone > 0) else '-{:02d}00'.format(abs(user.timezone))
outLines = []
for feed in user.all_custom_rss():
- outLines.append(''.format(
- xml_escape(feed.title), xml_escape(feed.url), feed.isfulltext))
+ isfulltext = 'yes' if feed.isfulltext else 'no'
+ outLines.append(''.format(
+ xml_escape(feed.title), xml_escape(feed.url), isfulltext))
outLines = '\n'.join(outLines)
opmlFile = opmlTpl.format(date=date, outLines=outLines).encode('utf-8')
diff --git a/application/view/subscribe.py b/application/view/subscribe.py
index bc27bc6c..dcd667e1 100644
--- a/application/view/subscribe.py
+++ b/application/view/subscribe.py
@@ -147,14 +147,19 @@ def AddCustomRss(user, form):
#删除自定义RSS
def DeleteCustomRss(user, rssId):
- recipeType, rssId = Recipe.type_and_id(rssId)
- rss = Recipe.get_by_id_or_none(rssId)
- if rss:
- rss.delete_instance()
+ tips = {'status': 'ok'}
+ if rssId == '#all_custom_rss#': #删除所有当前的自定义RSS
+ Recipe.delete().where((Recipe.user == user.name) & (Recipe.type_ == 'custom')).execute()
UpdateBookedCustomRss(user)
- return {'status': 'ok'}
else:
- return {'status': _('The Rss does not exist.')}
+ recipeType, rssId = Recipe.type_and_id(rssId)
+ rss = Recipe.get_by_id_or_none(rssId)
+ if (recipeType == 'custom') and rss:
+ rss.delete_instance()
+ UpdateBookedCustomRss(user)
+ else:
+ tips = {'status': _('The Rss does not exist.')}
+ return tips
#根据特定用户的自定义RSS推送使能设置,更新已订阅列表
def UpdateBookedCustomRss(user: KeUser):
diff --git a/docker/gunicorn.conf.py b/docker/gunicorn.conf.py
index d2e26779..b493a8b9 100644
--- a/docker/gunicorn.conf.py
+++ b/docker/gunicorn.conf.py
@@ -1,13 +1,62 @@
# gunicorn.conf.py
+import os
pythonpath = "/usr/local/lib/python3.10/site-packages"
bind = "0.0.0.0:8000"
workers = 1
threads = 3
-accesslog = "/data/gunicorn.access.log"
-errorlog = "/data/gunicorn.error.log"
capture_output = True
enable_stdio_inheritance = True
-loglevel = "info"
+#accesslog = "/data/gunicorn.access.log"
+#errorlog = "/data/gunicorn.error.log"
+#loglevel = "info"
#preload_app = True
-#certfile = 'cert.pem'
-#keyfile = 'key.pem'
+certfile = os.getenv('GUNI_CERT')
+keyfile = os.getenv('GUNI_KEY')
+#example: https://github.com/benoitc/gunicorn/blob/master/gunicorn/glogging.py
+logconfig_dict = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ "root": {"level": "INFO", "handlers": ["error_file", "access_file"]},
+ 'loggers': {
+ "gunicorn.error": {
+ "level": "INFO",
+ "handlers": ["error_file"],
+ "propagate": 1,
+ "qualname": "gunicorn.error"
+ },
+ "gunicorn.access": {
+ "level": "INFO",
+ "handlers": ["access_file"],
+ "propagate": 0,
+ "qualname": "gunicorn.access"
+ }
+ },
+ 'handlers': {
+ "error_file": {
+ "class": "logging.handlers.RotatingFileHandler",
+ "maxBytes": 50*1024*1024, #50M
+ "backupCount": 1,
+ "formatter": "generic",
+ #'mode': 'w+',
+ "filename": "/data/gunicorn.error.log"
+ },
+ "access_file": {
+ "class": "logging.handlers.RotatingFileHandler",
+ "maxBytes": 10*1024*1024, #10M
+ "backupCount": 1,
+ "formatter": "generic",
+ "filename": "/data/gunicorn.access.log"
+ }
+ },
+ 'formatters':{
+ "generic": {
+ "format": "'[%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s'",
+ "datefmt": "[%Y-%m-%d %H:%M:%S %z]",
+ "class": "logging.Formatter"
+ },
+ "access": {
+ "format": "'[%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s'",
+ "class": "logging.Formatter"
+ }
+ }
+}
diff --git a/tools/bookmarklet_src/send_to_kindle.js b/tools/bookmarklet_src/send_to_kindle.js
index 60cbfabe..5e133429 100644
--- a/tools/bookmarklet_src/send_to_kindle.js
+++ b/tools/bookmarklet_src/send_to_kindle.js
@@ -10,7 +10,7 @@ if(s){
var h = (tag, props)=>Object.assign(document.createElement(tag), props);
var form = h("form", {action:url, method:"post", hidden:true, target:"_blank"});
for (var [name, value] of Object.entries(formData)){
- form.appendChild(h("input", {name: value}));
+ form.appendChild(h("input", {"name": name, "value": value}));
}
document.body.appendChild(form);
form.submit();
diff --git a/tools/nginx/gunicorn.conf.py b/tools/nginx/gunicorn.conf.py
index 1d14010c..8a5af767 100644
--- a/tools/nginx/gunicorn.conf.py
+++ b/tools/nginx/gunicorn.conf.py
@@ -3,11 +3,59 @@
bind = "127.0.0.1:8000"
workers = 1
threads = 3
-accesslog = "/var/log/gunicorn/error.log"
-errorlog = "/var/log/gunicorn/access.log"
+#accesslog = "/var/log/gunicorn/error.log"
+#errorlog = "/var/log/gunicorn/access.log"
capture_output = True
enable_stdio_inheritance = True
-loglevel = "info"
+#loglevel = "info"
#preload_app = True
#certfile = 'cert.pem'
#keyfile = 'key.pem'
+#example: https://github.com/benoitc/gunicorn/blob/master/gunicorn/glogging.py
+logconfig_dict = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ "root": {"level": "INFO", "handlers": ["error_file", "access_file"]},
+ 'loggers': {
+ "gunicorn.error": {
+ "level": "INFO",
+ "handlers": ["error_file"],
+ "propagate": 1,
+ "qualname": "gunicorn.error"
+ },
+ "gunicorn.access": {
+ "level": "INFO",
+ "handlers": ["access_file"],
+ "propagate": 0,
+ "qualname": "gunicorn.access"
+ }
+ },
+ 'handlers': {
+ "error_file": {
+ "class": "logging.handlers.RotatingFileHandler",
+ "maxBytes": 50*1024*1024, #50M
+ "backupCount": 1,
+ "formatter": "generic",
+ #'mode': 'w+',
+ "filename": "/data/gunicorn.error.log"
+ },
+ "access_file": {
+ "class": "logging.handlers.RotatingFileHandler",
+ "maxBytes": 10*1024*1024, #10M
+ "backupCount": 1,
+ "formatter": "generic",
+ "filename": "/data/gunicorn.access.log"
+ }
+ },
+ 'formatters':{
+ "generic": {
+ "format": "'[%(process)d] [%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s'",
+ "datefmt": "[%Y-%m-%d %H:%M:%S %z]",
+ "class": "logging.Formatter"
+ },
+ "access": {
+ "format": "'[%(process)d] [%(asctime)s] %(levelname)s [%(filename)s:%(lineno)s] %(message)s'",
+ "class": "logging.Formatter"
+ }
+ }
+}
diff --git a/tools/update_req.py b/tools/update_req.py
index 789d3d86..e67688eb 100644
--- a/tools/update_req.py
+++ b/tools/update_req.py
@@ -41,7 +41,11 @@
'rq': [('flask-rq2', '>=18.3,<19.0'),],
}
-REQ_PLAT = {'gae': [('appengine-python-standard', '>=1.1.6,<2.0.0'),],}
+REQ_PLAT = {'gae': [('appengine-python-standard', '>=1.1.6,<2.0.0'),],
+ 'docker': [('weedata', '>=0.2.1,<1.0.0'),('pymysql', '>=1.1.0,<2.0.0'), #docker install all libs
+ ('psycopg2', '>=2.9.9,<3.0.0'),('pymongo', '>=3.7.2,<4.0.0'),('redis', '>=4.5.0,<6.0.0'),
+ ('celery', '>=5.3.6,<6.0.0'),('flask-rq2', '>=18.3,<19.0'),('sqlalchemy', '>=2.0.28,<3.0.0')],
+}
EXTRA = {
'sqlalchemy': [('sqlalchemy', '>=2.0.28,<3.0.0')],
@@ -78,7 +82,7 @@ def config_to_dict(cfgFile):
return config_dict
#prepare config.py to build docker
-def dockered_config_py(cfgFile):
+def dockerize_config_py(cfgFile):
default_cfg = {'APP_ID': 'kindleear', 'DATABASE_URL': 'sqlite:////data/kindleear.db',
'TASK_QUEUE_SERVICE': 'apscheduler', 'TASK_QUEUE_BROKER_URL': 'memory',
'KE_TEMP_DIR': '/tmp', 'DOWNLOAD_THREAD_NUM': '3', 'ALLOW_SIGNUP': 'no',
@@ -110,6 +114,41 @@ def dockered_config_py(cfgFile):
f.write('\n'.join(ret))
print(f'Finished update {cfgFile}')
+#prepare config.py to deploy in gae
+def gaeify_config_py(cfgFile):
+ appId = os.getenv('GOOGLE_CLOUD_PROJECT', 'kindleear')
+ domain = f"https://{appId}.appspot.com"
+ default_cfg = {'APP_ID': appId, 'APP_DOMAIN': domain, 'SERVER_LOCATION': 'us-central1',
+ 'DATABASE_URL': 'datastore', 'TASK_QUEUE_SERVICE': 'gae', 'TASK_QUEUE_BROKER_URL': '',
+ 'KE_TEMP_DIR': '', 'DOWNLOAD_THREAD_NUM': '3', 'ALLOW_SIGNUP': 'no',
+ 'HIDE_MAIL_TO_LOCAL': 'yes', 'LOG_LEVEL': 'warning'}
+ ret = []
+ inDocComment = False
+ pattern = r"^([_A-Z]+)\s*=\s*(.+)$"
+ with open(cfgFile, 'r', encoding='utf-8') as f:
+ lines = f.read().splitlines()
+ for line in lines:
+ line = line.strip()
+ if '"""' in line or "'''" in line:
+ inDocComment = not inDocComment
+ ret.append(line)
+ continue
+ elif not line or line.startswith('#') or inDocComment:
+ ret.append(line)
+ continue
+
+ match = re.match(pattern, line)
+ name = match.group(1) if match else None
+ value = default_cfg.get(name, None)
+ if name is not None and value is not None:
+ ret.append(f'{name} = "{value}"')
+ else:
+ ret.append(line)
+
+ with open(cfgFile, 'w', encoding='utf-8') as f:
+ f.write('\n'.join(ret))
+ print(f'Finished update {cfgFile}')
+
if __name__ == '__main__':
thisDir = os.path.abspath(os.path.dirname(__file__))
cfgFile = os.path.normpath(os.path.join(thisDir, '..', 'config.py'))
@@ -118,8 +157,14 @@ def dockered_config_py(cfgFile):
cfgFile = os.path.normpath(os.path.join(thisDir, 'config.py'))
reqFile = os.path.normpath(os.path.join(thisDir, 'requirements.txt'))
+ dockerize = False
+ gaeify = False
if len(sys.argv) == 2 and sys.argv[1] == 'docker':
- dockered_config_py(cfgFile)
+ dockerize_config_py(cfgFile)
+ dockerize = True
+ elif len(sys.argv) == 2 and sys.argv[1] == 'gae':
+ gaeify_config_py(cfgFile)
+ gaeify = True
else:
print('\nThis script can help you to update requirements.txt.\n')
usrInput = input('Press y to continue :')
@@ -130,10 +175,12 @@ def dockered_config_py(cfgFile):
db = cfg['DATABASE_URL'].split('://')[0]
task = cfg['TASK_QUEUE_SERVICE']
broker = cfg['TASK_QUEUE_BROKER_URL']
- if (cfg['DATABASE_URL'].startswith('datastore') or cfg['TASK_QUEUE_SERVICE'] == 'gae'):
+ plat = ''
+ if dockerize:
+ plat = 'docker'
+ elif (cfg['DATABASE_URL'].startswith('datastore') or cfg['TASK_QUEUE_SERVICE'] == 'gae'):
plat = 'gae'
- else:
- plat = ''
+
extras = set()
if broker.startswith('redis://'):
extras.add('redis')