├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── addons ├── FastWQ.py └── fastwq │ ├── __init__.py │ ├── constants.py │ ├── context.py │ ├── gui │ ├── __init__.py │ ├── base.py │ ├── common.py │ ├── dictmanager.py │ ├── foldermanager.py │ ├── options.py │ ├── progress.py │ └── setting.py │ ├── lang.py │ ├── libs │ ├── AnkiHub │ │ ├── __init__.py │ │ ├── markdown2.py │ │ └── updates.py │ ├── __init__.py │ ├── ankihub.py │ ├── bs4 │ │ ├── __init__.py │ │ ├── builder │ │ │ ├── __init__.py │ │ │ └── _htmlparser.py │ │ ├── dammit.py │ │ ├── diagnose.py │ │ └── element.py │ ├── mdict │ │ ├── __init__.py │ │ ├── lzo.py │ │ ├── mdict_query.py │ │ ├── pureSalsa20.py │ │ ├── readmdict.py │ │ └── ripemd128.py │ ├── pystardict.py │ └── snowballstemmer │ │ ├── __init__.py │ │ ├── among.py │ │ ├── basestemmer.py │ │ └── english_stemmer.py │ ├── query │ ├── __init__.py │ ├── common.py │ └── worker.py │ ├── res │ ├── add.png │ ├── null.png │ ├── ok.png │ ├── setting.png │ └── wqicon.png │ ├── service │ ├── __init__.py │ ├── base.py │ ├── dict │ │ ├── LDOCE5.py │ │ ├── LDOCE6.py │ │ ├── __init__.py │ │ ├── baicizhan.py │ │ ├── baidu_chinese.py │ │ ├── bing.py │ │ ├── bing3tp.py │ │ ├── bingimg.py │ │ ├── cambridge.py │ │ ├── cambridge_cs.py │ │ ├── cambridge_ct.py │ │ ├── cambridge_ee.py │ │ ├── dreye.py │ │ ├── esdict.py │ │ ├── frdic.py │ │ ├── iciba.py │ │ ├── longman.py │ │ ├── ludwig.py │ │ ├── minidict.py │ │ ├── oxford.py │ │ ├── oxford_learning.py │ │ ├── remotemdx.py │ │ ├── txt.py │ │ ├── vocabulary.py │ │ ├── yahoo.py │ │ ├── youdao.py │ │ ├── youdaofr.py │ │ └── youdaoko.py │ ├── manager.py │ ├── pool.py │ └── static │ │ ├── _baidu.css │ │ ├── _bing.css │ │ ├── _cambridge.css │ │ ├── _dreye.css │ │ ├── _ldoce6.css │ │ ├── _longman.css │ │ ├── _oxford.css │ │ ├── _yahoo.css │ │ └── _youdao.css │ └── utils │ ├── Queue.py │ ├── __init__.py │ ├── helper.py │ ├── importlib.py │ └── misc.py ├── addons21 └── fastwq │ ├── __init__.py │ ├── common.py │ ├── constants.py │ ├── context.py │ ├── gui │ ├── __init__.py │ ├── base.py │ ├── common.py │ ├── dictmanager.py │ ├── foldermanager.py │ ├── options.py │ ├── progress.py │ └── setting.py │ ├── lang.py │ ├── libs │ ├── __init__.py │ ├── mdict │ │ ├── __init__.py │ │ ├── lzo.py │ │ ├── mdict_query.py │ │ ├── pureSalsa20.py │ │ ├── readmdict.py │ │ └── ripemd128.py │ ├── pystardict.py │ └── snowballstemmer │ │ ├── __init__.py │ │ ├── among.py │ │ ├── basestemmer.py │ │ └── english_stemmer.py │ ├── query │ ├── __init__.py │ ├── common.py │ └── worker.py │ ├── res │ ├── add.png │ ├── null.png │ ├── ok.png │ ├── setting.png │ └── wqicon.png │ ├── service │ ├── __init__.py │ ├── base.py │ ├── dict │ │ ├── LDOCE5.py │ │ ├── LDOCE6.py │ │ ├── TLD.py │ │ ├── __init__.py │ │ ├── baicizhan.py │ │ ├── baidu_chinese.py │ │ ├── baidufy.py │ │ ├── bing.py │ │ ├── bing3tp.py │ │ ├── bingimg.py │ │ ├── cambridge.py │ │ ├── cambridge_cs.py │ │ ├── cambridge_ct.py │ │ ├── cambridge_ee.py │ │ ├── dreye.py │ │ ├── esdict.py │ │ ├── eudict.py │ │ ├── frdic.py │ │ ├── iciba.py │ │ ├── lgmcw_freq.py │ │ ├── longman.py │ │ ├── ludwig.py │ │ ├── minidict.py │ │ ├── mw.py │ │ ├── oalecd9_mdx.py │ │ ├── oxford.py │ │ ├── oxford_learning.py │ │ ├── remotemdx.py │ │ ├── spanishdict.py │ │ ├── txt.py │ │ ├── vocabulary.py │ │ ├── yahoo.py │ │ ├── youdao.py │ │ ├── youdaofr.py │ │ └── youdaoko.py │ ├── manager.py │ ├── pool.py │ └── static │ │ ├── _baidu.css │ │ ├── _bing.css │ │ ├── _cambridge.css │ │ ├── _dreye.css │ │ ├── _eudict.css │ │ ├── _ldoce6.css │ │ ├── _longman.css │ │ ├── _mw.css │ │ ├── _oxford.css │ │ ├── _yahoo.css │ │ └── _youdao.css │ └── utils │ ├── Queue.py │ ├── __init__.py │ ├── helper.py │ ├── importlib.py │ └── misc.py ├── docs ├── get_mdx_ldoce6_sounds.md ├── images │ ├── dict_folder_01.png │ ├── dict_folder_02.png │ ├── import.png │ ├── mdx_mdd_files.png │ ├── options.png │ ├── query_all.png │ ├── query_end.png │ ├── querying.png │ └── set_dicts.png └── services.md └── screenshots ├── options_01.png ├── options_02.png ├── options_03.png ├── options_04.png ├── setting_config_01.png ├── setting_config_02.png ├── setting_menu.png ├── setting_shortcut.png ├── use_01.png ├── use_02.png └── use_03.png /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | # osx 107 | .DS_Store 108 | 109 | # Anki 110 | prefs*.db 111 | User*/ 112 | 用户*/ 113 | .vscode/ 114 | .pylintrc 115 | *.pyc 116 | release*.zip 117 | README.txt 118 | .fastwq_*_ankihub.json 119 | 120 | # idea ide 121 | /.idea 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastWordQuery Addon For Anki 2 | 3 | [Supported Dictionaries](docs/services.md) 4 | 5 | [为单词添加真人发音(朗文mdx词典)](docs/get_mdx_ldoce6_sounds.md) 6 | 7 | 8 | 9 | ## Features 10 | 11 | This addon query words definitions or examples etc. fields from local or online dictionaries to fill into the Anki note. 12 | It forks from [WordQuery](https://github.com/finalion/WordQuery), added **multi-thread** feature, improve stability, and some other features. 13 | 14 | - Querying Words and Making Cards, IMMEDIATELY! 15 | - Support querying in mdx and stardict dictionaries. 16 | - Support querying in web dictionaries. 17 | - Support **Multi-Thread** to query faster. 18 | 19 | ## Install 20 | 21 | 1. Place addons or addons21 folder of this repository to anki addon folder. 22 | **OR** 23 | 2. Use the installation code: **1807206748** 24 | 25 | 26 | ## Setting 27 | 28 | ### Shortcut 29 | 1. Click Menu **"Tools -> Add-ons -> FastWQ -> Edit..."** 30 | ![](screenshots/setting_menu.png) 31 | 2. Edit the code and click **Save** 32 | ![](screenshots/setting_shortcut.png) 33 | 34 | ### Config 35 | 1. In Browser window click menu **"FastWQ -> Options"** 36 | ![](screenshots/setting_config_01.png) 37 | 38 | 2. Click **Settings** button in the Options window 39 | ![](screenshots/setting_config_02.png) 40 | - **Force Updates of all fields** : Update all fields even if it's None 41 | - **Ignore Accents** : Ignore accents symbol of word in querying 42 | - **Auto check new version** : Check new version at startup 43 | - **Number of Threads** : The number of threads running at the same time 44 | 45 | 46 | ## Usage 47 | 48 | ### Set the query fields 49 | 50 | 1. Click menu **"Tools -> FastWQ"**, or in Browser window click menu **"FastWQ -> Options"** 51 | 2. Select note type 52 | ![](screenshots/options_01.png) 53 | 3. Select Dictionary 54 | ![](screenshots/options_02.png) 55 | 4. Select Fields 56 | ![](screenshots/options_03.png) 57 | 5. Click **OK** button 58 | 59 | ### 'Browser' Window 60 | 1. Select single or multiple words, click menu **"FastWQ -> Query Selected"** or press shortcut Default is **Ctrl+Q**. 61 | ![](screenshots/options_04.png) 62 | 2. Waiting query finished 63 | ![](screenshots/use_01.png) 64 | 65 | ### 'Add' Window 66 | 1. Click Add button in Browser window, open Add window 67 | ![](screenshots/use_02.png) 68 | 2. Edit key field and click Query button 69 | ![](screenshots/use_03.png) 70 | 71 | 72 | ## Other Projects Used 73 | - [mdict-query](https://github.com/mmjang/mdict-query) 74 | - [pystardict](https://github.com/lig/pystardict) 75 | - [WordQuery](https://github.com/finalion/WordQuery) 76 | - [AnkiHub](https://github.com/dayjaby/AnkiHub) 77 | - [snowball_py](https://github.com/shibukawa/snowball_py) 78 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /addons/FastWQ.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import ssl 21 | import sys 22 | from anki.hooks import addHook 23 | from anki.utils import isMac 24 | 25 | sys.dont_write_bytecode = True 26 | if isMac: 27 | ssl._create_default_https_context = ssl._create_unverified_context 28 | 29 | ############## other config here ################## 30 | shortcut = ('Ctrl+Alt' if isMac else 'Ctrl') + '+Q' 31 | ################################################### 32 | 33 | def start_here(): 34 | 35 | """ 36 | Copyright (C) 2018 sthoo 37 | Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 38 | """ 39 | 40 | import fastwq 41 | 42 | fastwq.config.read() 43 | fastwq.my_shortcut = shortcut 44 | if not fastwq.have_setup: 45 | fastwq.have_setup = True 46 | fastwq.config_menu() 47 | fastwq.browser_menu() 48 | fastwq.context_menu() 49 | fastwq.customize_addcards() 50 | # if fastwq.config.auto_update: 51 | # fastwq.check_updates(True) 52 | 53 | addHook("profileLoaded", start_here) 54 | -------------------------------------------------------------------------------- /addons/fastwq/constants.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | from .lang import _ 22 | 23 | 24 | __all__ = ['VERSION', 'Endpoint', 'Template'] 25 | 26 | VERSION = 'v1.3.5' 27 | 28 | class Endpoint: 29 | repository = u'https://github.com/sth2018/FastWordQuery' 30 | feedback_issue = u'https://github.com/sth2018/FastWordQuery/issues' 31 | feedback_mail = u'sth201807@gmail.com' 32 | check_version = u'sth2018/FastWordQuery' 33 | user_guide = u'https://sth2018.github.io/FastWordQuery' 34 | version = VERSION 35 | 36 | 37 | class Template: 38 | tmpl_about = u'''{t0}
{version}
{t1}
39 | {url}
{t2}
40 | {feedback0}
41 | {feedback1}'''.format( 42 | t0=_('VERSION'), version=VERSION, 43 | t1=_('REPOSITORY'), url=Endpoint.repository, 44 | t2=_('FEEDBACK'), feedback0=Endpoint.feedback_issue, feedback1=Endpoint.feedback_mail) 45 | miss_css = u'MDX dictonary {dict} misses css file {css}.
Please choose the file.' 46 | -------------------------------------------------------------------------------- /addons/fastwq/context.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import json 21 | import os 22 | from aqt import mw 23 | from anki.hooks import runHook 24 | from .constants import VERSION 25 | from .utils import get_icon 26 | 27 | 28 | __all__ = ['APP_ICON', 'config'] 29 | 30 | 31 | APP_ICON = get_icon('wqicon.png') #Addon Icon 32 | 33 | 34 | class Config(object): 35 | 36 | """ 37 | Addon Config 38 | """ 39 | 40 | _CONFIG_FILENAME = u'fastwqcfg.json' #Config File Path 41 | 42 | def __init__(self, window): 43 | self.path = u'_' + self._CONFIG_FILENAME 44 | self.window = window 45 | self.data = None 46 | self.version = '0' 47 | self.read() 48 | 49 | @property 50 | def pmname(self): 51 | return self.window.pm.name 52 | 53 | def update(self, data): 54 | """ 55 | Update && Save 56 | """ 57 | data['version'] = VERSION 58 | data['%s_last' % self.pmname] = data.get('last_model', self.last_model_id) 59 | self.data.update(data) 60 | with open(self.path, 'wb') as f: 61 | json.dump(self.data, f, indent=4, sort_keys=True) 62 | f.close() 63 | runHook('config.update') 64 | 65 | def read(self): 66 | """ 67 | Load from config file 68 | """ 69 | if self.data: 70 | return self.data 71 | try: 72 | path = self.path if os.path.exists(self.path) else u'.' + self._CONFIG_FILENAME 73 | with open(path, 'rb') as f: 74 | self.data = json.load(f) 75 | f.close() 76 | if not os.path.exists(self.path): 77 | self.update(self.data) 78 | except: 79 | self.data = dict() 80 | 81 | def get_maps(self, model_id): 82 | """ 83 | Query fileds map 84 | """ 85 | return self.data.get(str(model_id), list()) 86 | 87 | @property 88 | def last_model_id(self): 89 | return self.data.get('%s_last' % self.pmname, 0) 90 | 91 | @property 92 | def dirs(self): 93 | return self.data.get('dirs', list()) 94 | 95 | @property 96 | def dicts(self): 97 | return self.data.get('dicts', dict()) 98 | 99 | @property 100 | def use_filename(self): 101 | return self.data.get('use_filename', True) 102 | 103 | @property 104 | def export_media(self): 105 | return self.data.get('export_media', False) 106 | 107 | @property 108 | def force_update(self): 109 | return self.data.get('force_update', False) 110 | 111 | @property 112 | def ignore_mdx_wordcase(self): 113 | return self.data.get('ignore_mdx_wordcase', False) 114 | 115 | @property 116 | def thread_number(self): 117 | """ 118 | Query Thread Number 119 | """ 120 | return self.data.get('thread_number', 16) 121 | 122 | @property 123 | def last_folder(self): 124 | """ 125 | last file dialog open path 126 | """ 127 | return self.data.get('last_folder', '') 128 | 129 | @property 130 | def ignore_accents(self): 131 | '''ignore accents of field in querying''' 132 | return self.data.get('ignore_accents', False) 133 | 134 | @property 135 | def auto_update(self): 136 | '''auto check new version''' 137 | return self.data.get('auto_update', True) 138 | 139 | @property 140 | def cloze_str(self): 141 | '''cloze formater string''' 142 | return self.data.get('cloze_str', '{{c1::%s}}') 143 | 144 | 145 | config = Config(mw) 146 | -------------------------------------------------------------------------------- /addons/fastwq/gui/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from .common import * 4 | from .progress import * 5 | -------------------------------------------------------------------------------- /addons/fastwq/gui/base.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import sys 21 | from anki.utils import isMac 22 | from aqt.qt import * 23 | from ..context import APP_ICON 24 | 25 | 26 | __all__ = ['Dialog', 'WIDGET_SIZE'] 27 | 28 | 29 | class Dialog(QDialog): 30 | ''' 31 | Base used for all dialog windows. 32 | ''' 33 | 34 | def __init__(self, parent, title): 35 | ''' 36 | Set the modal status for the dialog, sets its layout to the 37 | return value of the _ui() method, and sets a default title. 38 | ''' 39 | 40 | self._title = title 41 | self._parent = parent 42 | super(Dialog, self).__init__(parent) 43 | 44 | self.setModal(True) 45 | self.setWindowFlags( 46 | self.windowFlags() & 47 | ~Qt.WindowContextHelpButtonHint 48 | ) 49 | self.setWindowIcon(APP_ICON) 50 | self.setWindowTitle( 51 | title if "FastWQ" in title 52 | else "FastWQ - " + title 53 | ) 54 | # 2 & 3 & mac compatible 55 | if isMac and sys.hexversion >= 0x03000000: 56 | QApplication.setStyle('Fusion') 57 | 58 | 59 | class WidgetSize(object): 60 | ''' 61 | constant values 62 | ''' 63 | dialog_width = 850 64 | dialog_height_margin = 166 if isMac and sys.hexversion < 0x03000000 else 146 65 | map_min_height = 0 66 | map_max_height = 30 67 | map_fld_width = 100 68 | map_dictname_width = 150 69 | map_dict_width = 160 70 | map_field_width = 200 71 | 72 | 73 | WIDGET_SIZE = WidgetSize() 74 | -------------------------------------------------------------------------------- /addons/fastwq/gui/common.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import types 21 | from aqt import mw 22 | from aqt.qt import * 23 | from aqt.utils import showInfo 24 | from .options import OptionsDialog 25 | from .foldermanager import FoldersManageDialog 26 | from .dictmanager import DictManageDialog 27 | from ..libs import ankihub 28 | from ..context import config 29 | from ..lang import _ 30 | from ..constants import Endpoint, Template 31 | from ..service import service_manager, service_pool 32 | 33 | 34 | __all__ = ['show_options', 'show_fm_dialog', 'show_about_dialog'] #'check_updates', 35 | 36 | 37 | # def check_updates(background=False, parent=None): 38 | # '''check add-on last version''' 39 | # try: 40 | # parent = mw if parent is None else parent 41 | # state = ankihub.update([Endpoint.check_version], Endpoint.version, background, parent) 42 | # if not background: 43 | # if state == 0: 44 | # showInfo(_('LATEST_VERSION')) 45 | # elif state == -1: 46 | # showInfo(_('CHECK_FAILURE')) 47 | # except: 48 | # if not background: 49 | # showInfo(_('CHECK_FAILURE')) 50 | 51 | 52 | def show_fm_dialog(browser = None): 53 | '''open dictionary folder manager window''' 54 | parent = mw if browser is None else browser 55 | fm_dialog = FoldersManageDialog(parent, u'Dictionary Folder Manager') 56 | fm_dialog.activateWindow() 57 | fm_dialog.raise_() 58 | if fm_dialog.exec_() == QDialog.Accepted: 59 | # update local services 60 | service_pool.clean() 61 | service_manager.update_services() 62 | fm_dialog.destroy() 63 | # reshow options window 64 | show_options(browser) 65 | 66 | 67 | def show_dm_dialog(browser = None): 68 | parent = mw if browser is None else browser 69 | dm_dialog = DictManageDialog(parent, u'Dictionary Manager') 70 | dm_dialog.activateWindow() 71 | dm_dialog.raise_() 72 | if dm_dialog.exec_() == QDialog.Accepted: 73 | # update local services 74 | service_pool.clean() 75 | service_manager.update_services() 76 | dm_dialog.destroy() 77 | # reshow options window 78 | show_options(browser) 79 | 80 | 81 | def show_options(browser = None, model_id = -1, callback = None, *args, **kwargs): 82 | '''open options window''' 83 | parent = mw if browser is None else browser 84 | config.read() 85 | opt_dialog = OptionsDialog(parent, u'Options', model_id) 86 | opt_dialog.activateWindow() 87 | opt_dialog.raise_() 88 | result = opt_dialog.exec_() 89 | opt_dialog.destroy() 90 | if result == QDialog.Accepted: 91 | if isinstance(callback, types.FunctionType): 92 | callback(*args, **kwargs) 93 | elif result == 1001: 94 | show_fm_dialog(parent) 95 | elif result == 1002: 96 | show_dm_dialog(parent) 97 | 98 | 99 | def show_about_dialog(parent): 100 | '''open about dialog''' 101 | QMessageBox.about(parent, _('ABOUT'), Template.tmpl_about) 102 | -------------------------------------------------------------------------------- /addons/fastwq/gui/foldermanager.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from aqt.qt import * 21 | from .base import Dialog, WIDGET_SIZE 22 | from ..context import config 23 | from ..lang import _, _sl 24 | 25 | 26 | __all__ = ['FoldersManageDialog'] 27 | 28 | 29 | class FoldersManageDialog(Dialog): 30 | ''' 31 | Dictionary folder manager window. add or remove dictionary folders. 32 | ''' 33 | 34 | def __init__(self, parent, title=u'Dictionary Folder Manager'): 35 | super(FoldersManageDialog, self).__init__(parent, title) 36 | #self._dict_paths = [] 37 | self.build() 38 | 39 | def build(self): 40 | layout = QVBoxLayout() 41 | btn_layout = QHBoxLayout() 42 | add_btn = QPushButton("+") 43 | remove_btn = QPushButton("-") 44 | btn_layout.addWidget(add_btn) 45 | btn_layout.addWidget(remove_btn) 46 | add_btn.clicked.connect(self.add_folder) 47 | remove_btn.clicked.connect(self.remove_folder) 48 | self.folders_lst = QListWidget() 49 | self.folders_lst.addItems(config.dirs) 50 | self.chk_use_filename = QCheckBox(_('CHECK_FILENAME_LABEL')) 51 | self.chk_export_media = QCheckBox(_('EXPORT_MEDIA')) 52 | self.chk_use_filename.setChecked(config.use_filename) 53 | self.chk_export_media.setChecked(config.export_media) 54 | chk_layout = QHBoxLayout() 55 | chk_layout.addWidget(self.chk_use_filename) 56 | chk_layout.addWidget(self.chk_export_media) 57 | btnbox = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal, self) 58 | btnbox.accepted.connect(self.accept) 59 | layout.addLayout(btn_layout) 60 | layout.addWidget(self.folders_lst) 61 | layout.addLayout(chk_layout) 62 | layout.addWidget(btnbox) 63 | self.setLayout(layout) 64 | 65 | def add_folder(self): 66 | dir_ = QFileDialog.getExistingDirectory( 67 | self, 68 | caption=u"Select Folder", 69 | directory=config.last_folder, 70 | options=QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks 71 | ) 72 | if dir_: 73 | self.folders_lst.addItem(dir_) 74 | config.update({'last_folder': dir_}) 75 | 76 | def remove_folder(self): 77 | item = self.folders_lst.takeItem(self.folders_lst.currentRow()) 78 | del item 79 | 80 | @property 81 | def dirs(self): 82 | '''dictionary folders list''' 83 | return [self.folders_lst.item(i).text() 84 | for i in range(self.folders_lst.count())] 85 | 86 | def accept(self): 87 | '''ok button clicked''' 88 | self.save() 89 | super(FoldersManageDialog, self).accept() 90 | 91 | def save(self): 92 | '''save config to file''' 93 | data = { 94 | 'dirs': self.dirs, 95 | 'use_filename': self.chk_use_filename.isChecked(), 96 | 'export_media': self.chk_export_media.isChecked() 97 | } 98 | config.update(data) 99 | -------------------------------------------------------------------------------- /addons/fastwq/gui/setting.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from aqt.qt import * 21 | from .base import Dialog, WIDGET_SIZE 22 | from ..context import config 23 | from ..lang import _ 24 | 25 | 26 | __all__ = ['SettingDialog'] 27 | 28 | 29 | class SettingDialog(Dialog): 30 | ''' 31 | Setting window, some golbal params for query function. 32 | ''' 33 | 34 | def __init__(self, parent, title=u'Setting'): 35 | super(SettingDialog, self).__init__(parent, title) 36 | self.setFixedWidth(400) 37 | self.check_force_update = None 38 | self.check_ignore_accents = None 39 | # self.check_auto_update = None 40 | self.input_thread_number = None 41 | self.build() 42 | 43 | def build(self): 44 | layout = QVBoxLayout() 45 | 46 | check_force_update = QCheckBox(_("FORCE_UPDATE")) 47 | check_force_update.setChecked(config.force_update) 48 | layout.addWidget(check_force_update) 49 | layout.addSpacing(10) 50 | 51 | check_ignore_accents = QCheckBox(_("IGNORE_ACCENTS")) 52 | check_ignore_accents.setChecked(config.ignore_accents) 53 | layout.addWidget(check_ignore_accents) 54 | layout.addSpacing(10) 55 | 56 | # check_auto_update = QCheckBox(_("AUTO_UPDATE")) 57 | # check_auto_update.setChecked(config.auto_update) 58 | # layout.addWidget(check_auto_update) 59 | # layout.addSpacing(10) 60 | 61 | check_ighore_mdx_wordcase = QCheckBox(_("IGNORE_MDX_WORDCASE")) 62 | check_ighore_mdx_wordcase.setChecked(config.ignore_mdx_wordcase) 63 | layout.addWidget(check_ighore_mdx_wordcase) 64 | layout.addSpacing(10) 65 | 66 | hbox = QHBoxLayout() 67 | input_thread_number = QSpinBox(parent=self) 68 | input_thread_number.setRange(1, 120) 69 | input_thread_number.setValue(config.thread_number) 70 | input_label = QLabel(_("THREAD_NUMBER") + ":", parent=self) 71 | hbox.addWidget(input_label) 72 | hbox.setStretchFactor(input_label, 1) 73 | hbox.addWidget(input_thread_number) 74 | hbox.setStretchFactor(input_thread_number, 2) 75 | layout.addLayout(hbox) 76 | 77 | hbox = QHBoxLayout() 78 | input_cloze_str = QLineEdit() 79 | input_cloze_str.setText(config.cloze_str) 80 | input_label = QLabel(_("CLOZE_WORD_FORMAT") + ":", parent=self) 81 | hbox.addWidget(input_label) 82 | hbox.setStretchFactor(input_label, 1) 83 | hbox.addWidget(input_cloze_str) 84 | hbox.setStretchFactor(input_cloze_str, 2) 85 | layout.addLayout(hbox) 86 | 87 | buttonBox = QDialogButtonBox(parent=self) 88 | buttonBox.setStandardButtons(QDialogButtonBox.Ok) 89 | buttonBox.accepted.connect(self.accept) # 确定 90 | 91 | layout.addSpacing(48) 92 | layout.addWidget(buttonBox) 93 | 94 | self.check_force_update = check_force_update 95 | self.check_ignore_accents = check_ignore_accents 96 | # self.check_auto_update = check_auto_update 97 | self.check_ighore_mdx_wordcase = check_ighore_mdx_wordcase 98 | self.input_thread_number = input_thread_number 99 | self.input_cloze_str = input_cloze_str 100 | 101 | layout.setAlignment(Qt.AlignTop|Qt.AlignLeft) 102 | self.setLayout(layout) 103 | 104 | def accept(self): 105 | self.save() 106 | super(SettingDialog, self).accept() 107 | 108 | def save(self): 109 | data = { 110 | 'force_update': self.check_force_update.isChecked(), 111 | 'ignore_accents': self.check_ignore_accents.isChecked(), 112 | # 'auto_update': self.check_auto_update.isChecked(), 113 | 'ignore_mdx_wordcase': self.check_ighore_mdx_wordcase.isChecked(), 114 | 'thread_number': self.input_thread_number.value(), 115 | 'cloze_str': self.input_cloze_str.text() 116 | } 117 | config.update(data) 118 | -------------------------------------------------------------------------------- /addons/fastwq/libs/AnkiHub/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons/fastwq/libs/AnkiHub/__init__.py -------------------------------------------------------------------------------- /addons/fastwq/libs/AnkiHub/updates.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from aqt.qt import * 4 | 5 | try: 6 | _encoding = QApplication.UnicodeUTF8 7 | def _translate(context, text, disambig): 8 | return QApplication.translate(context, text, disambig, _encoding) 9 | except AttributeError: 10 | def _translate(context, text, disambig): 11 | return QApplication.translate(context, text, disambig) 12 | 13 | class Ui_DialogUpdates(object): 14 | def setupUi(self, DialogUpdates): 15 | DialogUpdates.setObjectName(u"DialogUpdates") 16 | DialogUpdates.resize(500, 400) 17 | self.verticalLayout = QVBoxLayout(DialogUpdates) 18 | self.verticalLayout.setObjectName(u"verticalLayout") 19 | self.labelUpdates = QLabel(DialogUpdates) 20 | self.labelUpdates.setWordWrap(True) 21 | self.labelUpdates.setOpenExternalLinks(True) 22 | self.labelUpdates.setObjectName(u"labelUpdates") 23 | self.verticalLayout.addWidget(self.labelUpdates) 24 | self.textBrowser = QTextBrowser(DialogUpdates) 25 | self.textBrowser.setObjectName(u"textBrowser") 26 | self.verticalLayout.addWidget(self.textBrowser) 27 | self.horizontalLayout = QHBoxLayout() 28 | self.horizontalLayout.setObjectName(u"horizontalLayout") 29 | self.update = QPushButton(DialogUpdates) 30 | self.update.setObjectName(u"update") 31 | self.horizontalLayout.addWidget(self.update, 0, Qt.AlignCenter) 32 | self.verticalLayout.addLayout(self.horizontalLayout) 33 | 34 | self.retranslateUi(DialogUpdates) 35 | QMetaObject.connectSlotsByName(DialogUpdates) 36 | 37 | def retranslateUi(self, DialogUpdates): 38 | DialogUpdates.setWindowTitle(_translate("DialogUpdates", "FastWQ - Updater", None)) 39 | self.labelUpdates.setText(_translate( 40 | "DialogUpdates", 41 | "\ 42 |

A new version of {0} is available for download!

\ 43 |

Do you want to update {1}to version {2}?

\ 44 |

Changes from your version are listed below:

\ 45 | ", 46 | None 47 | )) 48 | self.update.setText(_translate("DialogUpdates", "Update", None)) 49 | -------------------------------------------------------------------------------- /addons/fastwq/libs/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdict import IndexBuilder as MdxBuilder 2 | from .pystardict import Dictionary as StardictBuilder 3 | -------------------------------------------------------------------------------- /addons/fastwq/libs/mdict/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdict_query import IndexBuilder 2 | -------------------------------------------------------------------------------- /addons/fastwq/libs/mdict/ripemd128.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright by https://github.com/zhansliu/writemdict 3 | 4 | ripemd128.py - A simple ripemd128 library in pure Python. 5 | 6 | Supports both Python 2 (versions >= 2.6) and Python 3. 7 | 8 | Usage: 9 | from ripemd128 import ripemd128 10 | digest = ripemd128(b"The quick brown fox jumps over the lazy dog") 11 | assert(digest == b"\x3f\xa9\xb5\x7f\x05\x3c\x05\x3f\xbe\x27\x35\xb2\x38\x0d\xb5\x96") 12 | 13 | """ 14 | 15 | 16 | 17 | import struct 18 | 19 | 20 | # follows this description: http://homes.esat.kuleuven.be/~bosselae/ripemd/rmd128.txt 21 | 22 | def f(j, x, y, z): 23 | assert(0 <= j and j < 64) 24 | if j < 16: 25 | return x ^ y ^ z 26 | elif j < 32: 27 | return (x & y) | (z & ~x) 28 | elif j < 48: 29 | return (x | (0xffffffff & ~y)) ^ z 30 | else: 31 | return (x & z) | (y & ~z) 32 | 33 | def K(j): 34 | assert(0 <= j and j < 64) 35 | if j < 16: 36 | return 0x00000000 37 | elif j < 32: 38 | return 0x5a827999 39 | elif j < 48: 40 | return 0x6ed9eba1 41 | else: 42 | return 0x8f1bbcdc 43 | 44 | def Kp(j): 45 | assert(0 <= j and j < 64) 46 | if j < 16: 47 | return 0x50a28be6 48 | elif j < 32: 49 | return 0x5c4dd124 50 | elif j < 48: 51 | return 0x6d703ef3 52 | else: 53 | return 0x00000000 54 | 55 | def padandsplit(message): 56 | """ 57 | returns a two-dimensional array X[i][j] of 32-bit integers, where j ranges 58 | from 0 to 16. 59 | First pads the message to length in bytes is congruent to 56 (mod 64), 60 | by first adding a byte 0x80, and then padding with 0x00 bytes until the 61 | message length is congruent to 56 (mod 64). Then adds the little-endian 62 | 64-bit representation of the original length. Finally, splits the result 63 | up into 64-byte blocks, which are further parsed as 32-bit integers. 64 | """ 65 | origlen = len(message) 66 | padlength = 64 - ((origlen - 56) % 64) #minimum padding is 1! 67 | message += b"\x80" 68 | message += b"\x00" * (padlength - 1) 69 | message += struct.pack("> (32-s)) & 0xffffffff 86 | 87 | r = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, 88 | 7, 4,13, 1,10, 6,15, 3,12, 0, 9, 5, 2,14,11, 8, 89 | 3,10,14, 4, 9,15, 8, 1, 2, 7, 0, 6,13,11, 5,12, 90 | 1, 9,11,10, 0, 8,12, 4,13, 3, 7,15,14, 5, 6, 2] 91 | rp = [ 5,14, 7, 0, 9, 2,11, 4,13, 6,15, 8, 1,10, 3,12, 92 | 6,11, 3, 7, 0,13, 5,10,14,15, 8,12, 4, 9, 1, 2, 93 | 15, 5, 1, 3, 7,14, 6, 9,11, 8,12, 2,10, 0, 4,13, 94 | 8, 6, 4, 1, 3,11,15, 0, 5,12, 2,13, 9, 7,10,14] 95 | s = [11,14,15,12, 5, 8, 7, 9,11,13,14,15, 6, 7, 9, 8, 96 | 7, 6, 8,13,11, 9, 7,15, 7,12,15, 9,11, 7,13,12, 97 | 11,13, 6, 7,14, 9,13,15,14, 8,13, 6, 5,12, 7, 5, 98 | 11,12,14,15,14,15, 9, 8, 9,14, 5, 6, 8, 6, 5,12] 99 | sp = [ 8, 9, 9,11,13,15,15, 5, 7, 7, 8,11,14,14,12, 6, 100 | 9,13,15, 7,12, 8, 9,11, 7, 7,12, 7, 6,15,13,11, 101 | 9, 7,15,11, 8, 6, 6,14,12,13, 5,14,13,13, 7, 5, 102 | 15, 5, 8,11,14,14, 6,14, 6, 9,12, 9,12, 5,15, 8] 103 | 104 | 105 | def ripemd128(message): 106 | h0 = 0x67452301 107 | h1 = 0xefcdab89 108 | h2 = 0x98badcfe 109 | h3 = 0x10325476 110 | X = padandsplit(message) 111 | for i in range(len(X)): 112 | (A,B,C,D) = (h0,h1,h2,h3) 113 | (Ap,Bp,Cp,Dp) = (h0,h1,h2,h3) 114 | for j in range(64): 115 | T = rol(s[j], add(A, f(j,B,C,D), X[i][r[j]], K(j))) 116 | (A,D,C,B) = (D,C,B,T) 117 | T = rol(sp[j], add(Ap, f(63-j,Bp,Cp,Dp), X[i][rp[j]], Kp(j))) 118 | (Ap,Dp,Cp,Bp)=(Dp,Cp,Bp,T) 119 | T = add(h1,C,Dp) 120 | h1 = add(h2,D,Ap) 121 | h2 = add(h3,A,Bp) 122 | h3 = add(h0,B,Cp) 123 | h0 = T 124 | 125 | 126 | return struct.pack(" 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from collections import defaultdict 21 | import os 22 | import shutil 23 | import unicodedata 24 | 25 | from aqt import mw 26 | from aqt.utils import showInfo, showText, tooltip 27 | 28 | from .worker import QueryWorkerManager 29 | from .common import promot_choose_css, inspect_note 30 | 31 | from ..constants import Endpoint, Template 32 | from ..context import config 33 | from ..lang import _ 34 | from ..gui import ProgressWindow 35 | from ..service import service_manager, service_pool, QueryResult, copy_static_file 36 | from ..service.base import LocalService 37 | from ..utils import Empty, MapDict, Queue, wrap_css 38 | 39 | 40 | __all__ = ['query_from_browser', 'query_from_editor_fields'] 41 | 42 | 43 | def query_from_browser(browser): 44 | """ 45 | Query word from Browser 46 | """ 47 | 48 | if not browser: 49 | return 50 | 51 | notes = [browser.mw.col.getNote(note_id) 52 | for note_id in browser.selectedNotes()] 53 | 54 | if len(notes) == 1: 55 | query_from_editor_fields(browser.editor) 56 | else: 57 | query_all(notes) 58 | # browser.model.reset() 59 | 60 | 61 | def query_from_editor_fields(editor, fields=None): 62 | """ 63 | Query word fileds from Editor 64 | """ 65 | 66 | if not editor or not editor.note: 67 | return 68 | 69 | word_ord, word, maps = inspect_note(editor.note) 70 | flush = not editor.addMode 71 | nomaps = True 72 | for each in maps: 73 | dict_unique = each.get('dict_unique', '').strip() 74 | ignore = each.get('ignore', True) 75 | if dict_unique and not ignore: 76 | nomaps = False 77 | break 78 | if nomaps: 79 | from ..gui import show_options 80 | tooltip(_('PLS_SET_DICTIONARY_FIELDS')) 81 | show_options( 82 | editor.parentWindow, 83 | editor.note.model()['id'], 84 | query_from_editor_fields, 85 | editor, 86 | fields 87 | ) 88 | else: 89 | #editor.setNote(editor.note) 90 | query_all([editor.note], flush, fields) 91 | editor.setNote(editor.note, focus=True) 92 | editor.saveNow() 93 | 94 | 95 | def query_all(notes, flush=True, fields=None): 96 | """ 97 | Query maps word fileds 98 | """ 99 | 100 | if len(notes) == 0: 101 | return 102 | 103 | work_manager = QueryWorkerManager() 104 | #work_manager.reset() 105 | #progress.start(max=len(notes), min=0, immediate=True) 106 | work_manager.flush = flush 107 | work_manager.query_fields = fields 108 | queue = work_manager.queue 109 | 110 | for note in notes: 111 | queue.put(note) 112 | 113 | work_manager.start() 114 | work_manager.join() 115 | 116 | #progress.finish() 117 | promot_choose_css(work_manager.missed_css) 118 | tooltip(u'{0} {1} {2}, {3} {4}'.format(_('UPDATED'), work_manager.counter, _( 119 | 'CARDS'), work_manager.fields, _('FIELDS'))) 120 | #work_manager.clean() 121 | service_pool.clean() 122 | -------------------------------------------------------------------------------- /addons/fastwq/res/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons/fastwq/res/add.png -------------------------------------------------------------------------------- /addons/fastwq/res/null.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons/fastwq/res/null.png -------------------------------------------------------------------------------- /addons/fastwq/res/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons/fastwq/res/ok.png -------------------------------------------------------------------------------- /addons/fastwq/res/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons/fastwq/res/setting.png -------------------------------------------------------------------------------- /addons/fastwq/res/wqicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons/fastwq/res/wqicon.png -------------------------------------------------------------------------------- /addons/fastwq/service/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from .manager import ServiceManager 21 | from .pool import ServicePool 22 | from .base import QueryResult, copy_static_file 23 | 24 | service_manager = ServiceManager() # Service Manager 25 | service_pool = ServicePool(service_manager) # Service Instance Pool Manager -------------------------------------------------------------------------------- /addons/fastwq/service/dict/LDOCE5.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | import re 4 | import random 5 | from ..base import * 6 | # from BeautifulSoup import BeautifulSoup 7 | # from bs4 import BeautifulSoup 8 | 9 | 10 | VOICE_PATTERN = r'href="sound:\/\/([\w\/]+%s\/\w*\.mp3)"' 11 | VOICE_PATTERN_WQ = r'(.*?)' 12 | MAPPINGS = [ 13 | ['br', [re.compile(VOICE_PATTERN % r'breProns')]], 14 | ['us', [re.compile(VOICE_PATTERN % r'ameProns')]] 15 | ] 16 | LANG_TO_REGEXPS = {lang: regexps for lang, regexps in MAPPINGS} 17 | DICT_PATH = u'/Users/brian/Documents/LDOCE5++ V 2-15/LDOCE5++ V 2-15.mdx' # u'E:\\BaiduYunDownload\\mdx\\L6mp3.mdx' 18 | 19 | 20 | @register([u'本地词典-LDOCE5++', u'MDX-LDOCE5++']) 21 | class Ldoce5(MdxService): 22 | 23 | def __init__(self): 24 | dict_path = DICT_PATH 25 | # if DICT_PATH is a path, stop auto detect 26 | if not dict_path: 27 | from ...service import service_manager, service_pool 28 | for clazz in service_manager.mdx_services: 29 | service = service_pool.get(clazz.__unique__) 30 | title = service.builder._title if service and service.support else u'' 31 | service_pool.put(service) 32 | if title.startswith(u'LDOCE5'): 33 | dict_path = service.dict_path 34 | break 35 | super(Ldoce5, self).__init__(dict_path) 36 | 37 | @property 38 | def title(self): 39 | return getattr(self, '__register_label__', self.unique) 40 | 41 | def _fld_voice(self, html, voice): 42 | """获取发音字段""" 43 | for regexp in LANG_TO_REGEXPS[voice]: 44 | match = regexp.search(html) 45 | if match: 46 | val = '/' + match.group(1) 47 | name = get_hex_name('mdx-'+self.unique.lower(), val, 'mp3') 48 | name = self.save_file(val, name) 49 | if name: 50 | return self.get_anki_label(name, 'audio') 51 | return '' 52 | 53 | @export('BRE_PRON') 54 | def fld_voicebre(self): 55 | return self._fld_voice(self.get_html(), 'br') 56 | 57 | @export('AME_PRON') 58 | def fld_voiceame(self): 59 | return self._fld_voice(self.get_html(), 'us') 60 | 61 | @export('All examples with audios') 62 | def fld_sentence_audio(self): 63 | return self._range_sentence_audio([i for i in range(0, 100)]) 64 | 65 | @export('Random example with audio') 66 | def fld_random_sentence_audio(self): 67 | return self._range_sentence_audio() 68 | 69 | @export('First example with audio') 70 | def fld_first1_sentence_audio(self): 71 | return self._range_sentence_audio([0]) 72 | 73 | @export('First 2 examples with audios') 74 | def fld_first2_sentence_audio(self): 75 | return self._range_sentence_audio([0, 1]) 76 | 77 | def _fld_audio(self, audio): 78 | name = get_hex_name('mdx-'+self.unique.lower(), audio, 'mp3') 79 | name = self.save_file(audio, name) 80 | if name: 81 | return self.get_anki_label(name, 'audio') 82 | return '' 83 | 84 | def _range_sentence_audio(self, range_arr=None): 85 | m = re.findall(r'
\s*.*>\s*.*<\/div>', self.get_html()) 86 | if m: 87 | soup = parse_html(m[0]) 88 | el_list = soup.findAll('div', {'class':'EXAMPLE'}) 89 | if el_list: 90 | maps = [] 91 | for element in el_list: 92 | i_str = '' 93 | for content in element.contents: 94 | i_str = i_str + str(content).decode('utf-8') 95 | sound = re.search(r']+?href=\"sound\:\/(.*?\.mp3)\".*<\/a>', i_str) 96 | if sound: 97 | maps.append([sound, i_str]) 98 | my_str = '' 99 | range_arr = range_arr if range_arr else [random.randrange(0, len(maps) - 1, 1)] 100 | for i, e in enumerate(maps): 101 | if i in range_arr: 102 | i_str = e[1] 103 | sound = e[0] 104 | mp3 = self._fld_audio(sound.groups()[0]) 105 | i_str = re.sub(r']+?href=\"sound\:\/.*?\.mp3\".*<\/a>', '', i_str).strip() 106 | # remove chinese text 107 | i_str = re.sub(r'(
\s*\S*<\/div>)<\/span>', '', i_str).strip() 108 | my_str = my_str + mp3 + ' ' + i_str + '
' 109 | return my_str 110 | return '' 111 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons/fastwq/service/dict/__init__.py -------------------------------------------------------------------------------- /addons/fastwq/service/dict/baicizhan.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import json 3 | import os 4 | from collections import defaultdict 5 | from ..base import * 6 | 7 | 8 | @register([u'百词斩', u'Baicizhan']) 9 | class Baicizhan(WebService): 10 | 11 | bcz_download_mp3 = True 12 | bcz_download_img = True 13 | 14 | def __init__(self): 15 | super(Baicizhan, self).__init__() 16 | 17 | def _get_from_api(self): 18 | url = u"http://mall.baicizhan.com/ws/search?w={}".format(self.quote_word) 19 | result = { 20 | "accent": u"", 21 | "img": u"", 22 | "mean_cn": u"", 23 | "st": u"", 24 | "sttr": u"", 25 | "tv": u"", 26 | "word": u"", 27 | "df": u'', 28 | } 29 | try: 30 | html = self.get_response(url, timeout=5)#urllib2.urlopen(url, timeout=5).read() 31 | result.update(json.loads(html)) 32 | except: 33 | pass 34 | return self.cache_this(result) 35 | 36 | @export('PRON') 37 | def fld_phonetic(self): 38 | url = u'http://baicizhan.qiniucdn.com/word_audios/{}.mp3'.format(self.quote_word) 39 | audio_name = get_hex_name(self.unique.lower(), url, 'mp3') 40 | if self.bcz_download_mp3: 41 | if os.path.exists(audio_name) or self.download(url, audio_name, 5): 42 | with open(audio_name, 'rb') as f: 43 | if f.read().strip() == '{"error":"Document not found"}': 44 | res = '' 45 | else: 46 | res = self.get_anki_label(audio_name, 'audio') 47 | if not res: 48 | os.remove(audio_name) 49 | else: 50 | res = '' 51 | return res 52 | else: 53 | return url 54 | 55 | @export('PHON') 56 | def fld_phon(self): 57 | return self._get_field('accent') 58 | 59 | @export('IMAGE') 60 | def fld_img(self): 61 | url = self._get_field('img') 62 | if url and self.bcz_download_img: 63 | filename = url[url.rindex('/') + 1:] 64 | if os.path.exists(filename) or self.download(url, filename): 65 | return self.get_anki_label(filename, 'img') 66 | #return self.get_anki_label(url, 'img') 67 | return '' 68 | 69 | @export([u'象形', u'Pictogram']) 70 | def fld_df(self): 71 | url = self._get_field('df') 72 | if url and self.bcz_download_img: 73 | filename = url[url.rindex('/') + 1:] 74 | if os.path.exists(filename) or self.download(url, filename): 75 | return self.get_anki_label(filename, 'img') 76 | #return self.get_anki_label(url, 'img') 77 | return '' 78 | 79 | @export(u'DEF') 80 | def fld_mean(self): 81 | return self._get_field('mean_cn') 82 | 83 | @export(u'EXAMPLE') 84 | def fld_st(self): 85 | return self._get_field('st') 86 | 87 | @export('TRANS') 88 | def fld_sttr(self): 89 | return self._get_field('sttr') 90 | 91 | @export([u'单词tv', u'TV']) 92 | def fld_tv_url(self): 93 | video = self._get_field('tv') 94 | if video: 95 | return self.get_anki_label(video, 'video') 96 | return '' 97 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/bing.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | import os 4 | from ..base import * 5 | 6 | bing_download_mp3 = True 7 | 8 | @register([u'Bing', u'Bing']) 9 | class Bing(WebService): 10 | 11 | def __init__(self): 12 | super(Bing, self).__init__() 13 | 14 | def _get_from_api(self): 15 | data = self.get_response(u"http://cn.bing.com/dict/search?q={}&mkt=zh-cn".format(self.quote_word)) 16 | soup = parse_html(data) 17 | result = { 18 | 'pronunciation': {'AmE': '', 'BrE': '', 'AmEmp3': '', 'BrEmp3': ''}, 19 | 'def': [], 20 | 'sams': [], 21 | } 22 | 23 | #音 24 | element = soup.find('div', class_='hd_tf_lh') 25 | if element: 26 | audios = element.find_all('a') 27 | #美式英标 28 | tag = element.find('div', class_='hd_pr') 29 | if tag: 30 | result['pronunciation']['AmE'] = str(tag).decode('utf-8') 31 | #美音 32 | if audios: 33 | tag = audios[0] 34 | audio_url = tag.get('onclick') 35 | if audio_url: 36 | result['pronunciation']['AmEmp3'] = u''.join(re.findall(r'https://.*\.mp3', audio_url)) 37 | 38 | #英式音标 39 | tag = element.find('div', class_='hd_prUS') 40 | if tag: 41 | result['pronunciation']['BrE'] = str(tag).decode('utf-8') 42 | #英音 43 | if audios: 44 | tag = audios[1] 45 | audio_url = tag.get('onclick') 46 | if audio_url: 47 | result['pronunciation']['BrEmp3'] = u''.join(re.findall(r'https://.*\.mp3', audio_url)) 48 | 49 | #释义 50 | element = soup.find('div', class_='qdef') 51 | if element: 52 | element = getattr(element, 'ul', '') 53 | if element: 54 | result['def'] = u''.join([str(content) for content in element.contents]) 55 | 56 | #例句 57 | element = soup.find('div', id='sentenceSeg') 58 | if element: 59 | #英文例句 60 | tags = element.find_all('div', {"class": 'sen_en'}) 61 | result['sams'] = [{'eng': u''.join(tag.find_all(text=True))} for tag in tags] 62 | #例句翻译 63 | tags = element.find_all('div', {"class": 'sen_cn'}) 64 | for i, tag in enumerate(tags): 65 | result['sams'][i]['chn'] = u''.join(tag.find_all(text=True)) 66 | 67 | return self.cache_this(result) 68 | 69 | @with_styles(css='.pos{font-weight:bold;margin-right:4px;}', need_wrap_css=True, wrap_class='bing') 70 | def _css(self, val): 71 | return val 72 | 73 | @export('AME_PHON') 74 | def fld_phonetic_us(self): 75 | seg = self._get_field('pronunciation') 76 | return seg.get('AmE', u'') if seg else u'' 77 | 78 | @export('BRE_PHON') 79 | def fld_phonetic_uk(self): 80 | seg = self._get_field('pronunciation') 81 | return seg.get('BrE', u'') if seg else u'' 82 | 83 | def _fld_mp3(self, fld): 84 | seg = self._get_field('pronunciation') 85 | audio_url = seg[fld] if seg else u'' 86 | if bing_download_mp3 and audio_url: 87 | filename = get_hex_name('bing', audio_url, 'mp3') 88 | if os.path.exists(filename) or self.net_download(filename, audio_url): 89 | return self.get_anki_label(filename, 'audio') 90 | return '' 91 | 92 | @export('AME_PRON') 93 | def fld_mp3_us(self): 94 | return self._fld_mp3('AmEmp3') 95 | 96 | @export('BRE_PRON') 97 | def fld_mp3_uk(self): 98 | return self._fld_mp3('BrEmp3') 99 | 100 | @export('DEF') 101 | def fld_definition(self): 102 | val = self._get_field('def') 103 | if val == None or val == '': 104 | return '' 105 | return self._css(val) 106 | 107 | @export('EXAMPLE') 108 | def fld_samples(self): 109 | max_numbers = 10 110 | segs = self._get_field('sams') 111 | if segs: 112 | sentences = u'' 113 | for i, seg in enumerate(segs): 114 | sentences += u"""
  • 115 |
    {0}.{1}
    116 |
    {2}
    117 |
  • """.format(i+1, seg['eng'], seg['chn']) 118 | if i == 9: 119 | break 120 | if sentences: 121 | return u"""
    122 |
    123 |
      {0}
    124 |
    125 |
    """.format(sentences) 126 | return u'' 127 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/bing3tp.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import json 3 | import re 4 | import os 5 | from ..base import * 6 | 7 | bing_download_mp3 = True 8 | 9 | @register([u'Bing xtk', u'Bing xtk']) 10 | class BingXtk(WebService): 11 | 12 | def __init__(self): 13 | super(BingXtk, self).__init__() 14 | 15 | def _get_from_api(self): 16 | result = { 17 | 'pronunciation': {'AmE': '', 'BrE': '', 'AmEmp3': '', 'BrEmp3': ''}, 18 | 'def': [], 19 | 'sams': [], 20 | } 21 | headers = { 22 | 'Accept-Language': 'en-US,zh-CN;q=0.8,zh;q=0.6,en;q=0.4', 23 | 'User-Agent': 'WordQuery Addon (Anki)', 24 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'} 25 | url = u'http://xtk.azurewebsites.net/BingDictService.aspx?Word={}'.format(self.quote_word) 26 | try: 27 | result.update(json.loads(self.get_response(url, headers=headers, timeout=10))) 28 | except: 29 | pass 30 | return self.cache_this(result) 31 | 32 | @export('AME_PHON') 33 | def fld_phonetic_us(self): 34 | seg = self._get_field('pronunciation') 35 | phon = seg.get('AmE', u'') if seg else u'' 36 | if phon and phon[0:1] not in '/[': 37 | return u'/{}/'.format(phon) 38 | return u'' 39 | 40 | @export('BRE_PHON') 41 | def fld_phonetic_uk(self): 42 | seg = self._get_field('pronunciation') 43 | phon = seg.get('BrE', u'') if seg else u'' 44 | if phon and phon[0:1] not in '/[': 45 | return u'/{}/'.format(phon) 46 | return u'' 47 | 48 | def _fld_mp3(self, fld): 49 | seg = self._get_field('pronunciation') 50 | audio_url = seg[fld] if seg else u'' 51 | if bing_download_mp3 and audio_url: 52 | filename = get_hex_name('bing', audio_url, 'mp3') 53 | if os.path.exists(filename) or self.net_download(filename, audio_url): 54 | return self.get_anki_label(filename, 'audio') 55 | return '' 56 | 57 | @export('AME_PRON') 58 | def fld_mp3_us(self): 59 | return self._fld_mp3('AmEmp3') 60 | 61 | @export('BRE_PRON') 62 | def fld_mp3_uk(self): 63 | return self._fld_mp3('BrEmp3') 64 | 65 | @with_styles(css='.pos{font-weight:bold;margin-right:4px;}', need_wrap_css=True, wrap_class='bing') 66 | def _css(self, val): 67 | return val 68 | 69 | @export('DEF') 70 | def fld_definition(self): 71 | segs = self._get_field('defs') 72 | if isinstance(segs, list) and len(segs) > 0: 73 | val = u'
    '.join([u'''{0} 74 | {1}'''.format(seg['pos'], seg['def']) for seg in segs]) 75 | return self._css(val) 76 | return '' 77 | 78 | @export('EXAMPLE') 79 | def fld_samples(self): 80 | max_numbers = 10 81 | segs = self._get_field('sams') 82 | if segs: 83 | sentences = u'' 84 | for i, seg in enumerate(segs): 85 | sentences += u"""
  • 86 |
    {0}
    87 |
    {1}
    88 |
  • """.format(seg['eng'], seg['chn']) 89 | if i == 9: 90 | break 91 | if sentences: 92 | return u"""
    93 |
    94 |
      {0}
    95 |
    96 |
    """.format(sentences) 97 | return u'' 98 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/bingimg.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | import json 4 | from ..base import * 5 | 6 | 7 | @register([u'必应图片', u'Bing-Images']) 8 | class Bing_Images(WebService): 9 | 10 | bing_download_img = True 11 | 12 | def __init__(self): 13 | super(Bing_Images, self).__init__() 14 | 15 | def _get_from_api(self): 16 | url = u"http://cn.bing.com/images/search?q={}".format(self.quote_word) 17 | html = self.get_response(url, timeout=10) 18 | soup = parse_html(html) 19 | result = { 20 | 'img': '', 21 | } 22 | 23 | #图片连接 24 | tag = soup.find('a', class_='iusc') 25 | if tag: 26 | try: 27 | data = json.loads(tag.get('m')) 28 | result['img'] = data.get('turl', u'') 29 | except: 30 | pass 31 | 32 | return self.cache_this(result) 33 | 34 | @export([u'图片', u'Image']) 35 | def fld_pinyin(self): 36 | url = self._get_field('img') 37 | if url and self.bing_download_img: 38 | filename = get_hex_name(self.unique.lower(), url, 'jpg') 39 | if os.path.exists(filename) or self.download(url, filename): 40 | return self.get_anki_label(filename, 'img') 41 | return '' 42 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/cambridge_cs.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from ..base import * 3 | from .cambridge import Cambridge 4 | 5 | @register([u'剑桥词典-英汉简', u'Cambridge(英汉简)']) 6 | class CambridgeCS(Cambridge): 7 | 8 | def __init__(self): 9 | super(CambridgeCS, self).__init__() 10 | 11 | def _get_url(self): 12 | return u'https://dictionary.cambridge.org/us/dictionary/english-chinese-simplified/' 13 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/cambridge_ct.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from ..base import * 3 | from .cambridge import Cambridge 4 | 5 | @register([u'剑桥词典-英汉繁', u'Cambridge(英汉繁)']) 6 | class CambridgeCT(Cambridge): 7 | 8 | def __init__(self): 9 | super(CambridgeCT, self).__init__() 10 | 11 | def _get_url(self): 12 | return u'https://dictionary.cambridge.org/us/dictionary/english-chinese-traditional/' 13 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/cambridge_ee.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from ..base import * 3 | from .cambridge import Cambridge 4 | 5 | @register([u'剑桥词典-英英', u'Cambridge(English)']) 6 | class CambridgeEE(Cambridge): 7 | 8 | def __init__(self): 9 | super(CambridgeEE, self).__init__() 10 | 11 | def _get_url(self): 12 | return u'https://dictionary.cambridge.org/dictionary/english/' 13 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/dreye.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | import os 4 | from ..base import * 5 | 6 | dreye_download_mp3 = True 7 | 8 | @register([u'译典通', u'Dr.eye']) 9 | class Dreye(WebService): 10 | 11 | def __init__(self): 12 | super(Dreye, self).__init__() 13 | 14 | def _get_from_api(self): 15 | data = self.get_response(u"https://yun.dreye.com/dict_new/dict.php?w={}&hidden_codepage=01".format(self.quote_word)) 16 | soup = parse_html(data) 17 | result = { 18 | 'phon': '', 19 | 'pron': '', 20 | 'pos': '', 21 | 'def': '' 22 | } 23 | 24 | #音标 25 | element = soup.find('span', class_='phonetic') 26 | if element: 27 | result['phon'] = element.get_text() 28 | 29 | # 发音 30 | mp3_regexp = re.compile(r'var *RealSoundPath += +"(.*)";') 31 | mp3_match = mp3_regexp.search(data.decode('utf-8')) 32 | if mp3_match: 33 | result['pron'] = u'{}'.format(mp3_match.group(1)) 34 | #动变 35 | element = soup.find('div', id='digest') 36 | if element: 37 | result['pos'] = u'{}'.format(str(element)) 38 | #释义 39 | element = soup.find('div', id='usual') 40 | if element: 41 | result['def'] = u'{}'.format(str(element)) 42 | 43 | return self.cache_this(result) 44 | 45 | @with_styles(need_wrap_css=True, cssfile='_dreye.css') 46 | def _css(self, val): 47 | return val 48 | 49 | @export('PHON') 50 | def fld_phonetic_us(self): 51 | return self._get_field('phon') 52 | 53 | @export('PRON') 54 | def fld_mp3(self): 55 | audio_url = self._get_field('pron') 56 | if dreye_download_mp3 and audio_url: 57 | filename = get_hex_name('dreye', audio_url, 'mp3') 58 | if os.path.exists(filename) or self.net_download(filename, audio_url): 59 | return self.get_anki_label(filename, 'audio') 60 | return '' 61 | 62 | @export([u'摘要', u'Digest']) 63 | def fld_pos(self): 64 | val = self._get_field('pos') 65 | if val == None or val == '': 66 | return '' 67 | return self._css(val) 68 | 69 | @export('DEF') 70 | def fld_definition(self): 71 | val = self._get_field('def') 72 | if val == None or val == '': 73 | return '' 74 | return self._css(val) 75 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/esdict.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import base64 4 | import re 5 | import urllib2 6 | import os 7 | from ..base import * 8 | 9 | css = '' 10 | 11 | @register([u'西语助手', u'esdict']) 12 | class Esdict(WebService): 13 | 14 | def __init__(self): 15 | super(Esdict, self).__init__() 16 | 17 | def _get_from_api(self): 18 | url = 'https://www.esdict.cn/mdicts/es/{}'.format(self.quote_word) 19 | try: 20 | result = {} 21 | html = self.get_response(url, timeout=5) 22 | soup = parse_html(html) 23 | 24 | def _get_from_element(dict, key, soup, tag, id=None, class_=None): 25 | baseURL = 'https://www.esdict.cn/' 26 | # element = soup.find(tag, id=id, class_=class_) # bs4 27 | if id: 28 | element = soup.find(tag, {"id": id}) 29 | if class_: 30 | element = soup.find(tag, {"class": class_}) 31 | if element: 32 | dict[key] = str(element) 33 | dict[key] = re.sub( 34 | r'href="/', 'href="' + baseURL, dict[key]) 35 | dict[key] = re.sub(r'声明:.*。', '', dict[key]) 36 | dict[key] = dict[key].decode('utf-8') 37 | return dict 38 | 39 | # '[bɔ̃ʒur]' 40 | result = _get_from_element( 41 | result, 'phonitic', soup, 'span', class_='Phonitic') 42 | # '
    ' 43 | result = _get_from_element( 44 | result, 'fccf', soup, 'div', id='FCChild') # 西汉-汉西词典 45 | result = _get_from_element( 46 | result, 'example', soup, 'div', id='LJChild') # 西语例句库 47 | result = _get_from_element( 48 | result, 'syn', soup, 'div', id='SYNChild') # 近义、反义、派生词典 49 | result = _get_from_element( 50 | result, 'ff', soup, 'div', id='FFChild') # 西西词典 51 | result = _get_from_element( 52 | result, 'fe', soup, 'div', id='FEChild') # 西英词典 53 | 54 | return self.cache_this(result) 55 | except Exception as e: 56 | return {} 57 | 58 | @export([u'真人发音', u'Real person pronounciation']) 59 | def fld_sound(self): 60 | url = 'https://api.frdic.com/api/v2/speech/speakweb?langid=es&txt=QYN{word}'.format( 61 | word=urllib2.quote(base64.b64encode(self.word.encode('utf-8'))) 62 | ) 63 | filename = get_hex_name(self.unique.lower(), url, 'mp3') 64 | if os.path.exists(filename) or self.net_download(filename, url): 65 | return self.get_anki_label(filename, 'audio') 66 | return '' 67 | 68 | @export('PHON') 69 | def fld_phonetic(self): 70 | return self._get_field('phonitic') 71 | 72 | @export([u'西汉-汉西词典', u'spanish-chinese/chinese-spanish dictionary']) 73 | @with_styles(css=css) 74 | def fld_fccf(self): 75 | return self._get_field('fccf') 76 | 77 | @export([u'西语例句库', u'Spanish examples']) 78 | @with_styles(css=css) 79 | def fld_example(self): 80 | return self._get_field('example') 81 | 82 | @export([u'近义、反义、派生词典', u'Synonyms, antonyms, derivatives']) 83 | def fld_syn(self): 84 | return self._get_field('syn') 85 | 86 | @export([u'西西词典', u'Spanish-spanish dictionary']) 87 | @with_styles(css=css) 88 | def fld_ff(self): 89 | return self._get_field('ff') 90 | 91 | @export([u'西英词典', u'Spanish-english dictionary']) 92 | @with_styles(css=css) 93 | def fld_fe(self): 94 | return self._get_field('fe') 95 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/frdic.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import base64 3 | import re 4 | import urllib2 5 | import os 6 | from ..base import * 7 | 8 | 9 | css = '' 10 | 11 | @register([u'法语助手', u'frdic']) 12 | class Frdic(WebService): 13 | 14 | def __init__(self): 15 | super(Frdic, self).__init__() 16 | 17 | def _get_from_api(self): 18 | url = 'http://www.frdic.com/dicts/fr/{}'.format(self.quote_word) 19 | try: 20 | result = {} 21 | html = self.get_response(url, timeout=5) 22 | soup = parse_html(html) 23 | 24 | def _get_from_element(dict, key, soup, tag, id=None, class_=None): 25 | baseURL = 'http://www.frdic.com/' 26 | # element = soup.find(tag, id=id, class_=class_) # bs4 27 | if id: 28 | element = soup.find(tag, {"id": id}) 29 | if class_: 30 | element = soup.find(tag, {"class": class_}) 31 | if element: 32 | dict[key] = str(element) 33 | dict[key] = re.sub( 34 | r'href="/', 'href="' + baseURL, dict[key]) 35 | dict[key] = re.sub(r'声明:.*。', '', dict[key]) 36 | dict[key] = dict[key].decode('utf-8') 37 | return dict 38 | 39 | # '[bɔ̃ʒur]' 40 | result = _get_from_element( 41 | result, 'phonitic', soup, 'span', class_='Phonitic') 42 | # '
    ' 43 | result = _get_from_element( 44 | result, 'fccf', soup, 'div', id='ExpFCChild') # 法汉-汉法词典 45 | result = _get_from_element( 46 | result, 'example', soup, 'div', id='TingLijuChild') # 法语例句库 47 | result = _get_from_element( 48 | result, 'syn', soup, 'div', id='SYNChild') # 近义、反义、派生词典 49 | result = _get_from_element( 50 | result, 'ff', soup, 'div', id='FFChild') # 法法词典 51 | result = _get_from_element( 52 | result, 'fe', soup, 'div', id='FEChild') # 法英词典 53 | 54 | return self.cache_this(result) 55 | except Exception as e: 56 | return {} 57 | 58 | @export([u'真人发音', u'Real person pronunciation']) 59 | def fld_sound(self): 60 | url = 'https://api.frdic.com/api/v2/speech/speakweb?langid=fr&txt=QYN{word}'.format( 61 | word=urllib2.quote(base64.b64encode(self.word.encode('utf-8'))) 62 | ) 63 | filename = get_hex_name(self.unique.lower(), url, 'mp3') 64 | if os.path.exists(filename) or self.net_download(filename, url): 65 | return self.get_anki_label(filename, 'audio') 66 | return '' 67 | 68 | @export('PHON') 69 | def fld_phonetic(self): 70 | return self._get_field('phonitic') 71 | 72 | @export([u'法汉-汉法词典', u'French-chinese/chinese-french dictionary']) 73 | def fld_fccf(self): 74 | return self._get_field('fccf') 75 | 76 | @export([u'法语例句库', u'French examples']) 77 | @with_styles(css=css) 78 | def fld_example(self): 79 | return self._get_field('example') 80 | 81 | @export([u'近义、反义、派生词典', u'Synonyms, antonyms, derivative']) 82 | @with_styles(css=css) 83 | def fld_syn(self): 84 | return self._get_field('syn') 85 | 86 | @export([u'法法词典', u'French-french dictionary']) 87 | @with_styles(css=css) 88 | def fld_ff(self): 89 | return self._get_field('ff') 90 | 91 | @export([u'法英词典', u'French-english dictionary']) 92 | @with_styles(css=css) 93 | def fld_fe(self): 94 | return self._get_field('fe') 95 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/ludwig.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import random 3 | from ..base import * 4 | 5 | oxford_download_mp3 = True 6 | 7 | @register(u'Ludwig') 8 | class Ludwig(WebService): 9 | 10 | def __init__(self): 11 | super(Ludwig, self).__init__() 12 | 13 | def _get_from_api(self): 14 | data = self.get_response(u'https://ludwig.guru/s/{}'.format(self.quote_word)) 15 | soup = parse_html(data) 16 | result = { 17 | 'def': u'', 18 | 'examples': [] 19 | } 20 | 21 | # def 22 | element = soup.find('div', class_='-id-__definition--1E88I') 23 | if element: 24 | e_list = element.find_all('p') 25 | if e_list: 26 | result['def'] = u''.join(str(c).decode('utf-8') for c in e_list) 27 | 28 | # examples 29 | e_list = soup.find_all('p', class_='-id-__exact--SVDfq') 30 | if e_list: 31 | e_arr = [] 32 | for n in e_list: 33 | e_arr.append(str(n.get_text()).decode('utf-8')) 34 | result['examples'] = e_arr 35 | return self.cache_this(result) 36 | 37 | @export('DEF') 38 | def fld_definate(self): 39 | return self._get_field('def') 40 | 41 | @export('EXAMPLE') 42 | def fld_example(self): 43 | return self._range_examples([i for i in range(0, 100)]) 44 | 45 | @export([u'随机例句', u'Random example']) 46 | def fld_random_example(self): 47 | return self._range_examples() 48 | 49 | @export([u'首2个例句', u'First 2 examples']) 50 | def fld_first2_example(self): 51 | return self._range_examples([0, 1]) 52 | 53 | def _range_examples(self, range_arr=None): 54 | maps = self._get_field('examples') 55 | if maps: 56 | range_arr = range_arr if range_arr else [random.randrange(0, len(maps) - 1, 1)] 57 | my_str = u'' 58 | for i,n in enumerate(maps): 59 | if i in range_arr: 60 | my_str += u'
  • {}
  • '.format(n) 61 | return u'
      {}
    '.format(my_str) 62 | return u'' 63 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/minidict.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from ..base import * 4 | 5 | 6 | @register([u'海词迷你词典', u'cidian.dict']) 7 | class MiniDict(WebService): 8 | 9 | def __init__(self): 10 | super(MiniDict, self).__init__() 11 | 12 | def _get_from_api(self): 13 | data = self.get_response(u"http://apii.dict.cn/mini.php?q={}".format(self.quote_word)) 14 | soup = parse_html(data) 15 | result = { 16 | 'expressions': u'', 17 | 'sentences': u'', 18 | 'variations': u'', 19 | 'phonetic': u'', 20 | } 21 | 22 | # 音标 23 | tag = soup.find('span', class_='p') 24 | if tag: 25 | result['phonetic'] = str(tag.get_text()).encode('utf-8') 26 | tag.decompose() 27 | 28 | # 基本释义 29 | tag = soup.find('div', id='e') 30 | if tag: 31 | result['expressions'] = str(tag).encode('utf-8') 32 | tag.decompose() 33 | 34 | # 例句与用法 35 | tag = soup.find('div', id='s') 36 | if tag: 37 | result['sentences'] = str(tag).encode('utf-8') 38 | tag.decompose() 39 | 40 | # 词形变化 41 | tag = soup.find('div', id='t') 42 | if tag: 43 | result['variations'] = str(tag).encode('utf-8') 44 | tag.decompose() 45 | 46 | return self.cache_this(result) 47 | 48 | @export('PHON') 49 | def fld_phonetic(self): 50 | return self._get_field('phonetic') 51 | 52 | @export([u'基本释义', u'Expressions']) 53 | def fld_explains(self): 54 | return self._get_field('expressions') 55 | 56 | @export([u'例句与用法', u'Example and pattern']) 57 | @with_styles(css='em {color:#cc0066;font-style:normal;}', need_wrap_css=True, wrap_class='minidict') 58 | def fld_sentences(self): 59 | return self._get_field('sentences') 60 | 61 | @export([u'词形变化', u'Inflections']) 62 | def fld_variations(self): 63 | return self._get_field('variations') 64 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/oxford.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | import urllib2 4 | import json 5 | from ..base import * 6 | 7 | oxford_download_mp3 = True 8 | 9 | @register(u'Oxford') 10 | class Oxford(WebService): 11 | 12 | def __init__(self): 13 | super(Oxford, self).__init__() 14 | 15 | def _get_from_api(self, lang='en'): 16 | app_id = '45aecf84' 17 | app_key = 'bb36fd6a1259e5baf8df6110a2f7fc8f' 18 | headers = {'app_id': app_id, 'app_key': app_key} 19 | word_id = urllib2.quote(self.word.lower().replace(' ', '_')) 20 | url = u'https://od-api.oxforddictionaries.com/api/v1/entries/' + lang + u'/' + word_id 21 | result = {'lexicalEntries': ''} 22 | try: 23 | result.update(json.loads(self.get_response(url, headers=headers, timeout=10))['results'][0]) 24 | except: 25 | pass 26 | return self.cache_this(result) 27 | 28 | @export('DEF') 29 | def fld_definition(self): 30 | try: 31 | return self._get_field('lexicalEntries')[0]['entries'][0]['senses'][0]['definitions'][0] 32 | except: 33 | return '' 34 | 35 | def _fld_pron_mp3(self, audio_url): 36 | if oxford_download_mp3 and audio_url: 37 | filename = get_hex_name(self.unique.lower(), audio_url, 'mp3') 38 | if os.path.exists(filename) or self.net_download(filename, audio_url): 39 | return self.get_anki_label(filename, 'audio') 40 | return '' 41 | 42 | @export('PRON') 43 | def fld_pron(self): 44 | try: 45 | entries = self._get_field('lexicalEntries') 46 | prons_mp3 = '' 47 | for entry in entries: 48 | if 'pronunciations' in entry: 49 | prons = entry['pronunciations'] 50 | pos = entry['lexicalCategory'] 51 | prons_mp3 += '
    ' if prons_mp3 else '' + u'
    '.join( 52 | u'{0}({1}) {2}'.format( 53 | pron['dialects'][0] + ' ' if 'dialects' in pron else '', 54 | pos, 55 | self._fld_pron_mp3(pron['audioFile'])) for pron in prons) 56 | return prons_mp3 57 | except: 58 | return '' 59 | 60 | @export('PHON') 61 | def fld_phon(self): 62 | try: 63 | prons = self._get_field('lexicalEntries')[0]['pronunciations'] 64 | return u'
    '.join(u'{0}{1}'.format(pron['dialects'][0] + ': ' if 'dialects' in pron else '', pron['phoneticSpelling']) for pron in prons) 65 | except: 66 | return '' 67 | 68 | @export('EXAMPLE') 69 | def fld_example(self): 70 | try: 71 | entries = self._get_field('lexicalEntries')[0]['entries'][0]['senses'][0]['examples'] 72 | return u'
    '.join(entry['text'] for entry in entries) 73 | except: 74 | return '' 75 | 76 | @export([u'派生词', u'Derivatives']) 77 | def fld_deriv(self): 78 | try: 79 | entries = self._get_field('lexicalEntries')[0]['derivatives'] 80 | return u', '.join(entry['text'] for entry in entries) 81 | except: 82 | return '' 83 | 84 | @export([u'词性', u'POS']) 85 | def fld_pos(self): 86 | try: 87 | entries = self._get_field('lexicalEntries') 88 | return u', '.join(entry['lexicalCategory'] for entry in entries) 89 | except: 90 | return '' 91 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/remotemdx.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 Liang Feng 4 | # 5 | # Support: Report an issue at https://github.com/finalion/WordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import os 21 | # import ntpath 22 | import re 23 | import urllib 24 | from collections import defaultdict 25 | 26 | from aqt.utils import showInfo, showText 27 | from ..base import * 28 | 29 | 30 | @register('MDX_SERVER') 31 | class RemoteMdx(WebService): 32 | 33 | def __init__(self): 34 | super(RemoteMdx, self).__init__() 35 | self.cache = defaultdict(set) 36 | 37 | def active(self, dict_path, word): 38 | self.word = word 39 | self.url = dict_path + \ 40 | '/' if not dict_path.endswith('/') else dict_path 41 | try: 42 | html = self.get_response(self.url + word) 43 | result, js = self.adapt_to_anki(html) 44 | return QueryResult(result=result, js=js) 45 | except: 46 | return QueryResult.default() 47 | 48 | def download_media_files(self, data): 49 | diff = data.difference(self.cache[self.url]) 50 | self.cache[self.url].update(diff) 51 | errors, styles = list(), list() 52 | for each in diff: 53 | basename = os.path.basename(each.replace('\\', os.path.sep)) 54 | saved_basename = '_' + basename 55 | abs_url = urllib.parse.urljoin(self.url, each) 56 | if basename.endswith('.css') or basename.endswith('.js'): 57 | styles.append(saved_basename) 58 | if not os.path.exists(saved_basename): 59 | try: 60 | self.download(abs_url, saved_basename) 61 | except: 62 | errors.append(each) 63 | return errors, styles 64 | 65 | def adapt_to_anki(self, html): 66 | """ 67 | 1. convert the media path to actual path in anki's collection media folder. 68 | 2. remove the js codes 69 | 3. import css, to make sure the css file can be synced. TO VALIDATE! 70 | """ 71 | media_files_set = set() 72 | mcss = re.findall(r'href="(\S+?\.css)"', html) 73 | media_files_set.update(set(mcss)) 74 | mjs = re.findall(r'src="([\w\./]\S+?\.js)"', html) 75 | media_files_set.update(set(mjs)) 76 | msrc = re.findall(r'', html) 77 | media_files_set.update(set(msrc)) 78 | for each in media_files_set: 79 | html = html.replace(each, '_' + each.split('/')[-1]) 80 | errors, styles = self.download_media_files(media_files_set) 81 | html = u'
    '.join([u"".format(style) 82 | for style in styles if style.endswith('.css')]) + html 83 | js = re.findall(r'.*?', html, re.DOTALL) 84 | # for each in js: 85 | # html = html.replace(each, '') 86 | # showText(html) 87 | return unicode(html), u'\n'.join(js) 88 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/txt.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | 4 | from aqt.utils import showInfo, showText 5 | from ..base import LocalService, export, register, with_styles 6 | 7 | path = u'D:\\dicts\\LDOCE\\d.txt' 8 | 9 | 10 | @register(u'txt测试') 11 | class TxtTest(LocalService): 12 | 13 | def __init__(self): 14 | super(TxtTest, self).__init__(path) 15 | try: 16 | self.handle = open(path, 'rb') 17 | except: 18 | self.handle = None 19 | 20 | @export(u'all') 21 | def fld_phonetic(self): 22 | if not self.handle: 23 | return 24 | for line in self.handle: 25 | line = line.decode("UTF-8") 26 | m = re.search(self.word, line) 27 | if m: 28 | return line 29 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/vocabulary.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import random 3 | from ..base import * 4 | 5 | 6 | @register(u'Vocabulary.com') 7 | class Vocabulary(WebService): 8 | 9 | def __init__(self): 10 | super(Vocabulary, self).__init__() 11 | 12 | def _get_from_api(self): 13 | data = self.get_response(u'https://www.vocabulary.com/dictionary/{}'.format(self.quote_word)) 14 | soup = parse_html(data) 15 | result = { 16 | 'short': u'', 17 | 'long': u'', 18 | } 19 | 20 | # short 21 | element = soup.find('p', class_='short') 22 | if element: 23 | result['short'] = u''.join(str(e) for e in element.contents) 24 | 25 | # long 26 | element = soup.find('p', class_='long') 27 | if element: 28 | result['long'] = u''.join(str(e) for e in element.contents) 29 | 30 | return self.cache_this(result) 31 | 32 | @export([u'简短释义', u'Short definition']) 33 | def fld_definate(self): 34 | return self._get_field('short') 35 | 36 | @export([u'详细释义', u'Long definition']) 37 | def fld_example(self): 38 | return self._get_field('long') 39 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/yahoo.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | from ..base import * 4 | 5 | yahoo_download_mp3 = True 6 | 7 | 8 | @register([u'雅虎奇摩字典', u'Yahoo-Dict']) 9 | class Yahoo_Dict(WebService): 10 | 11 | def __init__(self): 12 | super(Yahoo_Dict, self).__init__() 13 | 14 | def _get_from_api(self): 15 | url = u"https://tw.dictionary.search.yahoo.com/search?p={}".format( 16 | self.quote_word) 17 | html = self.get_response(url, timeout=10) 18 | soup = parse_html(html) 19 | result = { 20 | 'phon': '', 21 | 'def': '', 22 | 'audio_url': '', 23 | 'detail': '', 24 | } 25 | 26 | # 基本 27 | element = soup.find('div', class_='dd cardDesign dictionaryWordCard sys_dict_word_card') 28 | if element: 29 | # 音标 30 | tag = element.find('div', class_='compList ml-25 d-ib') 31 | if tag: 32 | result['phon'] = tag.get_text() 33 | 34 | # 发音 35 | result['audio_url'] = u'https://s.yimg.com/bg/dict/dreye/live/f/{}.mp3'.format( 36 | self.word) 37 | 38 | # 词性及中文解释 39 | tag = element.find('div', class_='compList mb-25 ml-25 p-rel') 40 | if tag: 41 | result['def'] = u'
    ' + \ 42 | str(tag.find('ul')).decode('utf-8') + u'
    ' 43 | 44 | # 释义 45 | tag = soup.find('div', class_='grp grp-tab-content-explanation tabsContent tab-content-explanation tabActived') 46 | if tag: 47 | result['detail'] = u'
    ' + \ 48 | str(tag.find('ul')).decode('utf-8') + u'
    ' 49 | 50 | 51 | return self.cache_this(result) 52 | 53 | @with_styles(need_wrap_css=True, cssfile='_yahoo.css') 54 | def _css(self, val): 55 | return val 56 | 57 | @export('PHON') 58 | def fld_pinyin(self): 59 | return self._get_field('phon') 60 | 61 | @export('PRON') 62 | def fld_pron(self): 63 | audio_url = self._get_field('audio_url') 64 | if yahoo_download_mp3 and audio_url: 65 | filename = get_hex_name(self.unique.lower(), audio_url, 'mp3') 66 | if os.path.exists(filename) or self.download(audio_url, filename, 5): 67 | return self.get_anki_label(filename, 'audio') 68 | 69 | return '' 70 | 71 | @export('DEF') 72 | def fld_basic(self): 73 | val = self._get_field('def') 74 | if val is None or val == '': 75 | return '' 76 | return self._css(val) 77 | 78 | @export([u'详细释义', u'Detailed Interpretation']) 79 | def fld_detail(self): 80 | val = self._get_field('detail') 81 | if val is None or val == '': 82 | return '' 83 | return self._css(val) 84 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/youdaofr.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import xml.etree.ElementTree 4 | from ..base import * 5 | 6 | 7 | @register([u'有道词典-法语', u'Youdao-French']) 8 | class Youdaofr(WebService): 9 | 10 | def __init__(self): 11 | super(Youdaofr, self).__init__() 12 | 13 | def _get_from_api(self, lang='fr'): 14 | url = (u'http://dict.youdao.com/fsearch?client=deskdict' 15 | '&keyfrom=chrome.extension&pos=-1' 16 | '&doctype=xml&xmlVersion=3.2' 17 | '&dogVersion=1.0&vendor=unknown' 18 | '&appVer=3.1.17.4208' 19 | '&le={0}&q={1}').format(lang, self.quote_word) 20 | result ={ 21 | 'phonetic': '', 22 | 'explains':'', 23 | } 24 | try: 25 | html = self.get_response(url, timeout=5) 26 | # showInfo(str(result)) 27 | doc = xml.etree.ElementTree.fromstring(html) 28 | # fetch explanations 29 | explains = '
    '.join([node.text for node in doc.findall( 30 | ".//custom-translation/translation/content")]) 31 | result.update({'explains': explains}) 32 | except: 33 | pass 34 | return self.cache_this(result) 35 | 36 | @export([u'基本释义', u'Explanations']) 37 | def fld_explains(self): 38 | return self.cache_result('explains') if self.cached('explains') else \ 39 | self._get_from_api().get('explains', '') 40 | 41 | @with_styles(cssfile='_youdao.css', need_wrap_css=True, wrap_class='youdao') 42 | def _get_singledict(self, single_dict, lang='fr'): 43 | url = u"http://m.youdao.com/singledict?q={0}&dict={1}&le={2}&more=false".format( 44 | self.quote_word, single_dict, lang 45 | ) 46 | try: 47 | html = self.get_response(url, timeout=5) 48 | return (u'
    ' 49 | '
    {3}
    ' 50 | '
    ' 51 | '
    ' 52 | '' 53 | '
    ').format( 54 | single_dict, 55 | single_dict, 56 | single_dict, 57 | html.decode('utf-8') 58 | ) 59 | except: 60 | return '' 61 | 62 | @export([u'网络释义', u'Web translation']) 63 | def fld_web_trans(self): 64 | return self._get_singledict('web_trans') 65 | 66 | @export([u'双语例句', u'Bilingual examples']) 67 | def fld_blng_sents_part(self): 68 | return self._get_singledict('blng_sents_part') 69 | 70 | @export([u'百科', u'baike']) 71 | def fld_baike(self): 72 | return self._get_singledict('baike') 73 | 74 | @export([u'汉语词典(中)', u'Chinese dictionary']) 75 | def fld_hh(self): 76 | return self._get_singledict('hh') 77 | -------------------------------------------------------------------------------- /addons/fastwq/service/dict/youdaoko.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import xml.etree.ElementTree 4 | from ..base import * 5 | 6 | 7 | @register([u'有道词典-韩语', u'Youdao-Korean']) 8 | class Youdaoko(WebService): 9 | 10 | def __init__(self): 11 | super(Youdaoko, self).__init__() 12 | 13 | def _get_from_api(self, lang='ko'): 14 | url = (u'http://dict.youdao.com/fsearch?client=deskdict' 15 | '&keyfrom=chrome.extension&pos=-1' 16 | '&doctype=xml&xmlVersion=3.2' 17 | '&dogVersion=1.0&vendor=unknown' 18 | '&appVer=3.1.17.4208' 19 | '&le={0}&q={1}').format(lang, self.quote_word) 20 | result ={ 21 | 'phonetic': u'', 22 | 'explains': u'', 23 | } 24 | try: 25 | html = self.get_response(url, timeout=5) 26 | # showInfo(str(result)) 27 | doc = xml.etree.ElementTree.fromstring(html) 28 | # fetch explanations 29 | explains = '
    '.join([node.text for node in doc.findall( 30 | ".//custom-translation/translation/content")]) 31 | result.update({'explains': explains}) 32 | except: 33 | pass 34 | return self.cache_this(result) 35 | 36 | @export([u'基本释义', u'Explanations']) 37 | def fld_explains(self): 38 | return self.cache_result('explains') if self.cached('explains') else \ 39 | self._get_from_api().get('explains', '') 40 | 41 | @with_styles(cssfile='_youdao.css', need_wrap_css=True, wrap_class='youdao') 42 | def _get_singledict(self, single_dict, lang='ko'): 43 | url = u"http://m.youdao.com/singledict?q={0}&dict={1}&le={2}&more=false".format( 44 | self.quote_word, single_dict, lang 45 | ) 46 | try: 47 | html = self.get_response(url, timeout=5) 48 | return (u'
    ' 49 | '
    {3}
    ' 50 | '
    ' 51 | '
    ' 52 | '' 53 | '
    ').format( 54 | single_dict, 55 | single_dict, 56 | single_dict, 57 | html 58 | ) 59 | except: 60 | return '' 61 | 62 | @export([u'网络释义', u'Web translation']) 63 | def fld_web_trans(self): 64 | return self._get_singledict('web_trans') 65 | 66 | @export([u'双语例句', u'Bilingual examples']) 67 | def fld_blng_sents_part(self): 68 | return self._get_singledict('blng_sents_part') 69 | 70 | @export([u'百科', u'baike']) 71 | def fld_baike(self): 72 | return self._get_singledict('baike') 73 | 74 | @export([u'汉语词典(中)', u'Chinese dictionary']) 75 | def fld_hh(self): 76 | return self._get_singledict('hh') 77 | -------------------------------------------------------------------------------- /addons/fastwq/service/pool.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from ..utils import Empty, Queue 21 | 22 | 23 | class ServicePool(object): 24 | """ 25 | Service instance pool 26 | """ 27 | def __init__(self, manager): 28 | self.pools = {} 29 | self.manager = manager 30 | 31 | def get(self, unique): 32 | queue = self.pools.get(unique, None) 33 | if queue: 34 | try: 35 | return queue.get(True, timeout=0.1) 36 | except Empty: 37 | pass 38 | 39 | return self.manager.get_service(unique) 40 | 41 | def put(self, service): 42 | if service is None: 43 | return 44 | unique = service.unique 45 | queue = self.pools.get(unique, None) 46 | if queue == None: 47 | queue = Queue() 48 | self.pools[unique] = queue 49 | 50 | queue.put(service) 51 | 52 | def clean(self): 53 | self.pools = {} 54 | -------------------------------------------------------------------------------- /addons/fastwq/service/static/_bing.css: -------------------------------------------------------------------------------- 1 | ul, 2 | li, 3 | form, 4 | table, 5 | tr, 6 | th, 7 | td, 8 | blockquote { 9 | border: 0; 10 | border-collapse: collapse; 11 | border-spacing: 0; 12 | list-style: none; 13 | margin: 0; 14 | padding: 0 15 | } 16 | .qdef ul { 17 | padding-top: 10px 18 | } 19 | 20 | #sentenceSeg .sen_li { 21 | display: inline-block; 22 | padding-right: 10px; 23 | line-height: 19px; 24 | vertical-align: middle; 25 | margin: 0 0 10px 0; 26 | width: auto 27 | } 28 | 29 | #sentenceSeg .mm_div { 30 | margin-bottom: 10px; 31 | line-height: 19px; 32 | vertical-align: middle; 33 | padding: 0; 34 | display: inline-block 35 | } 36 | .sen_li { 37 | margin: 5px 0 2px 0; 38 | width: 100% 39 | } 40 | 41 | .sen_con { 42 | color: #333; 43 | padding: 2px 0; 44 | font-size: 14px; 45 | line-height: 22px 46 | } 47 | 48 | .sen_con strong { 49 | color: #c00 50 | } 51 | 52 | .sen_count { 53 | font-size: 14px 54 | } 55 | 56 | .sen_count a { 57 | color: #a1a1a1 58 | } 59 | 60 | .sen_count a:hover { 61 | color: #04c 62 | } 63 | 64 | .sen_count a:visited { 65 | color: #639 66 | } 67 | .sen_en>span { 68 | font-size: 14px; 69 | text-decoration: none; 70 | outline: medium none 71 | } 72 | 73 | .sen_en, 74 | .sen_cn, 75 | .sen_ime { 76 | width: 100%; 77 | font-size: 14px; 78 | padding-left: 0; 79 | margin-left: 0 80 | } 81 | 82 | .sen_en { 83 | line-height: 14px; 84 | margin-bottom: 2px; 85 | color: #000 86 | } 87 | 88 | .sen_cn { 89 | line-height: 22px; 90 | margin-bottom: 2px; 91 | color: #777 92 | } 93 | 94 | .sen_ime { 95 | line-height: 17.5px; 96 | color: #777; 97 | margin-bottom: 2px 98 | } 99 | 100 | 101 | .se_d, 102 | .se_def_nu { 103 | line-height: 22px 104 | } 105 | 106 | 107 | .se_d { 108 | width: 20px; 109 | float: left 110 | } 111 | 112 | .se_lis, 113 | .idmdef_li { 114 | margin-left: 20px 115 | } 116 | 117 | 118 | .se_buf { 119 | margin-left: 24px; 120 | float: left; 121 | margin-top: 4px 122 | } 123 | 124 | .se_d, 125 | .def_pa, 126 | .idmdef_li { 127 | color: #000; 128 | font-size: 14px 129 | } 130 | .se_div { 131 | margin-top: 10px; 132 | padding-bottom: 0; 133 | overflow: hidden 134 | } 135 | 136 | .se_li { 137 | list-style-type: none; 138 | padding-top: 0; 139 | padding-left: 0; 140 | clear: both 141 | } 142 | 143 | .se_li1 { 144 | margin-bottom: 5px; 145 | margin-left: 0; 146 | padding-left: 0; 147 | overflow: hidden; 148 | float: left; 149 | max-width: 508px 150 | } 151 | 152 | .se_n_d { 153 | float: left; 154 | width: 24px; 155 | padding-top: 3px 156 | } 157 | 158 | .qdef .hd_prUS, 159 | .qdef .hd_pr { 160 | color: #777; 161 | font-size: 14px 162 | } 163 | 164 | .qdef .pos { 165 | width: 35px; 166 | font-size: 93%; 167 | background-color: #aaa; 168 | color: #fff; 169 | line-height: 18px; 170 | vertical-align: middle; 171 | text-align: center 172 | } 173 | 174 | .qdef .pos1 { 175 | margin-top: 2px 176 | } 177 | 178 | .qdef .web { 179 | background-color: #333 180 | } 181 | 182 | .qdef ul { 183 | padding-top: 10px 184 | } 185 | 186 | .qdef li { 187 | padding-top: 4px; 188 | font-weight: bold 189 | } 190 | 191 | .qdef .def { 192 | padding-left: 15px; 193 | line-height: 20px; 194 | vertical-align: top; 195 | font-size: 14px; 196 | width: 90% 197 | } 198 | 199 | .qdef .def a { 200 | color: #000 201 | } 202 | 203 | .qdef .def a:hover { 204 | color: #04c 205 | } 206 | 207 | .qdef .hd_div1 .p2-1 { 208 | font-weight: bold; 209 | color: #777 210 | } 211 | 212 | .qdef .hd_div1 { 213 | color: #777; 214 | padding-top: 9px 215 | } 216 | 217 | .qdef:after { 218 | clear: both 219 | } 220 | 221 | .qdef .hd_if a { 222 | margin: 0 6px 0 0; 223 | font-size: 14px 224 | } 225 | 226 | .qdef div.simg, 227 | .qdef div.simgmore { 228 | margin-top: 10px 229 | } 230 | 231 | .qdef .simg { 232 | left: 1px 233 | } 234 | 235 | .qdef df_div { 236 | margin-top: 60px 237 | } 238 | 239 | .qdef .hd_tf_lh { 240 | line-height: 19px; 241 | padding-top: 3px 242 | } 243 | 244 | .qdef .wd_div { 245 | margin-top: 10px 246 | } 247 | 248 | .qdef .df_div { 249 | padding-top: 10px 250 | } 251 | 252 | .qdef .hd_area { 253 | float: none; 254 | overflow: hidden; 255 | margin-bottom: 0 256 | }.qdef h1 { 257 | font-size: 100% 258 | } -------------------------------------------------------------------------------- /addons/fastwq/service/static/_cambridge.css: -------------------------------------------------------------------------------- 1 | .epp-xref { 2 | margin-right: 3px; 3 | padding: 2px 5px; 4 | color: #fff; 5 | font-weight: 700; 6 | font-size: .8em; 7 | min-width: 14px; 8 | text-align: center; 9 | background-color: #444; 10 | border-radius: 8px; 11 | } 12 | 13 | b.def { 14 | line-height: 1.3rem 15 | } 16 | 17 | .examp { 18 | margin: 0 0 5px 0; 19 | line-height: 1.5em; 20 | font-weight: normal; 21 | font-style: italic; 22 | display: block; 23 | margin: 0 24 | } 25 | 26 | li { 27 | display: list-item; 28 | list-style: none; 29 | text-align: -webkit-match-parent; 30 | } 31 | 32 | menu, ol, ul { 33 | padding: 0; 34 | list-style: none; 35 | list-style-position: outside; 36 | } 37 | 38 | ol { 39 | display: block; 40 | list-style: none; 41 | list-style-type: decimal; 42 | } 43 | 44 | dl, menu, ol, ul, dd, p { 45 | margin: 0; 46 | } 47 | -------------------------------------------------------------------------------- /addons/fastwq/service/static/_yahoo.css: -------------------------------------------------------------------------------- 1 | #results #left #web .dd, #results #right .cardReg .dd { 2 | margin-left: 3px; 3 | margin-right: 3px; 4 | margin-bottom: 6px; 5 | } 6 | 7 | div { 8 | display: block; 9 | } 10 | 11 | li { 12 | display: list-item; 13 | list-style: none; 14 | text-align: -webkit-match-parent; 15 | } 16 | 17 | menu, ol, ul { 18 | padding: 0; 19 | list-style: none; 20 | list-style-position: outside; 21 | } 22 | 23 | ol { 24 | display: block; 25 | list-style: none; 26 | list-style-type: decimal; 27 | } 28 | 29 | dl, menu, ol, ul, dd, p { 30 | margin: 0; 31 | } 32 | 33 | ul, menu, dir { 34 | display: block; 35 | list-style-type: disc; 36 | } 37 | 38 | .dd .mr-25 { 39 | margin-right: 25px; 40 | } 41 | 42 | .mb-12, .mb-12 { 43 | margin-bottom: 12px; 44 | } 45 | 46 | .mt-12, .mt-12 { 47 | margin-top: 12px; 48 | } 49 | 50 | .lh-22 { 51 | line-height: 22px; 52 | } 53 | 54 | .mh-22 { 55 | min-height: 22px; 56 | } 57 | 58 | .dd .mr-12 { 59 | margin-right: 12px; 60 | } 61 | 62 | .dd .fz-14, .fz-14 { 63 | font-size: 14px; 64 | } 65 | 66 | .dd .fz-16, .fz-16 { 67 | font-size: 16px; 68 | } 69 | 70 | .dd .fl-l, .fl-l { 71 | float: left; 72 | } 73 | 74 | .cardDesign .tabs-pos_type, .cardDesign .pos_button { 75 | display: inline-block; 76 | vertical-align: middle; 77 | min-width: 36px; 78 | height: 22px; 79 | line-height: 22px; 80 | border-radius: 2px; 81 | background-color: #006cb7; 82 | text-align: center; 83 | color: #fff; 84 | padding: 0 2px 0 2px; 85 | } -------------------------------------------------------------------------------- /addons/fastwq/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .Queue import Queue, Empty, Full 2 | from . import importlib 3 | from .misc import * 4 | from .helper import * 5 | -------------------------------------------------------------------------------- /addons/fastwq/utils/helper.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | import os 4 | 5 | __all__ = ['add_metaclass', 'wrap_css'] 6 | 7 | 8 | def add_metaclass(metaclass): 9 | """Class decorator for creating a class with a metaclass.""" 10 | def wrapper(cls): 11 | orig_vars = cls.__dict__.copy() 12 | slots = orig_vars.get('__slots__') 13 | if slots is not None: 14 | if isinstance(slots, str): 15 | slots = [slots] 16 | for slots_var in slots: 17 | orig_vars.pop(slots_var) 18 | orig_vars.pop('__dict__', None) 19 | orig_vars.pop('__weakref__', None) 20 | return metaclass(cls.__name__, cls.__bases__, orig_vars) 21 | return wrapper 22 | 23 | 24 | def wrap_css(orig_css, is_file=True, class_wrapper=None, new_cssfile_suffix=u'wrap'): 25 | 26 | def process(content): 27 | # clean the comments 28 | regx = re.compile(r'/\*.*?\*/', re.DOTALL) 29 | content = regx.sub(r'', content).strip() 30 | # add wrappers to all the selectors except the first one 31 | regx = re.compile(r'([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)', re.DOTALL) 32 | new_css = regx.sub(u'.{} \\1\\2'.format(class_wrapper), content) 33 | return new_css 34 | 35 | if is_file: 36 | if not class_wrapper: 37 | class_wrapper = os.path.splitext(os.path.basename(orig_css))[0] 38 | new_cssfile = u'{css_name}_{suffix}.css'.format( 39 | css_name=orig_css[:orig_css.rindex('.css')], 40 | suffix=new_cssfile_suffix) 41 | # if new css file exists, not process 42 | # if input original css file doesn't exist, return the new css filename and class wrapper 43 | # to make the subsequent process easy. 44 | if os.path.exists(new_cssfile) or not os.path.exists(orig_css): 45 | return new_cssfile, class_wrapper 46 | result = '' 47 | with open(orig_css, 'rb') as f: 48 | try: 49 | result = process(f.read().strip().decode('utf-8', 'ignore')) 50 | except: 51 | showInfo('error: ' + orig_css) 52 | 53 | if result: 54 | with open(new_cssfile, 'wb') as f: 55 | f.write(result.encode('utf-8')) 56 | return new_cssfile, class_wrapper 57 | else: 58 | # class_wrapper must be valid. 59 | assert class_wrapper 60 | return process(orig_css), class_wrapper 61 | -------------------------------------------------------------------------------- /addons/fastwq/utils/importlib.py: -------------------------------------------------------------------------------- 1 | """Backport of importlib.import_module from 3.x.""" 2 | # While not critical (and in no way guaranteed!), it would be nice to keep this 3 | # code compatible with Python 2.3. 4 | import sys 5 | try: 6 | from importlib import reload 7 | except: 8 | pass 9 | 10 | def _resolve_name(name, package, level): 11 | """Return the absolute name of the module to be imported.""" 12 | if not hasattr(package, 'rindex'): 13 | raise ValueError("'package' not set to a string") 14 | dot = len(package) 15 | for x in xrange(level, 1, -1): 16 | try: 17 | dot = package.rindex('.', 0, dot) 18 | except ValueError: 19 | raise ValueError("attempted relative import beyond top-level " 20 | "package") 21 | return "%s.%s" % (package[:dot], name) 22 | 23 | 24 | def import_module(name, package=None): 25 | """Import a module. 26 | The 'package' argument is required when performing a relative import. It 27 | specifies the package to use as the anchor point from which to resolve the 28 | relative import to an absolute import. 29 | """ 30 | if name.startswith('.'): 31 | if not package: 32 | raise TypeError("relative imports require the 'package' argument") 33 | level = 0 34 | for character in name: 35 | if character != '.': 36 | break 37 | level += 1 38 | name = _resolve_name(name[level:], package, level) 39 | 40 | if name in sys.modules: 41 | reload(sys.modules[name]) 42 | else: 43 | __import__(name) 44 | return sys.modules[name] 45 | -------------------------------------------------------------------------------- /addons/fastwq/utils/misc.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import os 21 | from functools import wraps 22 | from aqt.utils import showInfo 23 | from aqt.qt import QIcon 24 | 25 | __all__ = ['ignore_exception', 26 | 'get_model_byId', 27 | 'get_icon', 28 | 'get_ord_from_fldname', 29 | 'MapDict'] 30 | 31 | 32 | def ignore_exception(func): 33 | @wraps(func) 34 | def wrap(*args, **kwargs): 35 | try: 36 | return func(*args, **kwargs) 37 | except: 38 | return '' 39 | return wrap 40 | 41 | 42 | def get_model_byId(models, id): 43 | for m in list(models.all()): 44 | # showInfo(str(m['id']) + ', ' + m['name']) 45 | if m['id'] == id: 46 | return m 47 | 48 | 49 | def get_ord_from_fldname(model, name): 50 | flds = model['flds'] 51 | for fld in flds: 52 | if fld['name'] == name: 53 | return fld['ord'] 54 | 55 | 56 | def get_icon(filename): 57 | curdir = os.path.dirname(os.path.abspath(__file__)) 58 | pardir = os.path.join(curdir, os.pardir) 59 | path = os.path.join(pardir, 'res', filename) 60 | return QIcon(path) 61 | 62 | 63 | class MapDict(dict): 64 | """ 65 | Example: 66 | m = Map({'first_name': 'Eduardo'}, 67 | last_name='Pool', age=24, sports=['Soccer']) 68 | """ 69 | 70 | def __init__(self, *args, **kwargs): 71 | super(MapDict, self).__init__(*args, **kwargs) 72 | for arg in args: 73 | if isinstance(arg, dict): 74 | for k, v in arg.items(): 75 | self[k] = v 76 | 77 | if kwargs: 78 | for k, v in kwargs.items(): 79 | self[k] = v 80 | 81 | def __getattr__(self, attr): 82 | return self.get(attr) 83 | 84 | def __setattr__(self, key, value): 85 | self.__setitem__(key, value) 86 | 87 | def __setitem__(self, key, value): 88 | super(MapDict, self).__setitem__(key, value) 89 | self.__dict__.update({key: value}) 90 | 91 | def __delattr__(self, item): 92 | self.__delitem__(item) 93 | 94 | def __delitem__(self, key): 95 | super(MapDict, self).__delitem__(key) 96 | del self.__dict__[key] 97 | -------------------------------------------------------------------------------- /addons21/fastwq/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import ssl 21 | import sys 22 | 23 | from anki.hooks import addHook 24 | from anki.utils import isMac 25 | 26 | sys.dont_write_bytecode = True 27 | if isMac: 28 | ssl._create_default_https_context = ssl._create_unverified_context 29 | 30 | ############## other config here ################## 31 | shortcut = ('Ctrl+Alt' if isMac else 'Ctrl') + '+Q' 32 | 33 | ################################################### 34 | 35 | 36 | def start_here(): 37 | from . import common as fastwq 38 | from .context import config 39 | config.read() 40 | fastwq.my_shortcut = shortcut 41 | if not fastwq.have_setup: 42 | fastwq.have_setup = True 43 | fastwq.config_menu() 44 | fastwq.browser_menu() 45 | fastwq.context_menu() 46 | fastwq.customize_addcards() 47 | 48 | 49 | addHook("profileLoaded", start_here) 50 | -------------------------------------------------------------------------------- /addons21/fastwq/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from .lang import _ 21 | 22 | __all__ = ['VERSION', 'Endpoint', 'Template'] 23 | 24 | VERSION = 'v2.0.0b' 25 | 26 | 27 | class Endpoint: 28 | repository = u'https://github.com/sth2018/FastWordQuery' 29 | feedback_issue = u'https://github.com/sth2018/FastWordQuery/issues' 30 | feedback_mail = u'sth201807@gmail.com' 31 | check_version = u'sth2018/FastWordQuery' 32 | user_guide = u'https://sth2018.github.io/FastWordQuery' 33 | version = VERSION 34 | 35 | 36 | class Template: 37 | tmpl_about = u'''{t0}
    {version}
    {t1}
    38 |
    {url}
    {t2}
    39 | {feedback0}
    40 | {feedback1}'''.format( 41 | t0=_('VERSION'), 42 | version=VERSION, 43 | t1=_('REPOSITORY'), 44 | url=Endpoint.repository, 45 | t2=_('FEEDBACK'), 46 | feedback0=Endpoint.feedback_issue, 47 | feedback1=Endpoint.feedback_mail) 48 | -------------------------------------------------------------------------------- /addons21/fastwq/gui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | from .common import * 4 | from .progress import * 5 | -------------------------------------------------------------------------------- /addons21/fastwq/gui/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import sys 21 | 22 | from anki.utils import isMac 23 | from aqt.qt import * 24 | 25 | from ..context import APP_ICON 26 | 27 | __all__ = ['Dialog', 'WIDGET_SIZE'] 28 | 29 | 30 | class Dialog(QDialog): 31 | ''' 32 | Base used for all dialog windows. 33 | ''' 34 | 35 | def __init__(self, parent, title): 36 | ''' 37 | Set the modal status for the dialog, sets its layout to the 38 | return value of the _ui() method, and sets a default title. 39 | ''' 40 | 41 | self._title = title if "FastWQ" in title else "FastWQ - " + title 42 | self._parent = parent 43 | super(Dialog, self).__init__(parent) 44 | 45 | self.setModal(True) 46 | self.setWindowFlags( 47 | self.windowFlags() & ~Qt.WindowContextHelpButtonHint) 48 | self.setWindowIcon(APP_ICON) 49 | self.setWindowTitle(self._title) 50 | # 2 & 3 & mac compatible 51 | if isMac and sys.hexversion >= 0x03000000: 52 | QApplication.setStyle('Fusion') 53 | 54 | 55 | class WidgetSize(object): 56 | ''' 57 | constant values 58 | ''' 59 | dialog_width = 850 60 | dialog_height_margin = 166 if isMac and sys.hexversion < 0x03000000 else 146 61 | map_min_height = 0 62 | map_max_height = 30 63 | map_fld_width = 100 64 | map_dictname_width = 150 65 | map_dict_width = 160 66 | map_field_width = 200 67 | 68 | 69 | WIDGET_SIZE = WidgetSize() 70 | -------------------------------------------------------------------------------- /addons21/fastwq/gui/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import types 21 | 22 | from aqt import mw 23 | from aqt.qt import * 24 | 25 | from ..constants import Template 26 | from ..context import config 27 | from ..lang import _ 28 | from ..service import service_manager, service_pool 29 | from .dictmanager import DictManageDialog 30 | from .foldermanager import FoldersManageDialog 31 | from .options import OptionsDialog 32 | 33 | __all__ = ['show_options', 'show_fm_dialog', 'show_about_dialog'] 34 | 35 | 36 | def show_fm_dialog(browser=None): 37 | '''open dictionary folder manager window''' 38 | parent = mw if browser is None else browser 39 | fm_dialog = FoldersManageDialog(parent, u'Dictionary Folder Manager') 40 | fm_dialog.activateWindow() 41 | fm_dialog.raise_() 42 | if fm_dialog.exec_() == QDialog.Accepted: 43 | # update local services 44 | service_pool.clean() 45 | service_manager.update_services() 46 | fm_dialog.destroy() 47 | # reshow options window 48 | show_options(browser) 49 | 50 | 51 | def show_dm_dialog(browser=None): 52 | parent = mw if browser is None else browser 53 | dm_dialog = DictManageDialog(parent, u'Dictionary Manager') 54 | dm_dialog.activateWindow() 55 | dm_dialog.raise_() 56 | if dm_dialog.exec_() == QDialog.Accepted: 57 | # update local services 58 | service_pool.clean() 59 | service_manager.update_services() 60 | dm_dialog.destroy() 61 | # reshow options window 62 | show_options(browser) 63 | 64 | 65 | def show_options(browser=None, model_id=-1, callback=None, *args, **kwargs): 66 | '''open options window''' 67 | parent = mw if browser is None else browser 68 | config.read() 69 | opt_dialog = OptionsDialog(parent, u'Options', model_id) 70 | opt_dialog.activateWindow() 71 | opt_dialog.raise_() 72 | result = opt_dialog.exec_() 73 | opt_dialog.destroy() 74 | if result == QDialog.Accepted: 75 | if isinstance(callback, types.FunctionType): 76 | callback(*args, **kwargs) 77 | elif result == 1001: 78 | show_fm_dialog(parent) 79 | elif result == 1002: 80 | show_dm_dialog(parent) 81 | 82 | 83 | def show_about_dialog(parent): 84 | '''open about dialog''' 85 | QMessageBox.about(parent, _('ABOUT'), Template.tmpl_about) 86 | -------------------------------------------------------------------------------- /addons21/fastwq/gui/foldermanager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from aqt.qt import * 21 | 22 | from ..context import config 23 | from ..lang import _, _sl 24 | from .base import WIDGET_SIZE, Dialog 25 | 26 | __all__ = ['FoldersManageDialog'] 27 | 28 | 29 | class FoldersManageDialog(Dialog): 30 | ''' 31 | Dictionary folder manager window. add or remove dictionary folders. 32 | ''' 33 | 34 | def __init__(self, parent, title=u'Dictionary Folder Manager'): 35 | super(FoldersManageDialog, self).__init__(parent, title) 36 | # self._dict_paths = [] 37 | self.build() 38 | 39 | def build(self): 40 | layout = QVBoxLayout() 41 | btn_layout = QHBoxLayout() 42 | add_btn = QPushButton("+") 43 | remove_btn = QPushButton("-") 44 | btn_layout.addWidget(add_btn) 45 | btn_layout.addWidget(remove_btn) 46 | add_btn.clicked.connect(self.add_folder) 47 | remove_btn.clicked.connect(self.remove_folder) 48 | self.folders_lst = QListWidget() 49 | self.folders_lst.addItems(config.dirs) 50 | self.chk_use_filename = QCheckBox(_('CHECK_FILENAME_LABEL')) 51 | self.chk_export_media = QCheckBox(_('EXPORT_MEDIA')) 52 | self.chk_use_filename.setChecked(config.use_filename) 53 | self.chk_export_media.setChecked(config.export_media) 54 | chk_layout = QHBoxLayout() 55 | chk_layout.addWidget(self.chk_use_filename) 56 | chk_layout.addWidget(self.chk_export_media) 57 | btnbox = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal, self) 58 | btnbox.accepted.connect(self.accept) 59 | layout.addLayout(btn_layout) 60 | layout.addWidget(self.folders_lst) 61 | layout.addLayout(chk_layout) 62 | layout.addWidget(btnbox) 63 | self.setLayout(layout) 64 | 65 | def add_folder(self): 66 | dir_ = QFileDialog.getExistingDirectory( 67 | self, 68 | caption=u"Select Folder", 69 | directory=config.last_folder, 70 | options=QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) 71 | if dir_: 72 | self.folders_lst.addItem(dir_) 73 | config.update({'last_folder': dir_}) 74 | 75 | def remove_folder(self): 76 | item = self.folders_lst.takeItem(self.folders_lst.currentRow()) 77 | del item 78 | 79 | @property 80 | def dirs(self): 81 | '''dictionary folders list''' 82 | return [ 83 | self.folders_lst.item(i).text() 84 | for i in range(self.folders_lst.count()) 85 | ] 86 | 87 | def accept(self): 88 | '''ok button clicked''' 89 | self.save() 90 | super(FoldersManageDialog, self).accept() 91 | 92 | def save(self): 93 | '''save config to file''' 94 | data = { 95 | 'dirs': self.dirs, 96 | 'use_filename': self.chk_use_filename.isChecked(), 97 | 'export_media': self.chk_export_media.isChecked() 98 | } 99 | config.update(data) 100 | -------------------------------------------------------------------------------- /addons21/fastwq/libs/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdict import IndexBuilder as MdxBuilder 2 | from .pystardict import Dictionary as StardictBuilder 3 | -------------------------------------------------------------------------------- /addons21/fastwq/libs/mdict/__init__.py: -------------------------------------------------------------------------------- 1 | from .mdict_query import IndexBuilder 2 | -------------------------------------------------------------------------------- /addons21/fastwq/libs/mdict/ripemd128.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright by https://github.com/zhansliu/writemdict 3 | 4 | ripemd128.py - A simple ripemd128 library in pure Python. 5 | 6 | Supports both Python 2 (versions >= 2.6) and Python 3. 7 | 8 | Usage: 9 | from ripemd128 import ripemd128 10 | digest = ripemd128(b"The quick brown fox jumps over the lazy dog") 11 | assert(digest == b"\x3f\xa9\xb5\x7f\x05\x3c\x05\x3f\xbe\x27\x35\xb2\x38\x0d\xb5\x96") 12 | 13 | """ 14 | 15 | 16 | 17 | import struct 18 | 19 | 20 | # follows this description: http://homes.esat.kuleuven.be/~bosselae/ripemd/rmd128.txt 21 | 22 | def f(j, x, y, z): 23 | assert(0 <= j and j < 64) 24 | if j < 16: 25 | return x ^ y ^ z 26 | elif j < 32: 27 | return (x & y) | (z & ~x) 28 | elif j < 48: 29 | return (x | (0xffffffff & ~y)) ^ z 30 | else: 31 | return (x & z) | (y & ~z) 32 | 33 | def K(j): 34 | assert(0 <= j and j < 64) 35 | if j < 16: 36 | return 0x00000000 37 | elif j < 32: 38 | return 0x5a827999 39 | elif j < 48: 40 | return 0x6ed9eba1 41 | else: 42 | return 0x8f1bbcdc 43 | 44 | def Kp(j): 45 | assert(0 <= j and j < 64) 46 | if j < 16: 47 | return 0x50a28be6 48 | elif j < 32: 49 | return 0x5c4dd124 50 | elif j < 48: 51 | return 0x6d703ef3 52 | else: 53 | return 0x00000000 54 | 55 | def padandsplit(message): 56 | """ 57 | returns a two-dimensional array X[i][j] of 32-bit integers, where j ranges 58 | from 0 to 16. 59 | First pads the message to length in bytes is congruent to 56 (mod 64), 60 | by first adding a byte 0x80, and then padding with 0x00 bytes until the 61 | message length is congruent to 56 (mod 64). Then adds the little-endian 62 | 64-bit representation of the original length. Finally, splits the result 63 | up into 64-byte blocks, which are further parsed as 32-bit integers. 64 | """ 65 | origlen = len(message) 66 | padlength = 64 - ((origlen - 56) % 64) #minimum padding is 1! 67 | message += b"\x80" 68 | message += b"\x00" * (padlength - 1) 69 | message += struct.pack("> (32-s)) & 0xffffffff 86 | 87 | r = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15, 88 | 7, 4,13, 1,10, 6,15, 3,12, 0, 9, 5, 2,14,11, 8, 89 | 3,10,14, 4, 9,15, 8, 1, 2, 7, 0, 6,13,11, 5,12, 90 | 1, 9,11,10, 0, 8,12, 4,13, 3, 7,15,14, 5, 6, 2] 91 | rp = [ 5,14, 7, 0, 9, 2,11, 4,13, 6,15, 8, 1,10, 3,12, 92 | 6,11, 3, 7, 0,13, 5,10,14,15, 8,12, 4, 9, 1, 2, 93 | 15, 5, 1, 3, 7,14, 6, 9,11, 8,12, 2,10, 0, 4,13, 94 | 8, 6, 4, 1, 3,11,15, 0, 5,12, 2,13, 9, 7,10,14] 95 | s = [11,14,15,12, 5, 8, 7, 9,11,13,14,15, 6, 7, 9, 8, 96 | 7, 6, 8,13,11, 9, 7,15, 7,12,15, 9,11, 7,13,12, 97 | 11,13, 6, 7,14, 9,13,15,14, 8,13, 6, 5,12, 7, 5, 98 | 11,12,14,15,14,15, 9, 8, 9,14, 5, 6, 8, 6, 5,12] 99 | sp = [ 8, 9, 9,11,13,15,15, 5, 7, 7, 8,11,14,14,12, 6, 100 | 9,13,15, 7,12, 8, 9,11, 7, 7,12, 7, 6,15,13,11, 101 | 9, 7,15,11, 8, 6, 6,14,12,13, 5,14,13,13, 7, 5, 102 | 15, 5, 8,11,14,14, 6,14, 6, 9,12, 9,12, 5,15, 8] 103 | 104 | 105 | def ripemd128(message): 106 | h0 = 0x67452301 107 | h1 = 0xefcdab89 108 | h2 = 0x98badcfe 109 | h3 = 0x10325476 110 | X = padandsplit(message) 111 | for i in range(len(X)): 112 | (A,B,C,D) = (h0,h1,h2,h3) 113 | (Ap,Bp,Cp,Dp) = (h0,h1,h2,h3) 114 | for j in range(64): 115 | T = rol(s[j], add(A, f(j,B,C,D), X[i][r[j]], K(j))) 116 | (A,D,C,B) = (D,C,B,T) 117 | T = rol(sp[j], add(Ap, f(63-j,Bp,Cp,Dp), X[i][rp[j]], Kp(j))) 118 | (Ap,Dp,Cp,Bp)=(Dp,Cp,Bp,T) 119 | T = add(h1,C,Dp) 120 | h1 = add(h2,D,Ap) 121 | h2 = add(h3,A,Bp) 122 | h3 = add(h0,B,Cp) 123 | h0 = T 124 | 125 | 126 | return struct.pack(" 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from collections import defaultdict 21 | import os 22 | import shutil 23 | import unicodedata 24 | 25 | from aqt import mw 26 | from aqt.utils import showInfo, showText, tooltip 27 | 28 | from .worker import QueryWorkerManager 29 | from .common import promot_choose_css, inspect_note 30 | 31 | from ..constants import Endpoint, Template 32 | from ..context import config 33 | from ..lang import _ 34 | from ..gui import ProgressWindow 35 | from ..service import service_manager, service_pool, QueryResult, copy_static_file 36 | from ..service.base import LocalService 37 | from ..utils import Empty, MapDict, Queue, wrap_css 38 | 39 | 40 | __all__ = ['query_from_browser', 'query_from_editor_fields'] 41 | 42 | 43 | def query_from_browser(browser): 44 | """ 45 | Query word from Browser 46 | """ 47 | 48 | if not browser: 49 | return 50 | 51 | notes = [browser.mw.col.getNote(note_id) 52 | for note_id in browser.selectedNotes()] 53 | 54 | if len(notes) == 1: 55 | query_from_editor_fields(browser.editor) 56 | else: 57 | query_all(notes) 58 | # browser.model.reset() 59 | 60 | 61 | def query_from_editor_fields(editor, fields=None): 62 | """ 63 | Query word fileds from Editor 64 | """ 65 | 66 | if not editor or not editor.note: 67 | return 68 | 69 | word_ord, word, maps = inspect_note(editor.note) 70 | flush = not editor.addMode 71 | nomaps = True 72 | for each in maps: 73 | dict_unique = each.get('dict_unique', '').strip() 74 | ignore = each.get('ignore', True) 75 | if dict_unique and not ignore: 76 | nomaps = False 77 | break 78 | if nomaps: 79 | from ..gui import show_options 80 | tooltip(_('PLS_SET_DICTIONARY_FIELDS')) 81 | show_options( 82 | editor.parentWindow, 83 | editor.note.model()['id'], 84 | query_from_editor_fields, 85 | editor, 86 | fields 87 | ) 88 | else: 89 | editor.setNote(editor.note) 90 | query_all([editor.note], flush, fields) 91 | editor.setNote(editor.note, focusTo=0) 92 | editor.saveNow(lambda:None) 93 | 94 | 95 | def query_all(notes, flush=True, fields=None): 96 | """ 97 | Query maps word fileds 98 | """ 99 | 100 | if len(notes) == 0: 101 | return 102 | 103 | work_manager = QueryWorkerManager() 104 | #work_manager.reset() 105 | #progress.start(max=len(notes), min=0, immediate=True) 106 | work_manager.flush = flush 107 | work_manager.query_fields = fields 108 | queue = work_manager.queue 109 | 110 | for note in notes: 111 | queue.put(note) 112 | 113 | work_manager.start() 114 | work_manager.join() 115 | 116 | #progress.finish() 117 | promot_choose_css(work_manager.missed_css) 118 | tooltip(u'{0} {1} {2}, {3} {4}'.format(_('UPDATED'), work_manager.counter, _( 119 | 'CARDS'), work_manager.fields, _('FIELDS'))) 120 | #work_manager.clean() 121 | service_pool.clean() 122 | -------------------------------------------------------------------------------- /addons21/fastwq/res/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons21/fastwq/res/add.png -------------------------------------------------------------------------------- /addons21/fastwq/res/null.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons21/fastwq/res/null.png -------------------------------------------------------------------------------- /addons21/fastwq/res/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons21/fastwq/res/ok.png -------------------------------------------------------------------------------- /addons21/fastwq/res/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons21/fastwq/res/setting.png -------------------------------------------------------------------------------- /addons21/fastwq/res/wqicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons21/fastwq/res/wqicon.png -------------------------------------------------------------------------------- /addons21/fastwq/service/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from .manager import ServiceManager 21 | from .pool import ServicePool 22 | from .base import QueryResult, copy_static_file 23 | 24 | service_manager = ServiceManager() # Service Manager 25 | service_pool = ServicePool(service_manager) # Service Instance Pool Manager -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/TLD.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | import re 4 | import random 5 | from ..base import * 6 | 7 | DICT_PATH = u"/Users/brian/Documents/mdx/The Little Dict/TLD.mdx" # u'E:\\BaiduYunDownload\\mdx\\L6mp3.mdx' 8 | 9 | 10 | @register([u'本地词典-The Little Dict', u'The Little Dict']) 11 | class TLD(MdxService): 12 | 13 | def __init__(self): 14 | dict_path = DICT_PATH 15 | # if DICT_PATH is a path, stop auto detect 16 | if not dict_path: 17 | from ...service import service_manager, service_pool 18 | for clazz in service_manager.mdx_services: 19 | service = service_pool.get(clazz.__unique__) 20 | title = service.builder._title if service and service.support else u'' 21 | service_pool.put(service) 22 | if title.startswith(u'TLD'): 23 | dict_path = service.dict_path 24 | break 25 | super(TLD, self).__init__(dict_path) 26 | 27 | @property 28 | def title(self): 29 | return getattr(self, '__register_label__', self.unique) 30 | 31 | 32 | def get_html_all(self): 33 | html = self.get_html() 34 | if not html: 35 | self.word = self.word.lower() 36 | html = self.get_html() 37 | if not html: 38 | self.word = self.word.capitalize() 39 | html = self.get_html() 40 | if not html: 41 | self.word = self.word.upper() 42 | html = self.get_html() 43 | return html 44 | 45 | 46 | @export('iWeb_Rank') 47 | def iweb_rank(self): 48 | m = re.findall(r'
    ', self.get_html_all()) 64 | if m: 65 | soup = parse_html(m[0]) 66 | 67 | el_list = soup.findAll('div', {'class':'coca2'}) 68 | def_distribution = '' 69 | if el_list: 70 | def_distribution = str(el_list[0]) 71 | el_list = soup.findAll('div', {'class':'gdc'}) 72 | cn_def = '' 73 | if el_list: 74 | cn_def = str(el_list[0]) 75 | return def_distribution + cn_def 76 | return '' 77 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/addons21/fastwq/service/dict/__init__.py -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/baicizhan.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import json 3 | import os 4 | from collections import defaultdict 5 | from ..base import * 6 | 7 | 8 | @register([u'百词斩', u'Baicizhan']) 9 | class Baicizhan(WebService): 10 | 11 | bcz_download_mp3 = True 12 | bcz_download_img = True 13 | 14 | def __init__(self): 15 | super(Baicizhan, self).__init__() 16 | 17 | def _get_from_api(self): 18 | url = u"http://mall.baicizhan.com/ws/search?w={}".format(self.quote_word) 19 | result = { 20 | "accent": u"", 21 | "img": u"", 22 | "mean_cn": u"", 23 | "st": u"", 24 | "sttr": u"", 25 | "tv": u"", 26 | "word": u"", 27 | "df": u'', 28 | } 29 | try: 30 | html = self.get_response(url, timeout=5)#urllib2.urlopen(url, timeout=5).read() 31 | result.update(json.loads(html)) 32 | except: 33 | pass 34 | return self.cache_this(result) 35 | 36 | @export('PRON') 37 | def fld_phonetic(self): 38 | url = u'http://baicizhan.qiniucdn.com/word_audios/{}.mp3'.format(self.quote_word) 39 | audio_name = get_hex_name(self.unique.lower(), url, 'mp3') 40 | if self.bcz_download_mp3: 41 | if os.path.exists(audio_name) or self.download(url, audio_name, 5): 42 | with open(audio_name, 'rb') as f: 43 | if f.read().strip() == '{"error":"Document not found"}': 44 | res = '' 45 | else: 46 | res = self.get_anki_label(audio_name, 'audio') 47 | if not res: 48 | os.remove(audio_name) 49 | else: 50 | res = '' 51 | return res 52 | else: 53 | return url 54 | 55 | @export('PHON') 56 | def fld_phon(self): 57 | return self._get_field('accent') 58 | 59 | @export('IMAGE') 60 | def fld_img(self): 61 | url = self._get_field('img') 62 | if url and self.bcz_download_img: 63 | filename = url[url.rindex('/') + 1:] 64 | if os.path.exists(filename) or self.download(url, filename): 65 | return self.get_anki_label(filename, 'img') 66 | #return self.get_anki_label(url, 'img') 67 | return '' 68 | 69 | @export([u'象形', u'Pictogram']) 70 | def fld_df(self): 71 | url = self._get_field('df') 72 | if url and self.bcz_download_img: 73 | filename = url[url.rindex('/') + 1:] 74 | if os.path.exists(filename) or self.download(url, filename): 75 | return self.get_anki_label(filename, 'img') 76 | #return self.get_anki_label(url, 'img') 77 | return '' 78 | 79 | @export(u'DEF') 80 | def fld_mean(self): 81 | return self._get_field('mean_cn') 82 | 83 | @export(u'EXAMPLE') 84 | def fld_st(self): 85 | return self._get_field('st') 86 | 87 | @export('TRANS') 88 | def fld_sttr(self): 89 | return self._get_field('sttr') 90 | 91 | @export([u'单词tv', u'TV']) 92 | def fld_tv_url(self): 93 | video = self._get_field('tv') 94 | if video: 95 | return self.get_anki_label(video, 'video') 96 | return '' 97 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/baidufy.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import xml.etree.ElementTree 3 | from ..base import WebService, export, register, with_styles 4 | import requests 5 | import hashlib 6 | import random 7 | import json 8 | 9 | 10 | @register([u'百度翻译', u'Baidu-Translate']) 11 | class BaiduFy(WebService): 12 | 13 | def __init__(self): 14 | super(BaiduFy, self).__init__() 15 | 16 | def _get_from_api(self, lang='fr'): 17 | url = u'http://api.fanyi.baidu.com/api/trans/vip/translate' 18 | result = { 19 | 'explains': '', 20 | } 21 | data = { 22 | 'q': '', 23 | 'from': 'en', 24 | 'to': 'zh', 25 | 'appid': '20190502000293467', 26 | 'salt': '', 27 | 'sign': '', 28 | } 29 | headers = { 30 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063', 31 | } 32 | 33 | miyao = 'QEl8tPQXtgn35Dzxubk6' 34 | try: 35 | data['q'] = self.word 36 | data['salt'] = str(random.randint(0, 100)) 37 | strs = data['appid'] + data['q'] + data['salt'] + miyao 38 | data['sign'] = hashlib.md5(strs.encode('utf-8')).hexdigest() 39 | html = requests.post(url, data=data, headers=headers) 40 | explains = json.loads(html.content)['trans_result'][0]['dst'] 41 | result.update({'explains': explains}) 42 | except: 43 | pass 44 | return self.cache_this(result) 45 | 46 | @export([u'翻译结果', 'translation']) 47 | def fld_explains(self): 48 | return self.cache_result('explains') if self.cached('explains') else \ 49 | self._get_from_api().get('explains', '') 50 | @export([u'英式发音', 'uk']) 51 | def fld_uk_audio(self): 52 | audiourl = 'http://tts.baidu.com/text2audio?lan=uk&pid=101&ie=UTF-8&text={0}&spd=4'.format(self.quote_word) 53 | audio = requests.get(audiourl) 54 | name = 'bdfy_uk'+hashlib.md5(audiourl.encode('utf-8')).hexdigest()+'.mp3' 55 | with open(name,'wb') as f: 56 | f.write(audio.content) 57 | return self.get_anki_label(name, 'audio') 58 | @export([u'美式发音', 'en']) 59 | def fld_en_audio(self): 60 | audiourl = 'http://tts.baidu.com/text2audio?lan=en&pid=101&ie=UTF-8&text={0}&spd=4'.format(self.quote_word) 61 | audio = requests.get(audiourl) 62 | name = 'bdfy_en'+hashlib.md5(audiourl.encode('utf-8')).hexdigest()+'.mp3' 63 | with open(name,'wb') as f: 64 | f.write(audio.content) 65 | return self.get_anki_label(name, 'audio') -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/bing.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | import os 4 | from ..base import * 5 | 6 | bing_download_mp3 = True 7 | 8 | @register([u'Bing', u'Bing']) 9 | class Bing(WebService): 10 | 11 | def __init__(self): 12 | super(Bing, self).__init__() 13 | 14 | def _get_from_api(self): 15 | data = self.get_response(u"https://cn.bing.com/dict/search?q={}&mkt=zh-cn".format(self.quote_word)) 16 | soup = parse_html(data) 17 | result = { 18 | 'pronunciation': {'AmE': '', 'BrE': '', 'AmEmp3': '', 'BrEmp3': ''}, 19 | 'def': [], 20 | 'sams': [], 21 | } 22 | 23 | #音 24 | element = soup.find('div', class_='hd_tf_lh') 25 | if element: 26 | audios = element.find_all('a') 27 | #美式英标 28 | tag = element.find('div', class_='hd_prUS') 29 | if tag: 30 | result['pronunciation']['AmE'] = str(tag) 31 | #美音 32 | if audios: 33 | tag = audios[0] 34 | audio_url = tag.get('onclick') 35 | if audio_url: 36 | result['pronunciation']['AmEmp3'] = u''.join(re.findall(r'https://.*\.mp3', audio_url)) 37 | 38 | #英式音标 39 | tag = element.find('div', class_='hd_pr') 40 | if tag: 41 | result['pronunciation']['BrE'] = str(tag) 42 | #英音 43 | if audios: 44 | tag = audios[1] 45 | audio_url = tag.get('onclick') 46 | if audio_url: 47 | result['pronunciation']['BrEmp3'] = u''.join(re.findall(r'https://.*\.mp3', audio_url)) 48 | 49 | #释义 50 | element = soup.find('div', class_='qdef') 51 | if element: 52 | element = getattr(element, 'ul', '') 53 | if element: 54 | result['def'] = u''.join([str(content) for content in element.contents]) 55 | 56 | #例句 57 | element = soup.find('div', id='sentenceSeg') 58 | if element: 59 | #英文例句 60 | tags = element.find_all('div', {"class": 'sen_en'}) 61 | result['sams'] = [{'eng': u''.join(tag.find_all(text=True))} for tag in tags] 62 | #例句翻译 63 | tags = element.find_all('div', {"class": 'sen_cn'}) 64 | for i, tag in enumerate(tags): 65 | result['sams'][i]['chn'] = u''.join(tag.find_all(text=True)) 66 | 67 | return self.cache_this(result) 68 | 69 | @with_styles(css='.pos{font-weight:bold;margin-right:4px;}', need_wrap_css=True, wrap_class='bing') 70 | def _css(self, val): 71 | return val 72 | 73 | @export('AME_PHON') 74 | def fld_phonetic_us(self): 75 | seg = self._get_field('pronunciation') 76 | return seg.get('AmE', u'') if seg else u'' 77 | 78 | @export('BRE_PHON') 79 | def fld_phonetic_uk(self): 80 | seg = self._get_field('pronunciation') 81 | return seg.get('BrE', u'') if seg else u'' 82 | 83 | def _fld_mp3(self, fld): 84 | seg = self._get_field('pronunciation') 85 | audio_url = seg[fld] if seg else u'' 86 | if bing_download_mp3 and audio_url: 87 | filename = get_hex_name('bing', audio_url, 'mp3') 88 | if os.path.exists(filename) or self.net_download(filename, audio_url): 89 | return self.get_anki_label(filename, 'audio') 90 | return '' 91 | 92 | @export('AME_PRON') 93 | def fld_mp3_us(self): 94 | return self._fld_mp3('AmEmp3') 95 | 96 | @export('BRE_PRON') 97 | def fld_mp3_uk(self): 98 | return self._fld_mp3('BrEmp3') 99 | 100 | @export('DEF') 101 | def fld_definition(self): 102 | val = self._get_field('def') 103 | if val == None or val == '': 104 | return '' 105 | return self._css(val) 106 | 107 | @export('EXAMPLE') 108 | def fld_samples(self): 109 | max_numbers = 10 110 | segs = self._get_field('sams') 111 | if segs: 112 | sentences = u'' 113 | for i, seg in enumerate(segs): 114 | sentences += u"""
  • 115 |
    {0}.{1}
    116 |
    {2}
    117 |
  • """.format(i+1, seg['eng'], seg['chn']) 118 | if i == 9: 119 | break 120 | if sentences: 121 | return u"""
    122 |
    123 |
      {0}
    124 |
    125 |
    """.format(sentences) 126 | return u'' 127 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/bing3tp.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import json 3 | import re 4 | import os 5 | from ..base import * 6 | 7 | bing_download_mp3 = True 8 | 9 | @register([u'Bing xtk', u'Bing xtk']) 10 | class BingXtk(WebService): 11 | 12 | def __init__(self): 13 | super(BingXtk, self).__init__() 14 | 15 | def _get_from_api(self): 16 | result = { 17 | 'pronunciation': {'AmE': '', 'BrE': '', 'AmEmp3': '', 'BrEmp3': ''}, 18 | 'def': [], 19 | 'sams': [], 20 | } 21 | headers = { 22 | 'Accept-Language': 'en-US,zh-CN;q=0.8,zh;q=0.6,en;q=0.4', 23 | 'User-Agent': 'WordQuery Addon (Anki)', 24 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'} 25 | url = u'http://xtk.azurewebsites.net/BingDictService.aspx?Word={}'.format(self.quote_word) 26 | try: 27 | result.update(json.loads(self.get_response(url, headers=headers, timeout=10))) 28 | except: 29 | pass 30 | return self.cache_this(result) 31 | 32 | @export('AME_PHON') 33 | def fld_phonetic_us(self): 34 | seg = self._get_field('pronunciation') 35 | phon = seg.get('AmE', u'') if seg else u'' 36 | if phon and phon[0:1] not in '/[': 37 | return u'/{}/'.format(phon) 38 | return u'' 39 | 40 | @export('BRE_PHON') 41 | def fld_phonetic_uk(self): 42 | seg = self._get_field('pronunciation') 43 | phon = seg.get('BrE', u'') if seg else u'' 44 | if phon and phon[0:1] not in '/[': 45 | return u'/{}/'.format(phon) 46 | return u'' 47 | 48 | def _fld_mp3(self, fld): 49 | seg = self._get_field('pronunciation') 50 | audio_url = seg[fld] if seg else u'' 51 | if bing_download_mp3 and audio_url: 52 | filename = get_hex_name('bing', audio_url, 'mp3') 53 | if os.path.exists(filename) or self.net_download(filename, audio_url): 54 | return self.get_anki_label(filename, 'audio') 55 | return '' 56 | 57 | @export('AME_PRON') 58 | def fld_mp3_us(self): 59 | return self._fld_mp3('AmEmp3') 60 | 61 | @export('BRE_PRON') 62 | def fld_mp3_uk(self): 63 | return self._fld_mp3('BrEmp3') 64 | 65 | @with_styles(css='.pos{font-weight:bold;margin-right:4px;}', need_wrap_css=True, wrap_class='bing') 66 | def _css(self, val): 67 | return val 68 | 69 | @export('DEF') 70 | def fld_definition(self): 71 | segs = self._get_field('defs') 72 | if isinstance(segs, list) and len(segs) > 0: 73 | val = u'
    '.join([u'''{0} 74 | {1}'''.format(seg['pos'], seg['def']) for seg in segs]) 75 | return self._css(val) 76 | return '' 77 | 78 | @export('EXAMPLE') 79 | def fld_samples(self): 80 | max_numbers = 10 81 | segs = self._get_field('sams') 82 | if segs: 83 | sentences = u'' 84 | for i, seg in enumerate(segs): 85 | sentences += u"""
  • 86 |
    {0}
    87 |
    {1}
    88 |
  • """.format(seg['eng'], seg['chn']) 89 | if i == 9: 90 | break 91 | if sentences: 92 | return u"""
    93 |
    94 |
      {0}
    95 |
    96 |
    """.format(sentences) 97 | return u'' 98 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/bingimg.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | import json 4 | from ..base import * 5 | 6 | 7 | @register([u'必应图片', u'Bing-Images']) 8 | class Bing_Images(WebService): 9 | 10 | bing_download_img = True 11 | 12 | def __init__(self): 13 | super(Bing_Images, self).__init__() 14 | 15 | def _get_from_api(self): 16 | url = u"http://cn.bing.com/images/search?q={}".format(self.quote_word) 17 | html = self.get_response(url, timeout=10) 18 | soup = parse_html(html) 19 | result = { 20 | 'img': '', 21 | } 22 | 23 | #图片连接 24 | tag = soup.find('a', class_='iusc') 25 | if tag: 26 | try: 27 | data = json.loads(tag.get('m')) 28 | result['img'] = data.get('turl', u'') 29 | except: 30 | pass 31 | 32 | return self.cache_this(result) 33 | 34 | @export([u'图片', u'Image']) 35 | def fld_pinyin(self): 36 | url = self._get_field('img') 37 | if url and self.bing_download_img: 38 | filename = get_hex_name(self.unique.lower(), url, 'jpg') 39 | if os.path.exists(filename) or self.download(url, filename): 40 | return self.get_anki_label(filename, 'img') 41 | return '' 42 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/cambridge_cs.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from ..base import * 3 | from .cambridge import Cambridge 4 | 5 | @register([u'剑桥词典-英汉简', u'Cambridge(英汉简)']) 6 | class CambridgeCS(Cambridge): 7 | 8 | def __init__(self): 9 | super(CambridgeCS, self).__init__() 10 | 11 | def _get_url(self): 12 | return u'https://dictionary.cambridge.org/us/dictionary/english-chinese-simplified/' 13 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/cambridge_ct.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from ..base import * 3 | from .cambridge import Cambridge 4 | 5 | @register([u'剑桥词典-英汉繁', u'Cambridge(英汉繁)']) 6 | class CambridgeCT(Cambridge): 7 | 8 | def __init__(self): 9 | super(CambridgeCT, self).__init__() 10 | 11 | def _get_url(self): 12 | return u'https://dictionary.cambridge.org/us/dictionary/english-chinese-traditional/' 13 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/cambridge_ee.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from ..base import * 3 | from .cambridge import Cambridge 4 | 5 | @register([u'剑桥词典-英英', u'Cambridge(English)']) 6 | class CambridgeEE(Cambridge): 7 | 8 | def __init__(self): 9 | super(CambridgeEE, self).__init__() 10 | 11 | def _get_url(self): 12 | return u'https://dictionary.cambridge.org/dictionary/english/' 13 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/dreye.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | import os 4 | from ..base import * 5 | 6 | dreye_download_mp3 = True 7 | 8 | @register([u'译典通', u'Dr.eye']) 9 | class Dreye(WebService): 10 | 11 | def __init__(self): 12 | super(Dreye, self).__init__() 13 | 14 | def _get_from_api(self): 15 | data = self.get_response(u"https://yun.dreye.com/dict_new/dict.php?w={}&hidden_codepage=01".format(self.quote_word)) 16 | soup = parse_html(data) 17 | result = { 18 | 'phon': '', 19 | 'pron': '', 20 | 'pos': '', 21 | 'def': '' 22 | } 23 | 24 | #音标 25 | element = soup.find('span', class_='phonetic') 26 | if element: 27 | result['phon'] = element.get_text() 28 | 29 | # 发音 30 | mp3_regexp = re.compile(r'var *RealSoundPath += +"(.*)";') 31 | mp3_match = mp3_regexp.search(data.decode('utf-8')) 32 | if mp3_match: 33 | result['pron'] = u'{}'.format(mp3_match.group(1)) 34 | #动变 35 | element = soup.find('div', id='digest') 36 | if element: 37 | result['pos'] = u'{}'.format(str(element)) 38 | #释义 39 | element = soup.find('div', id='usual') 40 | if element: 41 | result['def'] = u'{}'.format(str(element)) 42 | 43 | return self.cache_this(result) 44 | 45 | @with_styles(need_wrap_css=True, cssfile='_dreye.css') 46 | def _css(self, val): 47 | return val 48 | 49 | @export('PHON') 50 | def fld_phonetic_us(self): 51 | return self._get_field('phon') 52 | 53 | @export('PRON') 54 | def fld_mp3(self): 55 | audio_url = self._get_field('pron') 56 | if dreye_download_mp3 and audio_url: 57 | filename = get_hex_name('dreye', audio_url, 'mp3') 58 | if os.path.exists(filename) or self.net_download(filename, audio_url): 59 | return self.get_anki_label(filename, 'audio') 60 | return '' 61 | 62 | @export([u'摘要', u'Digest']) 63 | def fld_pos(self): 64 | val = self._get_field('pos') 65 | if val == None or val == '': 66 | return '' 67 | return self._css(val) 68 | 69 | @export('DEF') 70 | def fld_definition(self): 71 | val = self._get_field('def') 72 | if val == None or val == '': 73 | return '' 74 | return self._css(val) 75 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/esdict.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import base64 4 | import re 5 | import urllib.request as urllib2 6 | import os 7 | from ..base import * 8 | 9 | css = '' 10 | 11 | @register([u'西语助手', u'esdict']) 12 | class Esdict(WebService): 13 | 14 | def __init__(self): 15 | super(Esdict, self).__init__() 16 | 17 | def _get_from_api(self): 18 | url = 'https://www.esdict.cn/mdicts/es/{}'.format(self.quote_word) 19 | try: 20 | result = {} 21 | html = self.get_response(url, timeout=5) 22 | soup = parse_html(html) 23 | 24 | def _get_from_element(dict, key, soup, tag, id=None, class_=None): 25 | baseURL = 'https://www.esdict.cn/' 26 | # element = soup.find(tag, id=id, class_=class_) # bs4 27 | if id: 28 | element = soup.find(tag, {"id": id}) 29 | if class_: 30 | element = soup.find(tag, {"class": class_}) 31 | if element: 32 | dict[key] = str(element) 33 | dict[key] = re.sub( 34 | r'href="/', 'href="' + baseURL, dict[key]) 35 | dict[key] = re.sub(r'声明:.*。', '', dict[key]) 36 | dict[key] = dict[key] 37 | return dict 38 | 39 | # '[bɔ̃ʒur]' 40 | result = _get_from_element( 41 | result, 'phonitic', soup, 'span', class_='Phonitic') 42 | # '
    ' 43 | result = _get_from_element( 44 | result, 'fccf', soup, 'div', id='FCChild') # 西汉-汉西词典 45 | result = _get_from_element( 46 | result, 'example', soup, 'div', id='LJChild') # 西语例句库 47 | result = _get_from_element( 48 | result, 'syn', soup, 'div', id='SYNChild') # 近义、反义、派生词典 49 | result = _get_from_element( 50 | result, 'ff', soup, 'div', id='FFChild') # 西西词典 51 | result = _get_from_element( 52 | result, 'fe', soup, 'div', id='FEChild') # 西英词典 53 | 54 | return self.cache_this(result) 55 | except Exception as e: 56 | return {} 57 | 58 | @export([u'真人发音', u'Real person pronounciation']) 59 | def fld_sound(self): 60 | url = 'https://api.frdic.com/api/v2/speech/speakweb?langid=es&txt=QYN{word}'.format( 61 | word=urllib2.quote(base64.b64encode(self.word.encode('utf-8'))) 62 | ) 63 | filename = get_hex_name(self.unique.lower(), url, 'mp3') 64 | if os.path.exists(filename) or self.net_download(filename, url): 65 | return self.get_anki_label(filename, 'audio') 66 | return '' 67 | 68 | @export('PHON') 69 | def fld_phonetic(self): 70 | return self._get_field('phonitic') 71 | 72 | @export([u'西汉-汉西词典', u'Spanish-chinese/chinese-spanish dictionary']) 73 | @with_styles(css=css) 74 | def fld_fccf(self): 75 | return self._get_field('fccf') 76 | 77 | @export([u'西语例句库', u'Spanish examples']) 78 | @with_styles(css=css) 79 | def fld_example(self): 80 | return self._get_field('example') 81 | 82 | @export([u'近义、反义、派生词典', u'Synonyms, antonyms, derivatives']) 83 | def fld_syn(self): 84 | return self._get_field('syn') 85 | 86 | @export([u'西西词典', u'Spanish-spanish dictionary']) 87 | @with_styles(css=css) 88 | def fld_ff(self): 89 | return self._get_field('ff') 90 | 91 | @export([u'西英词典', u'Spanish-english dictionary']) 92 | @with_styles(css=css) 93 | def fld_fe(self): 94 | return self._get_field('fe') 95 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/frdic.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import base64 3 | import re 4 | import urllib.request as urllib2 5 | import os 6 | from ..base import * 7 | 8 | 9 | css = '' 10 | 11 | @register([u'法语助手', u'frdic']) 12 | class Frdic(WebService): 13 | 14 | def __init__(self): 15 | super(Frdic, self).__init__() 16 | 17 | def _get_from_api(self): 18 | url = 'http://www.frdic.com/dicts/fr/{}'.format(self.quote_word) 19 | try: 20 | result = {} 21 | html = self.get_response(url, timeout=5) 22 | soup = parse_html(html) 23 | 24 | def _get_from_element(dict, key, soup, tag, id=None, class_=None): 25 | baseURL = 'http://www.frdic.com/' 26 | # element = soup.find(tag, id=id, class_=class_) # bs4 27 | if id: 28 | element = soup.find(tag, {"id": id}) 29 | if class_: 30 | element = soup.find(tag, {"class": class_}) 31 | if element: 32 | dict[key] = str(element) 33 | dict[key] = re.sub( 34 | r'href="/', 'href="' + baseURL, dict[key]) 35 | dict[key] = re.sub(r'声明:.*。', '', dict[key]) 36 | dict[key] = dict[key] 37 | return dict 38 | 39 | # '[bɔ̃ʒur]' 40 | result = _get_from_element( 41 | result, 'phonitic', soup, 'span', class_='Phonitic') 42 | # '
    ' 43 | result = _get_from_element( 44 | result, 'fccf', soup, 'div', id='ExpFCChild') # 法汉-汉法词典 45 | result = _get_from_element( 46 | result, 'example', soup, 'div', id='TingLijuChild') # 法语例句库 47 | result = _get_from_element( 48 | result, 'syn', soup, 'div', id='SYNChild') # 近义、反义、派生词典 49 | result = _get_from_element( 50 | result, 'ff', soup, 'div', id='FFChild') # 法法词典 51 | result = _get_from_element( 52 | result, 'fe', soup, 'div', id='FEChild') # 法英词典 53 | 54 | return self.cache_this(result) 55 | except Exception as e: 56 | return {} 57 | 58 | @export([u'真人发音', u'Real person pronunciation']) 59 | def fld_sound(self): 60 | url = 'https://api.frdic.com/api/v2/speech/speakweb?langid=fr&txt=QYN{word}'.format( 61 | word=urllib2.quote(base64.b64encode(self.word.encode('utf-8'))) 62 | ) 63 | filename = get_hex_name(self.unique.lower(), url, 'mp3') 64 | if os.path.exists(filename) or self.net_download(filename, url): 65 | return self.get_anki_label(filename, 'audio') 66 | return '' 67 | @export('PHON') 68 | def fld_phonetic(self): 69 | return self._get_field('phonitic') 70 | 71 | @export([u'法汉-汉法词典', u'French-chinese/chinese-french dictionary']) 72 | def fld_fccf(self): 73 | return self._get_field('fccf') 74 | 75 | @export([u'法语例句库', u'French examples']) 76 | @with_styles(css=css) 77 | def fld_example(self): 78 | return self._get_field('example') 79 | 80 | @export([u'近义、反义、派生词典', u'Synonyms, antonyms, derivatives']) 81 | @with_styles(css=css) 82 | def fld_syn(self): 83 | return self._get_field('syn') 84 | 85 | @export([u'法法词典', u'French-french dictionary']) 86 | @with_styles(css=css) 87 | def fld_ff(self): 88 | return self._get_field('ff') 89 | 90 | @export([u'法英词典', u'French-english dictionary']) 91 | @with_styles(css=css) 92 | def fld_fe(self): 93 | return self._get_field('fe') 94 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/lgmcw_freq.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | import re 4 | import random 5 | from ..base import * 6 | 7 | DICT_PATH = u"/Users/brian/Documents/mdx/lgmcw_Sound++/SoundMobile.mdx" # u'E:\\BaiduYunDownload\\mdx\\L6mp3.mdx' 8 | 9 | 10 | @register([u'本地词典-lgmcw_Sound++', u'lgmcw_Sound++']) 11 | class lgmcw_Sound(MdxService): 12 | 13 | def __init__(self): 14 | dict_path = DICT_PATH 15 | # if DICT_PATH is a path, stop auto detect 16 | if not dict_path: 17 | from ...service import service_manager, service_pool 18 | for clazz in service_manager.mdx_services: 19 | service = service_pool.get(clazz.__unique__) 20 | title = service.builder._title if service and service.support else u'' 21 | service_pool.put(service) 22 | if title.startswith(u'SoundMobile'): 23 | dict_path = service.dict_path 24 | break 25 | super(lgmcw_Sound, self).__init__(dict_path) 26 | 27 | @property 28 | def title(self): 29 | return getattr(self, '__register_label__', self.unique) 30 | 31 | def get_html_all(self): 32 | html = self.get_html() 33 | if not html: 34 | self.word = self.word.lower() 35 | html = self.get_html() 36 | if not html: 37 | self.word = self.word.capitalize() 38 | html = self.get_html() 39 | if not html: 40 | self.word = self.word.upper() 41 | html = self.get_html() 42 | return html 43 | 44 | 45 | @export('BNC_freq') 46 | def bnc_freq(self): 47 | html = self.get_html_all() 48 | if html: 49 | freq = re.search(r'(.*?)', html) 50 | if freq: 51 | return freq[1].strip() 52 | return '' 53 | 54 | @export('IWEB_freq') 55 | def iweb_freq(self): 56 | html = self.get_html_all() 57 | if html: 58 | print(html) 59 | freq = re.search(r'(.*?)', html) 60 | if freq: 61 | return freq[1].strip() 62 | return '' 63 | 64 | @export('ECO_freq') 65 | def eco_freq(self): 66 | html = self.get_html_all() 67 | if html: 68 | freq = re.search(r'(.*?)', html) 69 | if freq: 70 | return freq[1].strip() 71 | return '' 72 | 73 | @export('COCA_freq') 74 | def coca_freq(self): 75 | html = self.get_html_all() 76 | if html: 77 | freq = re.search(r'(.*?)', html) 78 | if freq: 79 | return freq[1].strip() 80 | return '' 81 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/ludwig.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import random 3 | from ..base import * 4 | 5 | oxford_download_mp3 = True 6 | 7 | @register(u'Ludwig') 8 | class Ludwig(WebService): 9 | 10 | def __init__(self): 11 | super(Ludwig, self).__init__() 12 | 13 | def _get_from_api(self): 14 | data = self.get_response(u'https://ludwig.guru/s/{}'.format(self.quote_word)) 15 | soup = parse_html(data) 16 | result = { 17 | 'def': u'', 18 | 'examples': [] 19 | } 20 | 21 | # def 22 | element = soup.find('div', class_='-id-__definition--1E88I') 23 | if element: 24 | e_list = element.find_all('p') 25 | if e_list: 26 | result['def'] = u''.join(str(c) for c in e_list) 27 | 28 | # examples 29 | e_list = soup.find_all('p', class_='-id-__exact--SVDfq') 30 | if e_list: 31 | e_arr = [] 32 | for n in e_list: 33 | e_arr.append(str(n.get_text())) 34 | result['examples'] = e_arr 35 | return self.cache_this(result) 36 | 37 | @export('DEF') 38 | def fld_definate(self): 39 | return self._get_field('def') 40 | 41 | @export('EXAMPLE') 42 | def fld_example(self): 43 | return self._range_examples([i for i in range(0, 100)]) 44 | 45 | @export([u'随机例句', u'Random example']) 46 | def fld_random_example(self): 47 | return self._range_examples() 48 | 49 | @export([u'首2个例句', u'First 2 examples']) 50 | def fld_first2_example(self): 51 | return self._range_examples([0, 1]) 52 | 53 | def _range_examples(self, range_arr=None): 54 | maps = self._get_field('examples') 55 | if maps: 56 | range_arr = range_arr if range_arr else [random.randrange(0, len(maps) - 1, 1)] 57 | my_str = u'' 58 | for i,n in enumerate(maps): 59 | if i in range_arr: 60 | my_str += u'
  • {}
  • '.format(n) 61 | return u'
      {}
    '.format(my_str) 62 | return u'' 63 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/minidict.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from ..base import * 4 | 5 | 6 | @register([u'海词迷你词典', u'dict.cn']) 7 | class MiniDict(WebService): 8 | 9 | def __init__(self): 10 | super(MiniDict, self).__init__() 11 | 12 | def _get_from_api(self): 13 | data = self.get_response(u"http://apii.dict.cn/mini.php?q={}".format(self.quote_word)) 14 | soup = parse_html(data) 15 | result = { 16 | 'expressions': u'', 17 | 'sentences': u'', 18 | 'variations': u'', 19 | 'phonetic': u'', 20 | } 21 | 22 | # 音标 23 | tag = soup.find('span', class_='p') 24 | if tag: 25 | result['phonetic'] = str(tag.get_text()) 26 | tag.decompose() 27 | 28 | # 基本释义 29 | tag = soup.find('div', id='e') 30 | if tag: 31 | result['expressions'] = str(tag) 32 | tag.decompose() 33 | 34 | # 例句与用法 35 | tag = soup.find('div', id='s') 36 | if tag: 37 | result['sentences'] = str(tag) 38 | tag.decompose() 39 | 40 | # 词形变化 41 | tag = soup.find('div', id='t') 42 | if tag: 43 | result['variations'] = str(tag) 44 | tag.decompose() 45 | 46 | return self.cache_this(result) 47 | 48 | @export('PHON') 49 | def fld_phonetic(self): 50 | return self._get_field('phonetic') 51 | 52 | @export([u'基本释义', u'Expressions']) 53 | def fld_explains(self): 54 | return self._get_field('expressions') 55 | 56 | @export([u'例句与用法', u'Examples and patterns']) 57 | @with_styles(css='em {color:#cc0066;font-style:normal;}', need_wrap_css=True, wrap_class='minidict') 58 | def fld_sentences(self): 59 | return self._get_field('sentences') 60 | 61 | @export([u'词形变化', u'Inflections']) 62 | def fld_variations(self): 63 | return self._get_field('variations') 64 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/mw.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import os 4 | import re 5 | from bs4 import Tag 6 | from ..base import * 7 | from ...utils.misc import format_multi_query_word 8 | 9 | @register([u'韦氏词典', u'Merriam-Webster']) 10 | class MerriamWebster(WebService): 11 | 12 | def __init__(self): 13 | super(MerriamWebster, self).__init__() 14 | 15 | def _get_from_api(self): 16 | url = 'https://www.merriam-webster.com/dictionary/{}'.format(format_multi_query_word(self.quote_word)) 17 | data = self.get_response(url) 18 | soup = parse_html(data) 19 | 20 | # Top Container 21 | word_info = {} 22 | 23 | sym_div = soup.find('div', {'id': 'synonyms-anchor'}) 24 | for a in sym_div.findAll('a'): 25 | if 'thesaurus' not in a['href']: 26 | a['href'] = 'https://www.merriam-webster.com/dictionary{}'.format(a['href']) 27 | else: 28 | a['href'] = 'https://www.merriam-webster.com{}'.format(a['href']) 29 | word_info['sa'] = str(sym_div) 30 | return self.cache_this(word_info) 31 | 32 | @export([u'同义词反义词', u'Synonyms & Antonyms']) 33 | @with_styles(cssfile='_mw.css') 34 | def fld_sa(self): 35 | return self._get_field('sa') 36 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/oxford.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import os 3 | try: 4 | import urllib2 5 | except: 6 | import urllib.request as urllib2 7 | import json 8 | from aqt.utils import showInfo 9 | from ..base import WebService, export, register, with_styles 10 | 11 | oxford_download_mp3 = True 12 | 13 | @register(u'Oxford') 14 | class Oxford(WebService): 15 | 16 | def __init__(self): 17 | super(Oxford, self).__init__() 18 | 19 | def _get_from_api(self, lang='en'): 20 | app_id = '45aecf84' 21 | app_key = 'bb36fd6a1259e5baf8df6110a2f7fc8f' 22 | headers = {'app_id': app_id, 'app_key': app_key} 23 | word_id = urllib2.quote(self.word.lower().replace(' ', '_')) 24 | url = u'https://od-api.oxforddictionaries.com/api/v1/entries/' + lang + u'/' + word_id 25 | result = {'lexicalEntries': ''} 26 | try: 27 | result.update(json.loads(self.get_response(url, headers=headers, timeout=10))['results'][0]) 28 | except: 29 | pass 30 | return self.cache_this(result) 31 | 32 | @export('DEF') 33 | def fld_definition(self): 34 | try: 35 | return self._get_field('lexicalEntries')[0]['entries'][0]['senses'][0]['definitions'][0] 36 | except: 37 | return '' 38 | 39 | def _fld_pron_mp3(self, audio_url): 40 | if oxford_download_mp3 and audio_url: 41 | filename = get_hex_name(self.unique.lower(), audio_url, 'mp3') 42 | if os.path.exists(filename) or self.net_download(filename, audio_url): 43 | return self.get_anki_label(filename, 'audio') 44 | return '' 45 | 46 | @export('PRON') 47 | def fld_pron(self): 48 | try: 49 | entries = self._get_field('lexicalEntries') 50 | prons_mp3 = '' 51 | for entry in entries: 52 | if 'pronunciations' in entry: 53 | prons = entry['pronunciations'] 54 | pos = entry['lexicalCategory'] 55 | prons_mp3 += '
    ' if prons_mp3 else '' + u'
    '.join( 56 | u'{0}({1}) {2}'.format( 57 | pron['dialects'][0] + ' ' if 'dialects' in pron else '', 58 | pos, 59 | self._fld_pron_mp3(pron['audioFile'])) for pron in prons) 60 | return prons_mp3 61 | except: 62 | return '' 63 | 64 | @export('PHON') 65 | def fld_phon(self): 66 | try: 67 | prons = self._get_field('lexicalEntries')[0]['pronunciations'] 68 | return u'
    '.join(u'{0}{1}'.format(pron['dialects'][0] + ': ' if 'dialects' in pron else '', pron['phoneticSpelling']) for pron in prons) 69 | except: 70 | return '' 71 | 72 | @export('EXAMPLE') 73 | def fld_example(self): 74 | try: 75 | entries = self._get_field('lexicalEntries')[0]['entries'][0]['senses'][0]['examples'] 76 | return u'
    '.join(entry['text'] for entry in entries) 77 | except: 78 | return '' 79 | 80 | @export([u'派生词', u'Derivatives']) 81 | def fld_deriv(self): 82 | try: 83 | entries = self._get_field('lexicalEntries')[0]['derivatives'] 84 | return u', '.join(entry['text'] for entry in entries) 85 | except: 86 | return '' 87 | 88 | @export([u'词性', u'POS']) 89 | def fld_pos(self): 90 | try: 91 | entries = self._get_field('lexicalEntries') 92 | return u', '.join(entry['lexicalCategory'] for entry in entries) 93 | except: 94 | return '' 95 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/remotemdx.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 Liang Feng 4 | # 5 | # Support: Report an issue at https://github.com/finalion/WordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import os 21 | # import ntpath 22 | import re 23 | import urllib 24 | from collections import defaultdict 25 | 26 | from aqt.utils import showInfo, showText 27 | from ..base import QueryResult, WebService, export, register, with_styles 28 | 29 | 30 | @register('MDX_SERVER') 31 | class RemoteMdx(WebService): 32 | 33 | def __init__(self): 34 | super(RemoteMdx, self).__init__() 35 | self.cache = defaultdict(set) 36 | 37 | def active(self, dict_path, word): 38 | self.word = word 39 | self.url = dict_path + \ 40 | '/' if not dict_path.endswith('/') else dict_path 41 | try: 42 | html = self.get_response(self.url + word) 43 | result, js = self.adapt_to_anki(html) 44 | return QueryResult(result=result, js=js) 45 | except: 46 | return QueryResult.default() 47 | 48 | def download_media_files(self, data): 49 | diff = data.difference(self.cache[self.url]) 50 | self.cache[self.url].update(diff) 51 | errors, styles = list(), list() 52 | for each in diff: 53 | basename = os.path.basename(each.replace('\\', os.path.sep)) 54 | saved_basename = '_' + basename 55 | abs_url = urllib.parse.urljoin(self.url, each) 56 | if basename.endswith('.css') or basename.endswith('.js'): 57 | styles.append(saved_basename) 58 | if not os.path.exists(saved_basename): 59 | try: 60 | self.download(abs_url, saved_basename) 61 | except: 62 | errors.append(each) 63 | return errors, styles 64 | 65 | def adapt_to_anki(self, html): 66 | """ 67 | 1. convert the media path to actual path in anki's collection media folder. 68 | 2. remove the js codes 69 | 3. import css, to make sure the css file can be synced. TO VALIDATE! 70 | """ 71 | media_files_set = set() 72 | mcss = re.findall(r'href="(\S+?\.css)"', html) 73 | media_files_set.update(set(mcss)) 74 | mjs = re.findall(r'src="([\w\./]\S+?\.js)"', html) 75 | media_files_set.update(set(mjs)) 76 | msrc = re.findall(r'', html) 77 | media_files_set.update(set(msrc)) 78 | for each in media_files_set: 79 | html = html.replace(each, '_' + each.split('/')[-1]) 80 | errors, styles = self.download_media_files(media_files_set) 81 | html = u'
    '.join([u"".format(style) 82 | for style in styles if style.endswith('.css')]) + html 83 | js = re.findall(r'.*?', html, re.DOTALL) 84 | # for each in js: 85 | # html = html.replace(each, '') 86 | # showText(html) 87 | return str(html), u'\n'.join(js) 88 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/spanishdict.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import os 4 | from bs4 import BeautifulSoup 5 | import requests 6 | from ..base import * 7 | from ...utils.misc import format_multi_query_word 8 | 9 | @register('Span¡shD!ct') 10 | class SpanishDict(WebService): 11 | 12 | def _get_from_api(self): 13 | url = 'https://www.spanishdict.com/translate/{}'.format(format_multi_query_word(self.quote_word)) 14 | page = requests.get(url) 15 | soup = BeautifulSoup(page.text, 'html.parser') 16 | 17 | # Top Container 18 | word_info = {} 19 | 20 | word_info['image_url'] = soup.find_all('img')[1]['src'] 21 | word_info['test'] = str(soup.find_all('img')) 22 | return self.cache_this(word_info) 23 | 24 | @export([u'图片', u'Image']) 25 | def fld_image(self): 26 | #image_url = "https://d25rq8gxcq0p71.cloudfront.net/dictionary-images/300/c5453acf-e0f1-4fe4-9534-607c9aa21e85.jpg" 27 | image_url = self._get_field('image_url') 28 | file_extension = os.path.splitext(image_url)[1][1:].strip().lower() 29 | filename = get_hex_name(self.unique.lower(), image_url, file_extension) 30 | if os.path.exists(filename) or self.download(image_url, filename): 31 | return self.get_anki_label(filename, 'img') 32 | return '' 33 | 34 | @export([u'测试链接', u'test']) 35 | def fld_tst(self): 36 | return self._get_field('test') 37 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/txt.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | 4 | from aqt.utils import showInfo, showText 5 | from ..base import LocalService, export, register, with_styles 6 | 7 | path = u'D:\\dicts\\LDOCE\\d.txt' 8 | 9 | 10 | @register(u'txt测试') 11 | class TxtTest(LocalService): 12 | 13 | def __init__(self): 14 | super(TxtTest, self).__init__(path) 15 | try: 16 | self.handle = open(path, 'rb') 17 | except: 18 | self.handle = None 19 | 20 | @export(u'all') 21 | def fld_phonetic(self): 22 | if not self.handle: 23 | return 24 | for line in self.handle: 25 | line = line.decode("UTF-8") 26 | m = re.search(self.word, line) 27 | if m: 28 | return line 29 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/vocabulary.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import random 3 | from ..base import * 4 | 5 | 6 | @register(u'Vocabulary.com') 7 | class Vocabulary(WebService): 8 | 9 | def __init__(self): 10 | super(Vocabulary, self).__init__() 11 | 12 | def _get_from_api(self): 13 | data = self.get_response(u'https://www.vocabulary.com/dictionary/{}'.format(self.quote_word)) 14 | soup = parse_html(data) 15 | result = { 16 | 'short': u'', 17 | 'long': u'', 18 | } 19 | 20 | # short 21 | element = soup.find('p', class_='short') 22 | if element: 23 | result['short'] = u''.join(str(e) for e in element.contents) 24 | 25 | # long 26 | element = soup.find('p', class_='long') 27 | if element: 28 | result['long'] = u''.join(str(e) for e in element.contents) 29 | 30 | return self.cache_this(result) 31 | 32 | @export([u'简短释义', u'Short definition']) 33 | def fld_definate(self): 34 | return self._get_field('short') 35 | 36 | @export([u'详细释义', u'Long definition']) 37 | def fld_example(self): 38 | return self._get_field('long') 39 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/yahoo.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | from ..base import * 4 | 5 | yahoo_download_mp3 = True 6 | 7 | 8 | @register([u'雅虎奇摩字典', u'Yahoo-Dict']) 9 | class Yahoo_Dict(WebService): 10 | 11 | def __init__(self): 12 | super(Yahoo_Dict, self).__init__() 13 | 14 | def _get_from_api(self): 15 | url = u"https://tw.dictionary.search.yahoo.com/search?p={}".format( 16 | self.quote_word) 17 | html = self.get_response(url, timeout=10) 18 | soup = parse_html(html) 19 | result = { 20 | 'phon': '', 21 | 'def': '', 22 | 'audio_url': '', 23 | 'detail': '', 24 | } 25 | 26 | # 基本 27 | element = soup.find('div', class_='dd cardDesign dictionaryWordCard sys_dict_word_card') 28 | if element: 29 | # 音标 30 | tag = element.find('div', class_='compList ml-25 d-ib') 31 | if tag: 32 | result['phon'] = tag.get_text() 33 | 34 | # 发音 35 | result['audio_url'] = u'https://s.yimg.com/bg/dict/dreye/live/f/{}.mp3'.format( 36 | self.word) 37 | 38 | # 词性及中文解释 39 | tag = element.find('div', class_='compList mb-25 ml-25 p-rel') 40 | if tag: 41 | result['def'] = u'
    ' + \ 42 | str(tag.find('ul')) + u'
    ' 43 | 44 | # 释义 45 | tag = soup.find('div', class_='grp grp-tab-content-explanation tabsContent tab-content-explanation tabActived') 46 | if tag: 47 | result['detail'] = u'
    ' + \ 48 | str(tag.find('ul')) + u'
    ' 49 | 50 | 51 | return self.cache_this(result) 52 | 53 | @with_styles(need_wrap_css=True, cssfile='_yahoo.css') 54 | def _css(self, val): 55 | return val 56 | 57 | @export('PHON') 58 | def fld_pinyin(self): 59 | return self._get_field('phon') 60 | 61 | @export('PRON') 62 | def fld_pron(self): 63 | audio_url = self._get_field('audio_url') 64 | if yahoo_download_mp3 and audio_url: 65 | filename = get_hex_name(self.unique.lower(), audio_url, 'mp3') 66 | if os.path.exists(filename) or self.download(audio_url, filename, 5): 67 | return self.get_anki_label(filename, 'audio') 68 | 69 | return '' 70 | 71 | @export('DEF') 72 | def fld_basic(self): 73 | val = self._get_field('def') 74 | if val is None or val == '': 75 | return '' 76 | return self._css(val) 77 | 78 | @export([u'详细释义', u'Detailed Interpretation']) 79 | def fld_detail(self): 80 | val = self._get_field('detail') 81 | if val is None or val == '': 82 | return '' 83 | return self._css(val) 84 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/youdaofr.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import xml.etree.ElementTree 4 | from ..base import WebService, export, register, with_styles 5 | 6 | 7 | @register([u'有道词典-法语', u'Youdao-French']) 8 | class Youdaofr(WebService): 9 | 10 | def __init__(self): 11 | super(Youdaofr, self).__init__() 12 | 13 | def _get_from_api(self, lang='fr'): 14 | url = (u'http://dict.youdao.com/fsearch?client=deskdict' 15 | '&keyfrom=chrome.extension&pos=-1' 16 | '&doctype=xml&xmlVersion=3.2' 17 | '&dogVersion=1.0&vendor=unknown' 18 | '&appVer=3.1.17.4208' 19 | '&le={0}&q={1}').format(lang, self.quote_word) 20 | result ={ 21 | 'phonetic': '', 22 | 'explains':'', 23 | } 24 | try: 25 | html = self.get_response(url, timeout=5) 26 | # showInfo(str(result)) 27 | doc = xml.etree.ElementTree.fromstring(html) 28 | # fetch explanations 29 | explains = '
    '.join([node.text for node in doc.findall( 30 | ".//custom-translation/translation/content")]) 31 | result.update({'explains': explains}) 32 | except: 33 | pass 34 | return self.cache_this(result) 35 | 36 | @export([u'基本释义', 'Explanations']) 37 | def fld_explains(self): 38 | return self.cache_result('explains') if self.cached('explains') else \ 39 | self._get_from_api().get('explains', '') 40 | 41 | @with_styles(cssfile='_youdao.css', need_wrap_css=True, wrap_class='youdao') 42 | def _get_singledict(self, single_dict, lang='fr'): 43 | url = u"http://m.youdao.com/singledict?q={0}&dict={1}&le={2}&more=false".format( 44 | self.quote_word, single_dict, lang 45 | ) 46 | try: 47 | html = self.get_response(url, timeout=5) 48 | return (u'
    ' 49 | '
    {3}
    ' 50 | '
    ' 51 | '
    ' 52 | '' 53 | '
    ').format( 54 | single_dict, 55 | single_dict, 56 | single_dict, 57 | html.decode('utf-8') 58 | ) 59 | except: 60 | return '' 61 | 62 | @export([u'网络释义', 'Web translation']) 63 | def fld_web_trans(self): 64 | return self._get_singledict('web_trans') 65 | 66 | @export([u'双语例句', 'Bilingual examples']) 67 | def fld_blng_sents_part(self): 68 | return self._get_singledict('blng_sents_part') 69 | 70 | @export([u'百科', 'baike']) 71 | def fld_baike(self): 72 | return self._get_singledict('baike') 73 | 74 | @export([u'汉语词典(中)', 'Chinese dictionary']) 75 | def fld_hh(self): 76 | return self._get_singledict('hh') 77 | -------------------------------------------------------------------------------- /addons21/fastwq/service/dict/youdaoko.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import xml.etree.ElementTree 4 | from ..base import * 5 | 6 | 7 | @register([u'有道词典-韩语', u'Youdao-Korean']) 8 | class Youdaoko(WebService): 9 | 10 | def __init__(self): 11 | super(Youdaoko, self).__init__() 12 | 13 | def _get_from_api(self, lang='ko'): 14 | url = (u'http://dict.youdao.com/fsearch?client=deskdict' 15 | '&keyfrom=chrome.extension&pos=-1' 16 | '&doctype=xml&xmlVersion=3.2' 17 | '&dogVersion=1.0&vendor=unknown' 18 | '&appVer=3.1.17.4208' 19 | '&le={0}&q={1}').format(lang, self.quote_word) 20 | result ={ 21 | 'phonetic': '', 22 | 'explains':'', 23 | } 24 | try: 25 | html = self.get_response(url, timeout=5) 26 | # showInfo(str(result)) 27 | doc = xml.etree.ElementTree.fromstring(html) 28 | # fetch explanations 29 | explains = '
    '.join([node.text for node in doc.findall( 30 | ".//custom-translation/translation/content")]) 31 | result.update({'explains': explains}) 32 | except: 33 | pass 34 | return self.cache_this(result) 35 | 36 | @export([u'基本释义', u'Explanations']) 37 | def fld_explains(self): 38 | return self.cache_result('explains') if self.cached('explains') else \ 39 | self._get_from_api().get('explains', '') 40 | 41 | @with_styles(cssfile='_youdao.css', need_wrap_css=True, wrap_class='youdao') 42 | def _get_singledict(self, single_dict, lang='ko'): 43 | url = u"http://m.youdao.com/singledict?q={0}&dict={1}&le={2}&more=false".format( 44 | self.quote_word, single_dict, lang 45 | ) 46 | try: 47 | html = self.get_response(url, timeout=5) 48 | return (u'
    ' 49 | '
    {3}
    ' 50 | '
    ' 51 | '
    ' 52 | '' 53 | '
    ').format( 54 | single_dict, 55 | single_dict, 56 | single_dict, 57 | html.decode('utf-8') 58 | ) 59 | except: 60 | return '' 61 | 62 | @export([u'网络释义', u'Web translation']) 63 | def fld_web_trans(self): 64 | return self._get_singledict('web_trans') 65 | 66 | @export([u'双语例句', u'Biligual example']) 67 | def fld_blng_sents_part(self): 68 | return self._get_singledict('blng_sents_part') 69 | 70 | @export([u'百科', u'baike']) 71 | def fld_baike(self): 72 | return self._get_singledict('baike') 73 | 74 | @export([u'汉语词典(中)', u'Chinese dictionary']) 75 | def fld_hh(self): 76 | return self._get_singledict('hh') 77 | -------------------------------------------------------------------------------- /addons21/fastwq/service/pool.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from ..utils import Empty, Queue 21 | 22 | 23 | class ServicePool(object): 24 | """ 25 | Service instance pool 26 | """ 27 | def __init__(self, manager): 28 | self.pools = {} 29 | self.manager = manager 30 | 31 | def get(self, unique): 32 | queue = self.pools.get(unique, None) 33 | if queue: 34 | try: 35 | return queue.get(True, timeout=0.1) 36 | except Empty: 37 | pass 38 | 39 | return self.manager.get_service(unique) 40 | 41 | def put(self, service): 42 | if service is None: 43 | return 44 | unique = service.unique 45 | queue = self.pools.get(unique, None) 46 | if queue == None: 47 | queue = Queue() 48 | self.pools[unique] = queue 49 | 50 | queue.put(service) 51 | 52 | def clean(self): 53 | self.pools = {} 54 | -------------------------------------------------------------------------------- /addons21/fastwq/service/static/_bing.css: -------------------------------------------------------------------------------- 1 | ul, 2 | li, 3 | form, 4 | table, 5 | tr, 6 | th, 7 | td, 8 | blockquote { 9 | border: 0; 10 | border-collapse: collapse; 11 | border-spacing: 0; 12 | list-style: none; 13 | margin: 0; 14 | padding: 0 15 | } 16 | .qdef ul { 17 | padding-top: 10px 18 | } 19 | 20 | #sentenceSeg .sen_li { 21 | display: inline-block; 22 | padding-right: 10px; 23 | line-height: 19px; 24 | vertical-align: middle; 25 | margin: 0 0 10px 0; 26 | width: auto 27 | } 28 | 29 | #sentenceSeg .mm_div { 30 | margin-bottom: 10px; 31 | line-height: 19px; 32 | vertical-align: middle; 33 | padding: 0; 34 | display: inline-block 35 | } 36 | .sen_li { 37 | margin: 5px 0 2px 0; 38 | width: 100% 39 | } 40 | 41 | .sen_con { 42 | color: #333; 43 | padding: 2px 0; 44 | font-size: 14px; 45 | line-height: 22px 46 | } 47 | 48 | .sen_con strong { 49 | color: #c00 50 | } 51 | 52 | .sen_count { 53 | font-size: 14px 54 | } 55 | 56 | .sen_count a { 57 | color: #a1a1a1 58 | } 59 | 60 | .sen_count a:hover { 61 | color: #04c 62 | } 63 | 64 | .sen_count a:visited { 65 | color: #639 66 | } 67 | .sen_en>span { 68 | font-size: 14px; 69 | text-decoration: none; 70 | outline: medium none 71 | } 72 | 73 | .sen_en, 74 | .sen_cn, 75 | .sen_ime { 76 | width: 100%; 77 | font-size: 14px; 78 | padding-left: 0; 79 | margin-left: 0 80 | } 81 | 82 | .sen_en { 83 | line-height: 14px; 84 | margin-bottom: 2px; 85 | color: #000 86 | } 87 | 88 | .sen_cn { 89 | line-height: 22px; 90 | margin-bottom: 2px; 91 | color: #777 92 | } 93 | 94 | .sen_ime { 95 | line-height: 17.5px; 96 | color: #777; 97 | margin-bottom: 2px 98 | } 99 | 100 | 101 | .se_d, 102 | .se_def_nu { 103 | line-height: 22px 104 | } 105 | 106 | 107 | .se_d { 108 | width: 20px; 109 | float: left 110 | } 111 | 112 | .se_lis, 113 | .idmdef_li { 114 | margin-left: 20px 115 | } 116 | 117 | 118 | .se_buf { 119 | margin-left: 24px; 120 | float: left; 121 | margin-top: 4px 122 | } 123 | 124 | .se_d, 125 | .def_pa, 126 | .idmdef_li { 127 | color: #000; 128 | font-size: 14px 129 | } 130 | .se_div { 131 | margin-top: 10px; 132 | padding-bottom: 0; 133 | overflow: hidden 134 | } 135 | 136 | .se_li { 137 | list-style-type: none; 138 | padding-top: 0; 139 | padding-left: 0; 140 | clear: both 141 | } 142 | 143 | .se_li1 { 144 | margin-bottom: 5px; 145 | margin-left: 0; 146 | padding-left: 0; 147 | overflow: hidden; 148 | float: left; 149 | max-width: 508px 150 | } 151 | 152 | .se_n_d { 153 | float: left; 154 | width: 24px; 155 | padding-top: 3px 156 | } 157 | 158 | .qdef .hd_prUS, 159 | .qdef .hd_pr { 160 | color: #777; 161 | font-size: 14px 162 | } 163 | 164 | .qdef .pos { 165 | width: 35px; 166 | font-size: 93%; 167 | background-color: #aaa; 168 | color: #fff; 169 | line-height: 18px; 170 | vertical-align: middle; 171 | text-align: center 172 | } 173 | 174 | .qdef .pos1 { 175 | margin-top: 2px 176 | } 177 | 178 | .qdef .web { 179 | background-color: #333 180 | } 181 | 182 | .qdef ul { 183 | padding-top: 10px 184 | } 185 | 186 | .qdef li { 187 | padding-top: 4px; 188 | font-weight: bold 189 | } 190 | 191 | .qdef .def { 192 | padding-left: 15px; 193 | line-height: 20px; 194 | vertical-align: top; 195 | font-size: 14px; 196 | width: 90% 197 | } 198 | 199 | .qdef .def a { 200 | color: #000 201 | } 202 | 203 | .qdef .def a:hover { 204 | color: #04c 205 | } 206 | 207 | .qdef .hd_div1 .p2-1 { 208 | font-weight: bold; 209 | color: #777 210 | } 211 | 212 | .qdef .hd_div1 { 213 | color: #777; 214 | padding-top: 9px 215 | } 216 | 217 | .qdef:after { 218 | clear: both 219 | } 220 | 221 | .qdef .hd_if a { 222 | margin: 0 6px 0 0; 223 | font-size: 14px 224 | } 225 | 226 | .qdef div.simg, 227 | .qdef div.simgmore { 228 | margin-top: 10px 229 | } 230 | 231 | .qdef .simg { 232 | left: 1px 233 | } 234 | 235 | .qdef df_div { 236 | margin-top: 60px 237 | } 238 | 239 | .qdef .hd_tf_lh { 240 | line-height: 19px; 241 | padding-top: 3px 242 | } 243 | 244 | .qdef .wd_div { 245 | margin-top: 10px 246 | } 247 | 248 | .qdef .df_div { 249 | padding-top: 10px 250 | } 251 | 252 | .qdef .hd_area { 253 | float: none; 254 | overflow: hidden; 255 | margin-bottom: 0 256 | }.qdef h1 { 257 | font-size: 100% 258 | } -------------------------------------------------------------------------------- /addons21/fastwq/service/static/_cambridge.css: -------------------------------------------------------------------------------- 1 | .epp-xref { 2 | margin-right: 3px; 3 | padding: 2px 5px; 4 | color: #fff; 5 | font-weight: 700; 6 | font-size: .8em; 7 | min-width: 14px; 8 | text-align: center; 9 | background-color: #444; 10 | border-radius: 8px; 11 | } 12 | 13 | b.def { 14 | line-height: 1.3rem 15 | } 16 | 17 | .examp { 18 | margin: 0 0 5px 0; 19 | line-height: 1.5em; 20 | font-weight: normal; 21 | font-style: italic; 22 | display: block; 23 | margin: 0 24 | } 25 | 26 | li { 27 | display: list-item; 28 | list-style: none; 29 | text-align: -webkit-match-parent; 30 | } 31 | 32 | menu, ol, ul { 33 | padding: 0; 34 | list-style: none; 35 | list-style-position: outside; 36 | } 37 | 38 | ol { 39 | display: block; 40 | list-style: none; 41 | list-style-type: decimal; 42 | } 43 | 44 | dl, menu, ol, ul, dd, p { 45 | margin: 0; 46 | } 47 | -------------------------------------------------------------------------------- /addons21/fastwq/service/static/_yahoo.css: -------------------------------------------------------------------------------- 1 | #results #left #web .dd, #results #right .cardReg .dd { 2 | margin-left: 3px; 3 | margin-right: 3px; 4 | margin-bottom: 6px; 5 | } 6 | 7 | div { 8 | display: block; 9 | } 10 | 11 | li { 12 | display: list-item; 13 | list-style: none; 14 | text-align: -webkit-match-parent; 15 | } 16 | 17 | menu, ol, ul { 18 | padding: 0; 19 | list-style: none; 20 | list-style-position: outside; 21 | } 22 | 23 | ol { 24 | display: block; 25 | list-style: none; 26 | list-style-type: decimal; 27 | } 28 | 29 | dl, menu, ol, ul, dd, p { 30 | margin: 0; 31 | } 32 | 33 | ul, menu, dir { 34 | display: block; 35 | list-style-type: disc; 36 | } 37 | 38 | .dd .mr-25 { 39 | margin-right: 25px; 40 | } 41 | 42 | .mb-12, .mb-12 { 43 | margin-bottom: 12px; 44 | } 45 | 46 | .mt-12, .mt-12 { 47 | margin-top: 12px; 48 | } 49 | 50 | .lh-22 { 51 | line-height: 22px; 52 | } 53 | 54 | .mh-22 { 55 | min-height: 22px; 56 | } 57 | 58 | .dd .mr-12 { 59 | margin-right: 12px; 60 | } 61 | 62 | .dd .fz-14, .fz-14 { 63 | font-size: 14px; 64 | } 65 | 66 | .dd .fz-16, .fz-16 { 67 | font-size: 16px; 68 | } 69 | 70 | .dd .fl-l, .fl-l { 71 | float: left; 72 | } 73 | 74 | .cardDesign .tabs-pos_type, .cardDesign .pos_button { 75 | display: inline-block; 76 | vertical-align: middle; 77 | min-width: 36px; 78 | height: 22px; 79 | line-height: 22px; 80 | border-radius: 2px; 81 | background-color: #006cb7; 82 | text-align: center; 83 | color: #fff; 84 | padding: 0 2px 0 2px; 85 | } -------------------------------------------------------------------------------- /addons21/fastwq/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .Queue import Queue, Empty, Full 2 | from . import importlib 3 | from .misc import * 4 | from .helper import * 5 | -------------------------------------------------------------------------------- /addons21/fastwq/utils/helper.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import re 3 | import os 4 | 5 | __all__ = ['add_metaclass', 'wrap_css'] 6 | 7 | 8 | def add_metaclass(metaclass): 9 | """Class decorator for creating a class with a metaclass.""" 10 | def wrapper(cls): 11 | orig_vars = cls.__dict__.copy() 12 | slots = orig_vars.get('__slots__') 13 | if slots is not None: 14 | if isinstance(slots, str): 15 | slots = [slots] 16 | for slots_var in slots: 17 | orig_vars.pop(slots_var) 18 | orig_vars.pop('__dict__', None) 19 | orig_vars.pop('__weakref__', None) 20 | return metaclass(cls.__name__, cls.__bases__, orig_vars) 21 | return wrapper 22 | 23 | 24 | def wrap_css(orig_css, is_file=True, class_wrapper=None, new_cssfile_suffix=u'wrap'): 25 | 26 | def process(content): 27 | # clean the comments 28 | regx = re.compile(r'/\*.*?\*/', re.DOTALL) 29 | content = regx.sub(r'', content).strip() 30 | # add wrappers to all the selectors except the first one 31 | regx = re.compile(r'([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)', re.DOTALL) 32 | new_css = regx.sub(u'.{} \\1\\2'.format(class_wrapper), content) 33 | return new_css 34 | 35 | if is_file: 36 | if not class_wrapper: 37 | class_wrapper = os.path.splitext(os.path.basename(orig_css))[0] 38 | new_cssfile = u'{css_name}_{suffix}.css'.format( 39 | css_name=orig_css[:orig_css.rindex('.css')], 40 | suffix=new_cssfile_suffix) 41 | # if new css file exists, not process 42 | # if input original css file doesn't exist, return the new css filename and class wrapper 43 | # to make the subsequent process easy. 44 | if os.path.exists(new_cssfile) or not os.path.exists(orig_css): 45 | return new_cssfile, class_wrapper 46 | result = '' 47 | with open(orig_css, 'rb') as f: 48 | try: 49 | result = process(f.read().strip().decode('utf-8', 'ignore')) 50 | except: 51 | showInfo('error: ' + orig_css) 52 | 53 | if result: 54 | with open(new_cssfile, 'wb') as f: 55 | f.write(result.encode('utf-8')) 56 | return new_cssfile, class_wrapper 57 | else: 58 | # class_wrapper must be valid. 59 | assert class_wrapper 60 | return process(orig_css), class_wrapper 61 | -------------------------------------------------------------------------------- /addons21/fastwq/utils/importlib.py: -------------------------------------------------------------------------------- 1 | """Backport of importlib.import_module from 3.x.""" 2 | # While not critical (and in no way guaranteed!), it would be nice to keep this 3 | # code compatible with Python 2.3. 4 | import sys 5 | try: 6 | from importlib import reload 7 | except: 8 | pass 9 | 10 | def _resolve_name(name, package, level): 11 | """Return the absolute name of the module to be imported.""" 12 | if not hasattr(package, 'rindex'): 13 | raise ValueError("'package' not set to a string") 14 | dot = len(package) 15 | for x in range(level, 1, -1): 16 | try: 17 | dot = package.rindex('.', 0, dot) 18 | except ValueError: 19 | raise ValueError("attempted relative import beyond top-level " 20 | "package") 21 | return "%s.%s" % (package[:dot], name) 22 | 23 | 24 | def import_module(name, package=None): 25 | """Import a module. 26 | The 'package' argument is required when performing a relative import. It 27 | specifies the package to use as the anchor point from which to resolve the 28 | relative import to an absolute import. 29 | """ 30 | if name.startswith('.'): 31 | if not package: 32 | raise TypeError("relative imports require the 'package' argument") 33 | level = 0 34 | for character in name: 35 | if character != '.': 36 | break 37 | level += 1 38 | name = _resolve_name(name[level:], package, level) 39 | 40 | if name in sys.modules: 41 | reload(sys.modules[name]) 42 | else: 43 | __import__(name) 44 | return sys.modules[name] 45 | -------------------------------------------------------------------------------- /addons21/fastwq/utils/misc.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | # 3 | # Copyright (C) 2018 sthoo 4 | # 5 | # Support: Report an issue at https://github.com/sth2018/FastWordQuery/issues 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version; http://www.gnu.org/copyleft/gpl.html. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import os 21 | from functools import wraps 22 | from aqt.utils import showInfo 23 | from aqt.qt import QIcon 24 | 25 | __all__ = ['ignore_exception', 26 | 'get_model_byId', 27 | 'get_icon', 28 | 'get_ord_from_fldname', 29 | 'MapDict'] 30 | 31 | 32 | def ignore_exception(func): 33 | @wraps(func) 34 | def wrap(*args, **kwargs): 35 | try: 36 | return func(*args, **kwargs) 37 | except: 38 | return '' 39 | return wrap 40 | 41 | 42 | def get_model_byId(models, id): 43 | for m in list(models.all()): 44 | # showInfo(str(m['id']) + ', ' + m['name']) 45 | if m['id'] == id: 46 | return m 47 | 48 | 49 | def get_ord_from_fldname(model, name): 50 | flds = model['flds'] 51 | for fld in flds: 52 | if fld['name'] == name: 53 | return fld['ord'] 54 | 55 | 56 | def get_icon(filename): 57 | curdir = os.path.dirname(os.path.abspath(__file__)) 58 | pardir = os.path.join(curdir, os.pardir) 59 | path = os.path.join(pardir, 'res', filename) 60 | return QIcon(path) 61 | 62 | 63 | # Some query words like 'Saudi Arabia' is comprised by two or more words that split by '%20'(space), 64 | # it is an invalid query format. (Validated Dictionary: Longman, oxford learning) 65 | def format_multi_query_word(words: str): 66 | _space = '%20' 67 | if words is None or _space not in words: 68 | return words 69 | 70 | return words.lower().replace(_space, '-') 71 | 72 | 73 | class MapDict(dict): 74 | """ 75 | Example: 76 | m = Map({'first_name': 'Eduardo'}, 77 | last_name='Pool', age=24, sports=['Soccer']) 78 | """ 79 | 80 | def __init__(self, *args, **kwargs): 81 | super(MapDict, self).__init__(*args, **kwargs) 82 | for arg in args: 83 | if isinstance(arg, dict): 84 | for k, v in arg.items(): 85 | self[k] = v 86 | 87 | if kwargs: 88 | for k, v in kwargs.items(): 89 | self[k] = v 90 | 91 | def __getattr__(self, attr): 92 | return self.get(attr) 93 | 94 | def __setattr__(self, key, value): 95 | self.__setitem__(key, value) 96 | 97 | def __setitem__(self, key, value): 98 | super(MapDict, self).__setitem__(key, value) 99 | self.__dict__.update({key: value}) 100 | 101 | def __delattr__(self, item): 102 | self.__delitem__(item) 103 | 104 | def __delitem__(self, key): 105 | super(MapDict, self).__delitem__(key) 106 | del self.__dict__[key] 107 | -------------------------------------------------------------------------------- /docs/get_mdx_ldoce6_sounds.md: -------------------------------------------------------------------------------- 1 | # 为单词添加真人发音(朗文mdx词典) 2 | 3 | 在为Anki制作单词卡片的时候,发音一直是一个比较头痛的问题。 4 | 有道的发音是比较容易获取的,但是有道的发音不纯正,很多音读得不是非常正确。 5 | TTS也不能与真人发音相比,而且机器合成音在背单词的时候会有不良影响。 6 | 相对来讲朗文词典的真人发音就非常的纯正。 7 | 所以FastWQ特别针对朗文mdx词典制作对应的字典查询服务,以便快速的为单词添加读音并下载音频到本地。 8 | 按照以下步骤,可以很方便的为整个单词本快速的添加音频。 9 | 10 | ## 使用方法 11 | 1. 准备好mdx与mdd词典文件 12 | ![](images/mdx_mdd_files.png) 13 | 14 | 2. 准备好单词本 15 | ![](images/import.png) 16 | 17 | 3. 添加字典到字典目录 18 | ![](images/dict_folder_01.png) 19 | 20 | 4. 添加你准备的词典文件的路径 21 | ![](images/dict_folder_02.png) 22 | 23 | 5. 设置FastWQ中对应的字典和字段 24 | ![](images/options.png) 25 | 26 | 6. 选择词条并选择菜单 【FastWQ->Query Selected】 或按 【Ctral+Q】 快捷键 27 | ![](images/query_all.png) 28 | 29 | 7. 等待查询结束,因为是本地词典此处查询速度非常的快 30 | ![](images/querying.png) 31 | 32 | 8. 如果单词在朗文词典中能查询得到,则会自动添加发音并下载对应的音频文件到用户媒体文件夹 33 | ![](images/query_end.png) 34 | 35 | ## 参考 36 | - [碎片化英语学习](https://zhuanlan.zhihu.com/p/25958302) 37 | -------------------------------------------------------------------------------- /docs/images/dict_folder_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/dict_folder_01.png -------------------------------------------------------------------------------- /docs/images/dict_folder_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/dict_folder_02.png -------------------------------------------------------------------------------- /docs/images/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/import.png -------------------------------------------------------------------------------- /docs/images/mdx_mdd_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/mdx_mdd_files.png -------------------------------------------------------------------------------- /docs/images/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/options.png -------------------------------------------------------------------------------- /docs/images/query_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/query_all.png -------------------------------------------------------------------------------- /docs/images/query_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/query_end.png -------------------------------------------------------------------------------- /docs/images/querying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/querying.png -------------------------------------------------------------------------------- /docs/images/set_dicts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/docs/images/set_dicts.png -------------------------------------------------------------------------------- /docs/services.md: -------------------------------------------------------------------------------- 1 | # Dictionary Service For FastWQ 2 | 3 | ## Local 4 | | # | Dictionary Name | Describe | 5 | | ---| :--- | :--- | 6 | | 1 | MdxService | .mdx dict file, it's only has default field. | 7 | | 2 | StardictService | .ifo dict file, it's only has default field. | 8 | | 3 | MDX-LDOCE6 | local LDOCE6 mdx file, it's has some fileds. | 9 | 10 | ## Web 11 | | # | Dictionary Name | Link | 12 | | ---| :--- | :--- | 13 | | 1 | Baicizhan | http://mall.baicizhan.com/ws/search?w=word | 14 | | 2 | Bing | http://cn.bing.com/dict/search?q=word&mkt=zh-cn | 15 | | 3 | Bing xtk | http://xtk.azurewebsites.net/BingDictService.aspx?Word=word | 16 | | 4 | 西语助手 | https://www.esdict.cn/mdicts/es/word | 17 | | 5 | 法语助手 | https://frdic.com/mdicts/fr/ | 18 | | 6 | 爱词霸 | http://www.iciba.com/index.php?a=getWordMean&c=search&word=word | 19 | | 7 | 朗文 | https://www.ldoceonline.com/dictionary/word | 20 | | 8 | 海词迷你词典 | http://dict.cn/word | 21 | | 9 | Oxford Learner | https://www.oxfordlearnersdictionaries.com/definition/english/word | 22 | | 10 | Oxford | https://od-api.oxforddictionaries.com/ | 23 | | 11 | Youdao-English | [http://dict.youdao.com/...](http://dict.youdao.com/fsearch?client=deskdict&keyfrom=chrome.extension&pos=-1&doctype=xml&xmlVersion=3.2&dogVersion=1.0&vendor=unknown&appVer=3.1.17.4208&le=eng&q=word) | 24 | | 12 | Youdao-French | [http://dict.youdao.com/...](http://dict.youdao.com/fsearch?client=deskdict&keyfrom=chrome.extension&pos=-1&doctype=xml&xmlVersion=3.2&dogVersion=1.0&vendor=unknown&appVer=3.1.17.4208&le=fr&q=word) | 25 | | 13 | Youdao-Korean | [http://dict.youdao.com/...](http://dict.youdao.com/fsearch?client=deskdict&keyfrom=chrome.extension&pos=-1&doctype=xml&xmlVersion=3.2&dogVersion=1.0&vendor=unknown&appVer=3.1.17.4208&le=ko&q=대한민국) | 26 | | 14 | MDX Server | You have to run mdx server by yourself first. | 27 | | 15 | Yahoo Dictionary | [https://tw.dictionary...](https://tw.dictionary.search.yahoo.com/search?p=test) | 28 | | 16 | Baidu Hanyu | [http://dict.baidu.com/...](http://dict.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD#basicmean) | 29 | | 17 | Cambridge | [https://dictionary.cambridge.org/...](https://dictionary.cambridge.org/dictionary/english/test) | 30 | | 18 | Dr.eye | [https://yun.dreye.com/...](https://yun.dreye.com/dict_new/dict.php?w=test&hidden_codepage=01) | 31 | -------------------------------------------------------------------------------- /screenshots/options_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/options_01.png -------------------------------------------------------------------------------- /screenshots/options_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/options_02.png -------------------------------------------------------------------------------- /screenshots/options_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/options_03.png -------------------------------------------------------------------------------- /screenshots/options_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/options_04.png -------------------------------------------------------------------------------- /screenshots/setting_config_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/setting_config_01.png -------------------------------------------------------------------------------- /screenshots/setting_config_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/setting_config_02.png -------------------------------------------------------------------------------- /screenshots/setting_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/setting_menu.png -------------------------------------------------------------------------------- /screenshots/setting_shortcut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/setting_shortcut.png -------------------------------------------------------------------------------- /screenshots/use_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/use_01.png -------------------------------------------------------------------------------- /screenshots/use_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/use_02.png -------------------------------------------------------------------------------- /screenshots/use_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sth2018/FastWordQuery/e8540a26933616ffcb65902d4f67f39c419153d0/screenshots/use_03.png --------------------------------------------------------------------------------