Show words as Danmaku on the screen to help you remember them.
8 | 9 | [](https://pypi.org/project/DWords/) 10 | [](https://travis-ci.org/luyuhuang/DWords) 11 | [](https://codecov.io/gh/luyuhuang/DWords) 12 | [](https://github.com/luyuhuang/DWords/blob/dev/LICENSE) 13 | 14 |  15 | 16 | [简体中文](README_cn.md) 17 | 18 | ## Introduction 19 | 20 | DWords is a cross platform tool which show words as Danmaku on your screen to help you memorize them. The main purpose of DWords is to help non-English-speaking people study English, but not limited to English, you can also use it to memorize anything you want. 21 | 22 | Features: 23 | 24 | - Open source and cross platform 25 | - Memorize words with ubiquitous Danmakus while using a computer 26 | - Synchronize between multiple devices by email 27 | - Add words using you phone by email 28 | - Included dictionaries 29 | 30 | ## Installation 31 | 32 | DWords is written by Python3, we recommend install via pip. Please make sure your machine has been installed Python3. 33 | 34 | Running the following in your terminal and DWords is installed: 35 | 36 | ```sh 37 | pip3 install DWords 38 | ``` 39 | 40 | You can also install from source code: 41 | 42 | ```sh 43 | git clone https://github.com/luyuhuang/DWords.git 44 | cd DWords 45 | git checkout master 46 | python3 setup.py install 47 | ``` 48 | 49 | If you don't know Python, we also provide binary distributions for Windows. Click [here](https://github.com/luyuhuang/DWords/releases) to download it. Caution, binary distributions may not be trusted by antivirus software. 50 | 51 | ## Usage 52 | 53 | Type `DWords` in your terminal to start DWords. If you download binary distributions, double click `DWords.exe` to start it. 54 | 55 | ### Add words 56 | 57 | Click "+" button and enter into the input box below to add words. The format is: the first line is the word, the second line is a short explanation and followed by detailed explanations. The short explanation can be displayed directly on the Danmaku, while the detailed explanations can only be displayed on the details panel. Detailed explanation is optional. For example: 58 | 59 | ``` 60 | word 61 | a unit of language 62 | a unit of language that native speakers can identify; a brief statement. 63 | ``` 64 | 65 | And then click "Commit" button to add it, or press Ctrl + Enter. 66 | 67 | ### Using dictionary 68 | 69 | DWords included dictionaries, click "Setting" button and set it in the "Common" tab. Currently only English-Chinese dictionary is supported, including more than 30,000 words. Once setting up the dictionary, the explanation will appear automatically when you press Enter while input a word. 70 | 71 | ### Synchronize 72 | 73 | DWords can synchronize words between multiple devices. In order to use this feature, you need to set up an account first. Click "Setting" button and set the email address, email password, SMTP server and POP3 server in the "Account" tab. DWords'll synchronize data via send emails, so we recommend using an infrequently used mailbox. 74 | 75 | ### Add words by email 76 | 77 | Once setting up the account, you can add add words by sending email to the set up mailbox. Edit a email using any email client, with the subject "DWords add" and the format of content is similar to add words, but you can add more than one word, separated by triple dash "---" or triple comma ",,,"; Triple swung dash "~~~" or triple dot "..." tells DWords it's the end. For example: 78 | 79 | ``` 80 | world 81 | 世界 82 | --- 83 | word 84 | 单词 85 | ,,, 86 | hello 87 | 你好 88 | ... 89 | The content after "..." will be ignored. 90 | ``` 91 | 92 | Then send the email to the mailbox you set up. 93 | 94 | If you have set up the dictionary, you don't have to specify the explanation and DWords'll consult the dictionary automatically. 95 | 96 | ## Contribution 97 | 98 | If you find any problems or have any suggestions, open an [issue](https://github.com/luyuhuang/DWords/issues). We also welcome all kinds of pull requests. 99 | 100 | ## License 101 | 102 | DWords is distributed under the [GPLv3 License](https://github.com/luyuhuang/DWords/blob/dev/LICENSE) because PyQt5 is licensed under the GPLv3 (so we have no choice). You can use it for free and modify it freely, but you have to open source with the same license when you modify it. 103 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 |把单词变成屏幕上的弹幕来帮助你记住单词
4 | 5 | [](https://pypi.org/project/DWords/) 6 | [](https://travis-ci.org/luyuhuang/DWords) 7 | [](https://codecov.io/gh/luyuhuang/DWords) 8 | [](https://github.com/luyuhuang/DWords/blob/dev/LICENSE) 9 | 10 |  11 | 12 | ## 介绍 13 | 14 | DWords 是一个跨平台工具, 它可以把单词变成弹幕显示在屏幕上来帮助你记住单词. DWords 的主要目的是帮助非英语母语的人学习英语, 但不仅限于英语, 你还可以用它来记住任何你想要记住的东西. 15 | 16 | 特性: 17 | 18 | - 开源跨平台 19 | - 通过随处可见的弹幕在使用电脑的同时记住单词 20 | - 通过电子邮件在多台设备之间同步 21 | - 通过电子邮件用手机添加单词 22 | - 内置词典 23 | 24 | ## 安装 25 | 26 | DWords 使用 Python3 编写, 我们推荐通过 pip 安装. 先确保你的电脑上已安装好了 Python3. 27 | 28 | 打开终端运行以下命令, DWords 就安装好了: 29 | 30 | ```sh 31 | pip3 install DWords 32 | ``` 33 | 34 | 你也可以通过源码安装: 35 | 36 | ```sh 37 | git clone https://github.com/luyuhuang/DWords.git 38 | cd DWords 39 | git checkout master 40 | python3 setup.py install 41 | ``` 42 | 43 | 如果你不会 Python, 我们也提供了 Windows 系统的二进制版本. 点击[这里](https://github.com/luyuhuang/DWords/releases)下载. 注意, 二进制版本可能不被杀毒软件信任. 44 | 45 | ## 使用方法 46 | 47 | 在终端键入 `DWords` 以启动 DWords. 如果你是下载的二进制版本, 双击 `DWords.exe` 启动. 48 | 49 | ### 添加单词 50 | 51 | 点击 "+" 按钮并在下方的输入框中输入单词. 格式如下: 第一行为单词, 第二行为简要释义, 接下来的是详细释义. 简要释义能够直接显示在弹幕上, 但详细释义只能显示在详细面板中. 详细释义是可选的. 比如: 52 | 53 | ``` 54 | word 55 | 单词 56 | n. 单词;词;字; eg. Do not write more than 200 words. 57 | ``` 58 | 59 | 然后点击 "Commit" 按钮添加它, 或者按下 Ctrl + Enter. 60 | 61 | ### 使用词典 62 | 63 | DWords 内置了词典, 点击 "Setting" 按钮在 "Common" 页签中设置它. 目前仅支持英汉词典, 收录了超过 3 万词. 一旦设置了词典, 在输入单词时按下 Enter 键, 释义就会自动出现. 64 | 65 | ### 同步 66 | 67 | DWords 支持在多个客户端之间同步单词. 为了启用这一功能, 你需要先设置账户. 点击 "Setting" 按钮在 "Account" 页签中设置邮箱地址, 邮箱密码, SMTP 服务器和 POP3 服务器. DWords 会通过发送邮件来同步数据, 所以推荐使用一个不常用的邮箱. 68 | 69 | ### 通过邮件添加单词 70 | 71 | 设置好了账户后, 你就可以通过发送邮件来添加单词. 随意使用一个邮件客户端编辑邮件, 主题为 "DWords add", 内容的格式与添加单词类似, 不过可以添加多个单词, 单词之间用三连杠 "---" 或者三逗号 ",,," 分割; 三波浪线 "~~~" 或者三点号 "..." 表示结束. 例如: 72 | 73 | ``` 74 | world 75 | 世界 76 | --- 77 | word 78 | 单词 79 | ,,, 80 | hello 81 | 你好 82 | ... 83 | 在 ... 之后的内容都会被忽略 84 | ``` 85 | 86 | 然后把这封邮件发送到你设置好的邮箱即可. 87 | 88 | 如果你已经设置了词典, 就不必指定释义, DWords 会自动查阅词典. 89 | 90 | ## 贡献 91 | 92 | 如果你遇到了任何问题, 或者有任何建议, 请提交 [issue](https://github.com/luyuhuang/DWords/issues). 我们也欢迎各种 pull request. 93 | 94 | ## 许可证 95 | 96 | DWords 在 [GPLv3 许可](https://github.com/luyuhuang/DWords/blob/dev/LICENSE)下发布, 因为 PyQt5 是在 GPLv3 下发布的 (所以我们别无选择). 你可以免费使用, 自由修改, 但是当你修改它时你必须使用同样的许可证开源. 97 | -------------------------------------------------------------------------------- /logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyuhuang/DWords/cf25661dfb8223dd7ba035861eb8fc57b41f2573/logo.ico -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 94 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | html-text==0.5.1 2 | PyQt5==5.13.2 3 | python-dateutil==2.8.1 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luyuhuang/DWords/cf25661dfb8223dd7ba035861eb8fc57b41f2573/screenshot.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from DWords.version import VERSION 2 | from setuptools import setup, find_packages 3 | import os 4 | 5 | here = os.path.dirname(__file__) 6 | 7 | with open(os.path.join(here, "README.md"), encoding="utf-8") as f: 8 | long_description = f.read() 9 | 10 | with open(os.path.join(here, "requirements.txt")) as f: 11 | install_requires = [ 12 | line.strip() for line in f.readlines() if not line.startswith("#") 13 | ] 14 | 15 | setup( 16 | name="DWords", 17 | version=VERSION, 18 | description="Show words as Danmaku in the screen to helps you remember them.", 19 | long_description=long_description, 20 | long_description_content_type="text/markdown", 21 | url="https://github.com/luyuhuang/DWords", 22 | keywords="danmaku words english-learning vocabulary pyqt5", 23 | license="GPLv3", 24 | author="Luyu Huang", 25 | author_email="luyu_huang@foxmail.com", 26 | 27 | packages=find_packages(), 28 | install_requires=install_requires, 29 | package_data={ 30 | '': ['img/*', 'data/*.db'] 31 | }, 32 | entry_points={ 33 | "gui_scripts": ["DWords=DWords.__main__:main"] 34 | }, 35 | 36 | classifiers=[ 37 | "Intended Audience :: Education", 38 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 39 | "Natural Language :: English", 40 | "Operating System :: Microsoft :: Windows", 41 | "Operating System :: MacOS", 42 | "Operating System :: POSIX :: Linux", 43 | "Programming Language :: Python :: 3.6", 44 | "Programming Language :: Python :: 3.7", 45 | "Programming Language :: Python :: 3.8", 46 | "Programming Language :: Python :: 3 :: Only", 47 | ] 48 | ) 49 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from DWords import db 3 | 4 | db.initialize() 5 | -------------------------------------------------------------------------------- /tests/test_danmaku.py: -------------------------------------------------------------------------------- 1 | import random 2 | import uuid 3 | from PyQt5.QtCore import Qt 4 | from DWords import danmaku 5 | from DWords import utils 6 | from DWords.launcher import Launcher 7 | from DWords.db import user_db 8 | 9 | def test_add_words(): 10 | utils.add_words( 11 | (str(uuid.uuid1()), str(uuid.uuid1())), 12 | (str(uuid.uuid1()), str(uuid.uuid1())), 13 | ) 14 | 15 | def test_danmaku(qtbot): 16 | word, paraphrase, _, color = utils.random_one_word() 17 | widget = danmaku.Danmaku(word, paraphrase, random.randrange(0, 200), False, color) 18 | qtbot.addWidget(widget) 19 | 20 | assert widget._word_label.text() == word 21 | 22 | def test_danmaku_with_paraphrase(qtbot): 23 | word, paraphrase, _, color = utils.random_one_word() 24 | widget = danmaku.Danmaku(word, paraphrase, random.randrange(0, 200), True, color) 25 | qtbot.addWidget(widget) 26 | 27 | assert widget._word_label.text() == word + " " + paraphrase 28 | 29 | def test_danmaku_panel(qtbot): 30 | word, paraphrase, _, color = utils.random_one_word() 31 | widget = danmaku.Danmaku(word, paraphrase, random.randrange(0, 200), True, color) 32 | qtbot.addWidget(widget) 33 | 34 | assert widget._continenter.isVisible() == False 35 | qtbot.mouseClick(widget._word_label, Qt.LeftButton) 36 | assert widget._continenter.isVisible() == True 37 | qtbot.mouseClick(widget._word_label, Qt.LeftButton) 38 | assert widget._continenter.isVisible() == False 39 | 40 | def test_danmaku_clear(qtbot): 41 | word, paraphrase, _, color = utils.random_one_word() 42 | widget = danmaku.Danmaku(word, paraphrase, random.randrange(0, 200), True, color) 43 | qtbot.addWidget(widget) 44 | 45 | launcher = Launcher() 46 | widget.onModified.connect(launcher.modifyWord) 47 | 48 | assert widget._continenter.isVisible() == False 49 | qtbot.mouseClick(widget._word_label, Qt.LeftButton) 50 | assert widget._continenter.isVisible() == True 51 | 52 | qtbot.mouseClick(widget._clear, Qt.LeftButton) 53 | cleared, = user_db.getOne("select cleared from words where word = ?", (word,)) 54 | assert cleared 55 | -------------------------------------------------------------------------------- /tests/test_home.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from PyQt5.QtCore import Qt 3 | from DWords import home 4 | from DWords.db import user_db 5 | 6 | def test_home(qtbot): 7 | widget = home.Home() 8 | qtbot.addWidget(widget) 9 | 10 | assert widget.windowTitle() == "DWords" 11 | 12 | def test_add_word(qtbot): 13 | widget = home.Home() 14 | qtbot.addWidget(widget) 15 | 16 | add = widget.layout().itemAt(3).itemAt(0).widget() 17 | assert add.text() == "+" 18 | qtbot.mouseClick(add, Qt.LeftButton) 19 | 20 | assert widget._word_editor.isVisible() == True 21 | 22 | word = str(uuid.uuid1()) 23 | paraphrase = str(uuid.uuid1()) 24 | widget._word_editor.setPlainText(word + "\n" + paraphrase) 25 | 26 | commit = widget._editor.layout().itemAt(1).itemAt(1).widget() 27 | assert commit.text() == "Commit" 28 | 29 | qtbot.mouseClick(commit, Qt.LeftButton) 30 | res, = user_db.getOne("select paraphrase from words where word = ?", (word,)) 31 | assert res == paraphrase 32 | 33 | close = widget._editor.layout().itemAt(1).itemAt(2).widget() 34 | assert close.text() == "Close" 35 | qtbot.mouseClick(close, Qt.LeftButton) 36 | assert widget._word_editor.isVisible() == False 37 | -------------------------------------------------------------------------------- /tests/test_other.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from DWords import utils 3 | 4 | def test_del_word(): 5 | word, paraphrase = str(uuid.uuid1()), str(uuid.uuid1()) 6 | utils.add_words((word, paraphrase)) 7 | utils.delete_words(word) 8 | 9 | def test_dictionary(): 10 | assert utils.consult('apple') != '' 11 | -------------------------------------------------------------------------------- /tests/test_setting.py: -------------------------------------------------------------------------------- 1 | from DWords.setting import Setting 2 | 3 | def test_setting(qtbot): 4 | widget = Setting() 5 | qtbot.addWidget(widget) 6 | 7 | assert widget.windowTitle() == "DWords - Setting" 8 | -------------------------------------------------------------------------------- /tests/test_sync.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | import time 4 | import poplib 5 | from PyQt5.QtCore import QThread 6 | from DWords.synchronizer import Synchronizer 7 | from DWords.db import user_db 8 | from DWords import utils 9 | from DWords import async_thread 10 | 11 | def test_set_account(): 12 | utils.set_setting("email", os.environ["MAIL_ADDR"]) 13 | utils.set_setting("password", os.environ["MAIL_PASSWORD"]) 14 | utils.set_setting("smtp_server", os.environ["SMTP_SERVER"]) 15 | utils.set_setting("pop3_server", os.environ["POP3_SERVER"]) 16 | 17 | def test_add_words(): 18 | utils.add_words( 19 | (str(uuid.uuid1()), str(uuid.uuid1())), 20 | (str(uuid.uuid1()), str(uuid.uuid1())), 21 | ) 22 | 23 | @async_thread.normal 24 | async def _sync(): 25 | synchronizer = Synchronizer() 26 | await synchronizer.sync() 27 | 28 | def _delete_mails(): 29 | pop3_server = utils.get_setting("pop3_server") 30 | email = utils.get_setting("email") 31 | password = utils.get_setting("password") 32 | 33 | pop3 = poplib.POP3_SSL(pop3_server, poplib.POP3_SSL_PORT, timeout=30) 34 | pop3.user(email) 35 | pop3.pass_(password) 36 | 37 | count, _ = pop3.stat() 38 | for i in range(1, count + 1): 39 | pop3.dele(i) 40 | 41 | pop3.quit() 42 | 43 | def test_sync(qtbot): 44 | _delete_mails() 45 | 46 | num, = user_db.getOne("select count(*) from sync_cache where op = 'add'") 47 | 48 | _sync() 49 | qtbot.waitUntil(lambda: not async_thread._coroutines, timeout=30000) 50 | assert user_db.getOne("select count(*) from sync_cache")[0] == 0 51 | 52 | with user_db.cursor() as c: 53 | c.execute("delete from words") 54 | c.execute("delete from sys where id = 'last_mail_id'") 55 | c.execute("update sys set value = ? where id = 'uuid'", (str(uuid.uuid1()),)) 56 | 57 | _sync() 58 | qtbot.waitUntil(lambda: not async_thread._coroutines, timeout=30000) 59 | assert user_db.getOne("select count(*) from words")[0] == num 60 | -------------------------------------------------------------------------------- /win.py: -------------------------------------------------------------------------------- 1 | from DWords.__main__ import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /win.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['win.py', 7 | 'DWords/__init__.py', 8 | 'DWords/__main__.py', 9 | 'DWords/app.py', 10 | 'DWords/async_thread.py', 11 | 'DWords/danmaku.py', 12 | 'DWords/db.py', 13 | 'DWords/home.py', 14 | 'DWords/launcher.py', 15 | 'DWords/mail.py', 16 | 'DWords/migrate.py', 17 | 'DWords/setting.py', 18 | 'DWords/synchronizer.py', 19 | 'DWords/utils.py', 20 | 'DWords/version.py' 21 | ], 22 | binaries=[], 23 | datas=[('DWords/data/dictionary.db', 'DWords/data/'), ('DWords/img/logo.svg', 'DWords/img/')], 24 | hiddenimports=[], 25 | hookspath=[], 26 | runtime_hooks=[], 27 | excludes=[], 28 | win_no_prefer_redirects=False, 29 | win_private_assemblies=False, 30 | cipher=block_cipher, 31 | noarchive=False) 32 | pyz = PYZ(a.pure, a.zipped_data, 33 | cipher=block_cipher) 34 | exe = EXE(pyz, 35 | a.scripts, 36 | a.binaries, 37 | a.zipfiles, 38 | a.datas, 39 | [], 40 | name='DWords', 41 | debug=False, 42 | bootloader_ignore_signals=False, 43 | strip=False, 44 | upx=True, 45 | upx_exclude=[], 46 | runtime_tmpdir=None, 47 | console=False , icon='logo.ico') 48 | --------------------------------------------------------------------------------