', self.source_lang)
# Recommend setting temperature to 0.5 for retaining the placeholder.
if self.merge_enabled:
- prompt += ' Ensure that placeholders matching the pattern' \
- '{{id_\\d+}} in the content are retained.'
+ prompt += (' Ensure that placeholders matching the pattern'
+ '{{id_\\d+}} in the content are retained.')
return prompt + ' Start translating: ' + text
def _headers(self):
diff --git a/application/lib/ebook_translator/engines/microsoft.py b/application/lib/ebook_translator/engines/microsoft.py
index 8c7452e7..e49b7622 100644
--- a/application/lib/ebook_translator/engines/microsoft.py
+++ b/application/lib/ebook_translator/engines/microsoft.py
@@ -1,21 +1,16 @@
import json
import base64
from datetime import datetime
-
+from urllib.parse import urljoin, urlencode
from .languages import microsoft
from .base import Base
-
-try:
- from urllib.parse import urlencode
-except ImportError:
- from urllib import urlencode
-
class MicrosoftEdgeTranslate(Base):
name = 'MicrosoftEdge(Free)'
alias = 'Microsoft Edge (Free)'
lang_codes = Base.load_lang_codes(microsoft)
- endpoint = 'https://api-edge.cognitive.microsofttranslator.com/translate'
+ default_api_host = 'https://api-edge.cognitive.microsofttranslator.com'
+ endpoint = '/translate'
need_api_key = False
access_info = None
@@ -27,7 +22,8 @@ def _normalized_endpoint(self):
}
if not self._is_auto_lang():
query['from'] = self._get_source_code()
- return '%s?%s' % (self.endpoint, urlencode(query))
+ endpoint = urljoin(self.default_api_host, self.endpoint)
+ return f'{endpoint}?{urlencode(query)}'
def _parse_jwt(self, token):
parts = token.split(".")
diff --git a/application/lib/ebook_translator/engines/youdao.py b/application/lib/ebook_translator/engines/youdao.py
index 21363eb7..5edd3b28 100644
--- a/application/lib/ebook_translator/engines/youdao.py
+++ b/application/lib/ebook_translator/engines/youdao.py
@@ -3,7 +3,7 @@
import time
import uuid
import hashlib
-
+from urllib.parse import urljoin
from .base import Base
from .languages import youdao
@@ -11,7 +11,8 @@ class YoudaoTranslate(Base):
name = 'Youdao'
alias = 'Youdao'
lang_codes = Base.load_lang_codes(youdao)
- endpoint = 'https://openapi.youdao.com/api'
+ default_api_host = 'https://openapi.youdao.com'
+ endpoint = '/api'
api_key_hint = 'appid|appsecret'
api_key_pattern = r'^[^\s:\|]+?[:\|][^\s:\|]+$'
api_key_errors = ['401']
@@ -53,6 +54,6 @@ def translate(self, text):
'vocabId': False,
}
- return self.get_result(
- self.endpoint, data, headers, method='POST',
+ endpoint = urljoin(self.api_host or self.default_api_host, self.endpoint)
+ return self.get_result(endpoint, data, headers, method='POST',
callback=lambda r: json.loads(r)['translation'][0])
diff --git a/application/lib/ebook_translator/html_translator.py b/application/lib/ebook_translator/html_translator.py
index 116d0878..fd90ddc4 100644
--- a/application/lib/ebook_translator/html_translator.py
+++ b/application/lib/ebook_translator/html_translator.py
@@ -10,6 +10,7 @@ def get_trans_engines():
info = {}
for engine in builtin_translate_engines:
info[engine.name] = {'alias': engine.alias, 'need_api_key': engine.need_api_key,
+ 'default_api_host': engine.default_api_host, 'api_key_hint': engine.api_key_hint,
'source': engine.lang_codes.get('source', {}),
'target': engine.lang_codes.get('target', {}),}
return info
@@ -22,18 +23,17 @@ def __init__(self, params: dict, thread_num: int=1):
self.engineName = self.params.get('engine')
self.src = self.params.get('src_lang', '')
self.dst = self.params.get('dst_lang', 'en')
- self.translator = self.create_engine(self.engineName)
- self.translator.set_config(params)
+ self.translator = self.create_engine(self.engineName, params)
self.translator.set_source_code(self.src)
self.translator.set_target_code(self.dst)
- def create_engine(self, name):
+ def create_engine(self, name, params):
engines = {engine.name: engine for engine in builtin_translate_engines}
if name in engines:
engine_class = engines.get(name)
else:
engine_class = GoogleFreeTranslate
- return engine_class()
+ return engine_class(params)
#翻译文本
#data: 文本/字典/列表 {'text': text, ...}, [{},{}]
@@ -55,11 +55,11 @@ def translate_text(self, data):
item['error'] = ''
item['translated'] = ''
if text:
- try:
+ if 1:
item['translated'] = self.translator.translate(text)
- except Exception as e:
- default_log.warning(str(e))
- item['error'] = str(e)
+ #except Exception as e:
+ #default_log.warning('translate_text() failed: ' + str(e))
+ #item['error'] = str(e)
else:
item['error'] = _('The input text is empty')
ret.append(item)
@@ -89,7 +89,7 @@ def translate_soup(self, soup):
else:
failed += 1
except Exception as e:
- default_log.warning(str(e))
+ default_log.warning('translate_soup failed: ' + str(e))
failed += 1
if (idx < count - 1) and (self.translator.request_interval > 0.01):
time.sleep(self.translator.request_interval)
diff --git a/application/lib/pocket.py b/application/lib/pocket.py
index c945601b..93596cbb 100644
--- a/application/lib/pocket.py
+++ b/application/lib/pocket.py
@@ -74,24 +74,24 @@ def __init__(self, consumer_key, redirect_uri=None):
self.access_token = None
self.opener = UrlOpener(headers=self.POCKET_HEADERS)
- def _post(self, method_url, **kw):
+ def _post(self, method_url, act, **kw):
ret = self.opener.open(method_url, data=kw)
if ret.status_code > 399:
- raise APIError(ret.status_code, ret.headers.get('X-Error-Code',''), ret.headers.get('X-Error',''), 'Get access token')
+ raise APIError(ret.status_code, ret.headers.get('X-Error-Code',''), ret.headers.get('X-Error',''), act)
try:
return json.loads(ret.content)
except:
return json.text
- def _authenticated_post(self, method_url, **kw):
+ def _authenticated_post(self, method_url, act, **kw):
kw['consumer_key'] = self.consumer_key
kw['access_token'] = self.access_token
- return self._post(method_url, **kw)
+ return self._post(method_url, act, **kw)
def get_request_token(self):
#此步仅用来直接通过一次http获取一个request_token(code),pocket不会回调redirect_uri
- ret = self._post(self.REQUEST_TOKEN_URL, consumer_key=self.consumer_key, redirect_uri=self.redirect_uri)
+ ret = self._post(self.REQUEST_TOKEN_URL, 'get request token', consumer_key=self.consumer_key, redirect_uri=self.redirect_uri)
return ret.get('code', '') if isinstance(ret, dict) else ret.split('=')[-1]
def get_authorize_url(self, code):
@@ -101,7 +101,7 @@ def get_authorize_url(self, code):
def get_access_token(self, code):
# access token : {"access_token":"dcba4321-dcba-4321-dcba-4321dc","username":"pocketuser"}.
- ret = self._post(self.ACCESS_TOKEN_URL, consumer_key=self.consumer_key, code=code)
+ ret = self._post(self.ACCESS_TOKEN_URL, 'get access token', consumer_key=self.consumer_key, code=code)
self.access_token = ret.get('access_token', '')
return self.access_token
@@ -115,10 +115,10 @@ def add(self, **kw):
#tags : 可选,逗号分隔的字符串列表
#tweet_id : 可选,用于发推的ID
#返回一个字典,包含的键可能有:https://getpocket.com/developer/docs/v3/add
- return self._authenticated_post('https://getpocket.com/v3/add', **kw)
+ return self._authenticated_post('https://getpocket.com/v3/add', 'add an entry', **kw)
def get(self, **kw):
- return self._authenticated_post('https://getpocket.com/v3/get', **kw)
+ return self._authenticated_post('https://getpocket.com/v3/get', 'get an entry', **kw)
def modify(self, **kw):
pass
diff --git a/application/lib/wallabag.py b/application/lib/wallabag.py
new file mode 100644
index 00000000..c03b8b93
--- /dev/null
+++ b/application/lib/wallabag.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+#Wallabag api,实现添加一个条目到wallabag
+#Wallabag 官方主机:https://app.wallabag.it
+from urllib.parse import urljoin
+from urlopener import UrlOpener
+
+class WallaBag:
+ ALLOW_EXTENSIONS = ('json', 'xml', 'txt', 'csv', 'pdf', 'epub', 'mobi', 'html')
+
+ #config是一个字典,包含键 host, client_id, client_secret, username, password,
+ #access_token, refresh_token
+ def __init__(self, config, log, extension='json'):
+ for item in ['host', 'client_id', 'client_secret', 'username', 'password']:
+ config.setdefault(item, '') #保证每个元素都存在
+ self.config = config
+ assert(extension in self.ALLOW_EXTENSIONS)
+ self.extension = extension
+ self.log = log
+ self.opener = UrlOpener()
+
+ #添加一个条目到 WallaBag
+ #url : 要保存的URL,最好先urlencode
+ #title : 可选,如果保存的是一个图片或PDF,则需要,否则不需要
+ #tags : 可选,逗号分隔的字符串列表
+ #archive: 0/1 - 条目已存档
+ #starred: 0/1 - 条目已加星标
+ #content: 如果不需要wallbag联网获取url的内容,可以使用此参数传递,同时title/url也不能为空
+ #language: 语言种类
+ #published_at: 发布日期,$YYYY-MM-DDTHH:II:SS+TZ 或整数时间戳
+ #authors: 逗号分割的作者名字字符串
+ #public: 0/1 - 是否创建用于公开分享的链接
+ #origin_url: 原始地址,可以和url相同
+ #返回字典:{'msg': , 'changed': , 'resp': }
+ #如果成功,resp保存服务器返回的json,msg为空,changed只是token是否有变化
+ #失败时msg保存失败描述
+ def add(self, url, **kwargs):
+ ret = {'msg': '', 'changed': False, 'resp': None}
+ kwargs['url'] = url
+ resp, need_get_token = self._add_entry(kwargs)
+
+ if need_get_token: #重新获取access token
+ self.log.debug('Updating wallabag token')
+ msg = self.update_token()
+ if not msg:
+ self.log.info('Refreshed wallabag token')
+ ret['changed'] = True
+ resp, _ = self._add_entry(kwargs) #重新执行
+ else:
+ ret['msg'] = msg
+ elif not resp:
+ ret['msg'] = 'No server response or non-JSON format returned.'
+
+ ret['resp'] = resp
+ return ret
+
+ #更新token,包括access_token/refresh_token
+ #如果失败,返回错误描述字符串,否则返回空串
+ def update_token(self):
+ c = self.config
+ url = urljoin(c["host"], '/oauth/v2/token')
+ data1 = {'username': c['username'], 'password': c['password'], 'client_id': c['client_id'],
+ 'client_secret': c['client_secret'], 'grant_type': 'password'}
+ data2 = {'client_id': c['client_id'], 'client_secret': c['client_secret'],
+ 'refresh_token': c.get('refresh_token', ''), 'grant_type': 'refresh_token'}
+
+ errors = []
+ dataList = [data2, data1] if c.get('refresh_token') else [data1]
+ for data in dataList:
+ try:
+ resp = self.opener.open(url, data=data)
+ except Exception as e:
+ msg = 'Error while getting token using {}: {}'.format(data['grant_type'], str(e))
+ self.log.warning(msg)
+ errors.append(msg)
+ continue
+
+ if resp.status_code < 400:
+ respDict = resp.json()
+ if isinstance(respDict, dict):
+ self.config['access_token'] = respDict.get('access_token', '')
+ self.config['refresh_token'] = respDict.get('refresh_token', '')
+ return '' #任何一个成功就返回
+ else:
+ msg = 'Error while getting token using {}: {}'.format(data['grant_type'], resp.status_code)
+ self.log.warning(msg)
+ errors.append(msg)
+
+ return '\n'.join(errors)
+
+ #将一个url添加到wallabag
+ #data: 为一个字典
+ #返回 resp, need_get_token
+ def _add_entry(self, data):
+ access_token = self.config.get('access_token', '')
+ if not access_token:
+ return None, True
+
+ headers = {'Authorization': f'Bearer {access_token}'}
+
+ wallaUrl = urljoin(self.config['host'], f'/api/entries.{self.extension}')
+ try:
+ resp = self.opener.open(wallaUrl, headers=headers, data=data)
+ except Exception as e:
+ self.log.error('Error adding url: {}'.format(str(e)))
+ return None, False
+
+ self.log.debug('add url to wallabag get Resp: {}, {}'.format(resp.status_code, resp.text))
+ if resp.status_code >= 400:
+ return None, (resp.status_code == 401)
+ else:
+ return resp.json(), False
+
diff --git a/application/static/base.css b/application/static/base.css
index b9fcaae3..91d9e41a 100644
--- a/application/static/base.css
+++ b/application/static/base.css
@@ -916,7 +916,7 @@ div[class="schedule_daytimes"] input {
border: 1px solid #333;
border-radius: 5px;
font-size: 14px;
- line-height:20px;
+ line-height: 18px;
top: calc(-100%); /*calc(10% + 5px);*/
left: 50%;
transform: translateX(-50%);
diff --git a/application/static/base.js b/application/static/base.js
index 40c54b60..55b36dbd 100644
--- a/application/static/base.js
+++ b/application/static/base.js
@@ -733,7 +733,7 @@ function insertBookmarkletGmailThis(subscribeUrl, mailPrefix) {
var parent = $('#bookmarklet_content');
var newElement = $('', {
class: 'actionButton',
- href: "javascript:(function(){popw='';Q='';d=document;w=window;if(d.selection){Q=d.selection.createRange().text;}else if(w.getSelection){Q=w.getSelection();}else if(d.getSelection){Q=d.getSelection();}popw=w.open('http://mail.google.com/mail/s?view=cm&fs=1&tf=1&to=" + addr +
+ href: "javascript:(function(){popw='';Q='';d=document;w=window;if(d.selection){Q=d.selection.createRange().text;}else if(w.getSelection){Q=w.getSelection();}else if(d.getSelection){Q=d.getSelection();}popw=w.open('https://mail.google.com/mail/s?view=cm&fs=1&tf=1&to=" + addr +
"&su='+encodeURIComponent(d.title)+'&body='+encodeURIComponent(Q)+escape('%5Cn%5Cn')+encodeURIComponent(d.location)+'&zx=RANDOMCRAP&shva=1&disablechatbrowsercheck=1&ui=1','gmailForm','scrollbars=yes,width=550,height=400,top=100,left=75,status=no,resizable=yes');if(!d.all)setTimeout(function(){popw.focus();},50);})();",
click: function() {
return false;
@@ -774,15 +774,10 @@ function SelectDeliverNone() {
///[end] adv_delivernow.html
///[start] adv_archive.html
-function verifyInstapaper() {
- var notifyInstapaperVerify = function () {
- $("#averInstapaper").html(i18n.verify);
- };
+function VerifyInstapaper() {
var instauser = $("#instapaper_username").val();
var instapass = $("#instapaper_password").val();
- notifyInstapaperVerify();
-
$.ajax({
url: "/verifyajax/instapaper",
type: "POST",
@@ -790,18 +785,41 @@ function verifyInstapaper() {
success: function (data, textStatus, jqXHR) {
if (data.status != "ok") {
alert("Error:" + data.status);
- notifyInstapaperVerify();
} else if (data.correct == 1) {
- alert(i18n.congratulations);
- $("#averInstapaper").html(i18n.verified);
+ ShowSimpleModalDialog('{0}
{1}
'.format(i18n.congratulations, i18n.configOk));
} else {
alert(i18n.passwordWrong);
- notifyInstapaperVerify();
}
},
error: function (status) {
alert(status);
- notifyInstapaperVerify();
+ }
+ });
+}
+
+//测试wallabag的配置信息是否正确
+function VerifyWallaBag() {
+ let host = $("#wallabag_host").val();
+ let name = $("#wallabag_username").val();
+ let passwd = $("#wallabag_password").val();
+ let id_ = $("#wallabag_client_id").val();
+ let secret = $("#wallabag_client_secret").val();
+ let data = {'host': host, 'username': name, 'password': passwd, 'client_id': id_,
+ 'client_secret': secret};
+
+ $.ajax({
+ url: "/verifyajax/wallabag",
+ type: "POST",
+ data: data,
+ success: function (data, textStatus, jqXHR) {
+ if (data.status == "ok") {
+ ShowSimpleModalDialog('{0}
{1}
'.format(i18n.congratulations, i18n.configOk));
+ } else {
+ alert(data.status);
+ }
+ },
+ error: function (status) {
+ alert(status.toString());
}
});
}
@@ -1123,12 +1141,18 @@ function PopulateTranslatorFields(currEngineName) {
//选择一个翻译引擎后显示或隐藏ApiKey文本框,语种列表也同步更新
//src_lang/dst_lang: 当前recipe的语种代码
function TranslatorEngineFieldChanged(src_lang, dst_lang) {
- //显示或隐藏ApiKey文本框
+ //显示或隐藏ApiHost/ApiKey文本框
var engineName = $('#translator_engine').val();
var engine = g_trans_engines[engineName];
if (!engine || engine.need_api_key) {
+ $('#api_host_input').attr('placeholder', engine.default_api_host);
+ $('#api_keys_textarea').attr('placeholder', engine.api_key_hint);
+ $('#api_keys_textarea').prop("required", true);
+ $('#translator_api_host').show();
$('#translator_api_key').show();
} else {
+ $('#api_keys_textarea').prop("required", false);
+ $('#translator_api_host').hide();
$('#translator_api_key').hide();
}
@@ -1177,14 +1201,16 @@ function TranslatorEngineFieldChanged(src_lang, dst_lang) {
//测试Recipe的翻译器设置是否正确
function TestTranslator(recipeId) {
var text = $('#translator_test_src_text').val();
- console.log(text);
+ //console.log(text);
var divDst = $('#translator_test_dst_text');
+ divDst.val(i18n.translating);
$.post("/translator/test", {recipeId: recipeId, text: text}, function (data) {
if (data.status == "ok") {
divDst.val(data.text);
} else if (data.status == i18n.loginRequired) {
window.location.href = '/login';
} else {
+ divDst.val('');
alert(data.status);
}
});
diff --git a/application/templates/admin.html b/application/templates/admin.html
index 146cf12b..718b4524 100644
--- a/application/templates/admin.html
+++ b/application/templates/admin.html
@@ -65,7 +65,7 @@
{{_("Yes") if u.enable_send else _("No")}} |
{{u.email}} |
{% if u.expiration_days == 0 -%}
- {{_("Never expire")}} |
+ {{_("Never")}} |
{% elif u.expiration_days == 7 -%}
{{_("7 Days")}} |
{% elif u.expiration_days == 30 -%}
@@ -82,7 +82,7 @@
{{u.expiration_days}} Days |
{% endif -%}
-
+
{% if u.name == adminName -%}
{% else -%}
diff --git a/application/templates/adv_archive.html b/application/templates/adv_archive.html
index 05994172..460ae07d 100644
--- a/application/templates/adv_archive.html
+++ b/application/templates/adv_archive.html
@@ -8,6 +8,7 @@
{% set wiz = shareLinks.get('Wiz', {}) %}
{% set instapaper = shareLinks.get('Instapaper', {}) %}
{% set pocket = shareLinks.get('Pocket', {}) %}
+{% set wallabag = shareLinks.get('wallabag', {}) %}
|