├── .gitignore ├── Procfile ├── Procfile.windows ├── README.md ├── app.py ├── config.py ├── requirements.txt ├── run.bat ├── src ├── __init__.py ├── error_email.py ├── forms.py ├── spider.py └── validate.py ├── static ├── add-new-word │ ├── file.css │ ├── hand.css │ ├── icon.svg │ └── new.css ├── bulma.min.css ├── error_handler │ ├── 404.css │ └── 500.css ├── favicon.ico ├── jquery.min.js ├── login │ ├── github.jpg │ ├── icon.svg │ ├── login.css │ └── login.js ├── main.css ├── main.js ├── recite-words │ ├── icon.svg │ ├── recite.css │ ├── recite.js │ └── result.css ├── registe │ ├── icon.svg │ └── registe.css ├── search-words │ ├── icon.svg │ ├── result.css │ └── search.css ├── see-words │ ├── icon.svg │ └── see.css ├── settings │ ├── icon.svg │ └── settings.css ├── word-books │ ├── books.css │ ├── icon.svg │ ├── youdict-gre.txt │ ├── youdict-kaoyan.txt │ ├── youdict-liuji.txt │ ├── youdict-siji.txt │ ├── youdict-tuofu.txt │ ├── youdict-yasi.txt │ ├── youdict-zhuanbaji.txt │ └── youdict-zhuansiji.txt └── wrong-words │ ├── icon.svg │ ├── recite.css │ ├── result.css │ └── wrong.css ├── templates ├── add-new-word │ ├── failure1.html │ ├── failure2.html │ ├── file.html │ ├── hand.html │ └── new.html ├── error-handler │ ├── 404.html │ └── 500.html ├── login │ └── login.html ├── main.html ├── recite-words │ ├── recite.html │ └── result.html ├── registe │ └── registe.html ├── search-words │ ├── result.html │ └── search.html ├── see-words │ └── see.html ├── settings │ └── settings.html ├── word-books │ └── books.html └── wrong-words │ └── wrong.html └── test ├── test-output.xml └── test_flask.py /.gitignore: -------------------------------------------------------------------------------- 1 | words.sqlite3 2 | wrongwords.sqlite3 3 | 4 | *.pyc 5 | 6 | .idea/ 7 | .pytest_cache/ 8 | test-output.xml 9 | 10 | *.sqlite3 11 | 12 | spider_all.py 13 | sort.py 14 | 15 | word-books-origin/ 16 | 17 | application.wsgi 18 | 19 | app.zip 20 | 21 | .env -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app -------------------------------------------------------------------------------- /Procfile.windows: -------------------------------------------------------------------------------- 1 | web: python app.py runserver 0.0.0.0:5000 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 词神 2 | 3 | **一个用来背单词的网站** 4 | 5 | ## 体验 6 | 7 | 在线网站:[cishen.herokuapp.com](https://cishen.herokuapp.com) 8 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import difflib 3 | import hashlib 4 | import os 5 | import threading 6 | import datetime 7 | import queue 8 | 9 | from flask import Flask, render_template, request, flash, redirect, \ 10 | get_flashed_messages, session, jsonify, url_for, \ 11 | send_from_directory, current_app, g 12 | from flask_sqlalchemy import SQLAlchemy 13 | from flask_github import GitHub 14 | from werkzeug import secure_filename 15 | import requests 16 | from dotenv import find_dotenv, load_dotenv 17 | 18 | import traceback 19 | 20 | from src import SpiderForm, youdict, hujiang, words_validate, mail 21 | 22 | load_dotenv(find_dotenv()) 23 | 24 | 25 | def youdict_spider(threadName, q): 26 | """ 27 | :param threadName: thread name of threading module 28 | :param q: Queue object of queue 29 | 30 | Usage:: 31 | 32 | get youdict words and store them in the sqlalchemy database 33 | """ 34 | words = youdict(threadName, q) 35 | for i in words: 36 | word = Words(i[0], i[1]) 37 | try: 38 | db.session.add(word) 39 | db.session.commit() 40 | except Exception: 41 | pass 42 | 43 | 44 | def hujiang_spider(threadName, q): 45 | """ 46 | :param threadName: thread name of threading module 47 | :param q: Queue object of queue module 48 | 49 | Usage:: 50 | 51 | get hujiang words and store them in the sqlalchemy database 52 | """ 53 | words = hujiang(threadName, q) 54 | for i in words: 55 | word = Words(i[0], i[1]) 56 | db.session.add(word) 57 | db.session.commit() 58 | 59 | 60 | class SpiderThread(threading.Thread): 61 | """ 62 | :param name: thread name 63 | :param q: queue object of queue module 64 | :param website: the words website to scrapy 65 | 66 | :method run: 67 | 68 | run the words scrapy program with threading and queue 69 | """ 70 | 71 | def __init__(self, name, q, website): 72 | threading.Thread.__init__(self) 73 | self.name = name 74 | self.q = q 75 | self.website = website 76 | 77 | def run(self): 78 | print("Starting " + self.name) 79 | while True: 80 | try: 81 | if self.website == "youdict": 82 | youdict_spider(self.name, self.q) 83 | elif self.website == "hujiang": 84 | hujiang_spider(self.name, self.q) 85 | except Exception: 86 | break 87 | print("Exiting" + self.name) 88 | 89 | 90 | app = Flask(__name__) 91 | app.jinja_env.auto_reload = True 92 | 93 | app.config.from_object("config") 94 | db = SQLAlchemy(app) 95 | github = GitHub(app) 96 | 97 | 98 | class Words(db.Model): 99 | """ 100 | :attr|param id: id for words 101 | :attr|param english: english for words 102 | :attr|param chinese: chinese explanation for english words 103 | """ 104 | id = db.Column("words_id", db.Integer, primary_key=True) 105 | english = db.Column(db.String(75)) 106 | chinese = db.Column(db.String(200)) 107 | 108 | def __init__(self, english, chinese): 109 | self.english = english 110 | self.chinese = chinese 111 | 112 | 113 | class WrongWords(db.Model): 114 | """ 115 | :attr|param id: id for 1wrong words 116 | :attr|param english: english for wrong words 117 | :attr|param chinese: chinese explanation for english wrong words 118 | """ 119 | __bind_key__ = "wrongwords" 120 | id = db.Column("wrong_words_id", db.Integer, primary_key=True) 121 | english = db.Column(db.String(75)) 122 | chinese = db.Column(db.String(200)) 123 | 124 | def __init__(self, english, chinese): 125 | self.english = english 126 | self.chinese = chinese 127 | 128 | 129 | class GithubUsers(db.Model): 130 | """ 131 | :attr|param id: id for users 132 | :attr|param username: username of github 133 | """ 134 | __bind_key__ = "github-users" 135 | id = db.Column("github-users_id", db.Integer, primary_key=True) 136 | username = db.Column(db.String(100)) 137 | access_token = db.Column(db.String(200)) 138 | 139 | def __init__(self, username, access_token): 140 | self.username = username 141 | self.access_token = access_token 142 | 143 | 144 | class AdminUsers(db.Model): 145 | """ 146 | :attr|param id: id for users 147 | :attr|param username: username of github 148 | """ 149 | __bind_key__ = "admin-users" 150 | id = db.Column("admin-users_id", db.Integer, primary_key=True) 151 | username = db.Column(db.String(100)) 152 | password = db.Column(db.String(100)) 153 | 154 | def __init__(self, username, password): 155 | self.username = username 156 | self.password = password 157 | 158 | 159 | @app.before_first_request 160 | def before_first_request(): 161 | """ 162 | Usage:: 163 | 164 | Before first request for the application, we need to 165 | initialize some variables. 166 | """ 167 | global choice, failure, is_failure, first, wrong_word_choice 168 | global db 169 | 170 | choice = 0 171 | failure = [] 172 | is_failure = False 173 | first = True 174 | wrong_word_choice = 0 175 | 176 | session.permanent = True 177 | session["search_diff"] = 0.5 178 | session["words_count"] = 20 179 | session["recite_progress"] = 0 180 | 181 | db.create_all() 182 | db.create_all(bind="wrongwords") 183 | db.create_all(bind="github-users") 184 | db.create_all(bind="admin-users") 185 | 186 | 187 | @app.before_request 188 | def before_request(): 189 | g.user = None 190 | if 'users_id' in session: 191 | g.user = GithubUsers.query.get(session['users_id']) 192 | elif 'admin_users_id' in session: 193 | g.user = AdminUsers.query.get(session["admin_users_id"]) 194 | 195 | 196 | @app.route("/") 197 | def main(): 198 | """ 199 | Usage:: 200 | 201 | the index page for cishen application 202 | """ 203 | if g.user: 204 | is_login = True 205 | if isinstance(g.user, GithubUsers): 206 | response = github.get('user') 207 | username = response['name'] 208 | elif isinstance(g.user, AdminUsers): 209 | username = g.user.username 210 | return render_template('main.html', is_login=is_login, username=username) 211 | is_login = False 212 | return render_template("main.html", is_login=is_login) 213 | 214 | 215 | @app.route("/registe") 216 | def registe(): 217 | return render_template("registe/registe.html") 218 | 219 | 220 | @app.route("/registe/normal", methods=["POST"]) 221 | def registe_normal(): 222 | md5 = hashlib.md5() 223 | registe_username = request.form.get("username") 224 | registe_password = request.form.get("password") 225 | if not len(registe_password) > 8 or not registe_username: 226 | flash("数值不合法!") 227 | return redirect("/registe") 228 | md5.update(registe_password.encode(encoding="utf-8")) 229 | registe_password = md5.hexdigest() 230 | if AdminUsers.query.filter_by(username=registe_username, password=registe_password).first() is not None: 231 | flash("用户名已存在!") 232 | return redirect("/registe") 233 | user = AdminUsers(registe_username, registe_password) 234 | db.session.add(user) 235 | db.session.commit() 236 | flash("注册成功") 237 | return redirect("/") 238 | 239 | 240 | @app.route("/login") 241 | def login(): 242 | return render_template("login/login.html") 243 | 244 | 245 | @app.route("/login/normal", methods=["POST"]) 246 | def login_normal(): 247 | md5 = hashlib.md5() 248 | login_username = request.form.get("username") 249 | login_password = request.form.get("password") 250 | md5.update(login_password.encode(encoding="utf-8")) 251 | login_password = md5.hexdigest() 252 | user = AdminUsers.query.filter_by(username=login_username, password=login_password).first() 253 | if user: 254 | password = user.password 255 | if password == login_password: 256 | flash("登录成功!") 257 | session["admin_users_id"] = user.id 258 | g.user = user 259 | return redirect("/") 260 | else: 261 | flash("用户名或密码错误!") 262 | else: 263 | flash("用户名或密码错误!") 264 | 265 | 266 | @app.route('/login/oauth2') 267 | def login_oauth2(): 268 | if session.get('users_id', None) is None: 269 | return github.authorize() 270 | flash('已经登录!') 271 | return redirect("/") 272 | 273 | 274 | @app.route('/login/oauth2/callback') 275 | @github.authorized_handler 276 | def oauth2_callback(access_token): 277 | if access_token is None: 278 | flash('登陆失败!') 279 | return redirect("/") 280 | 281 | response = github.get('user', access_token=access_token) 282 | username = response['login'] # get username 283 | user = GithubUsers.query.filter_by(username=username).first() 284 | if user is None: 285 | user = GithubUsers(username=username, access_token=access_token) 286 | db.session.add(user) 287 | db.session.commit() 288 | session["users_id"] = user.id 289 | flash('登录成功!') 290 | return redirect("/") 291 | 292 | 293 | @github.access_token_getter 294 | def token_getter(): 295 | user = g.user 296 | if user is not None: 297 | return user.access_token 298 | 299 | 300 | @app.route("/see-words", methods=["GET", "POST"]) 301 | def see_words_redirect(): 302 | return redirect("/see-words/1") 303 | 304 | @app.route("/see-words/delete-all", methods=["POST"]) 305 | def see_words_delete_all(): 306 | words = Words.query.all() 307 | for i in words: 308 | db.session.delete(i) 309 | db.session.commit() 310 | return redirect("/see-words") 311 | 312 | @app.route("/see-words/", methods=["GET", "POST"]) 313 | def see(page): 314 | """ 315 | Usage:: 316 | 317 | The page for seeing all words in the databases. It can delete 318 | some words or all words. 319 | """ 320 | if request.method == "POST": 321 | english = request.form.get("english") 322 | chinese = request.form.get("chinese") 323 | u = Words.query.filter_by(english=english, chinese=chinese).first() 324 | db.session.delete(u) 325 | db.session.commit() 326 | return render_template("see-words/see.html", 327 | words=Words.query.all()) 328 | else: 329 | info = Words.query.paginate(int(page), per_page=30) 330 | return render_template("see-words/see.html", words=info) 331 | 332 | 333 | @app.route("/add-new-word", methods=["GET", "POST"]) 334 | def new(): 335 | """ 336 | Usage:: 337 | 338 | the page for href to use hand or file to add new words. Also accept 339 | post from hand page. 340 | """ 341 | if request.method == "GET": 342 | return render_template("add-new-word/new.html") 343 | else: 344 | success, errors = words_validate(request.form.get("words")) 345 | for data in success: 346 | word = Words(data[0], data[1]) 347 | db.session.add(word) 348 | db.session.commit() 349 | return redirect("see-words") 350 | 351 | @app.route("/before-recite-words") 352 | def before_recite_words(): 353 | global failure 354 | failure = [] 355 | choice = request.args.get("choice") 356 | return redirect("recite-words?choice=" + choice) 357 | 358 | @app.route("/recite-words") 359 | def recite(): 360 | """ 361 | Usage:: 362 | 363 | The page for recite words in the words database. Accept the first 364 | get and other post for data transmission. 365 | """ 366 | global first, choice, failure 367 | 368 | today = session.get("today-progress") 369 | 370 | if today and datetime.date.today == today: 371 | pass 372 | else: 373 | session["today_progress"] = datetime.date.today() 374 | 375 | # get recite progress to start from here 376 | recite_progress = session.get("recite_progress") 377 | if not recite_progress: 378 | recite_progress = "0" 379 | else: 380 | recite_progress = str(recite_progress) 381 | 382 | # get other args from url 383 | choice = request.args.get("choice") 384 | input_data = request.args.get("input") 385 | data = request.args.get("data") 386 | wrong = request.args.get("wrong") 387 | 388 | # if you answered wrong, it will add the word to the wrong words database 389 | if wrong == "True": 390 | wrongword = Words.query.get(int(choice)) 391 | failure.append([wrongword.english, wrongword.chinese]) 392 | wrongword = WrongWords(wrongword.english, wrongword.chinese) 393 | db.session.add(wrongword) 394 | db.session.commit() 395 | 396 | choice = int(choice) 397 | word = Words.query.get(choice+1) 398 | 399 | words_count = session.get("words_count") 400 | words_count = words_count if words_count else 20 401 | # judge if we have finished the recite 402 | if choice >= int(recite_progress) + words_count or not word: 403 | session["recite_progress"] = choice 404 | if failure: 405 | return redirect("/recite-wrong-words?choice=0") 406 | else: 407 | flash("复习完成!") 408 | return redirect("/") 409 | word = [word.english, word.chinese] 410 | # other situations 411 | if input_data and data: 412 | return redirect("/recite-words") 413 | elif choice != None: 414 | return render_template("/recite-words/recite.html", word=word) 415 | 416 | 417 | @app.route("/recite-wrong-words") 418 | def recite_words_wrong(): 419 | global failure 420 | 421 | # get args from url 422 | wrong_word_choice = int(request.args.get("choice")) 423 | input_data = request.args.get("input") 424 | data = request.args.get("data") 425 | wrong = request.args.get("wrong") 426 | 427 | # judge if you answered right, if right, remove the word from failure list 428 | if not wrong: 429 | pass 430 | elif wrong == "False": 431 | failure.remove(failure[wrong_word_choice - 1]) 432 | else: 433 | failure.append(failure[wrong_word_choice - 1]) 434 | 435 | # when failure is empty, it means you finished recite 436 | if not failure: 437 | flash("复习完成!") 438 | return redirect("/") 439 | 440 | # other situations 441 | if input_data and data: 442 | return redirect("/recite-wrong-words") 443 | elif choice != None: 444 | return render_template("/recite-words/recite.html", word=failure[wrong_word_choice], come="wrong") 445 | 446 | 447 | @app.route("/search-words", methods=["GET", "POST"]) 448 | def search(): 449 | """ 450 | Usage:: 451 | 452 | The page for search words from words database. Accept get and post of 453 | key word for search. 454 | """ 455 | if request.method == "GET": 456 | return render_template("search-words/search.html") 457 | else: 458 | result = [] 459 | data = request.form.get("data") 460 | for i in Words.query.all(): 461 | sm = difflib.SequenceMatcher(None, i.english, data) 462 | search_diff = session.get("search_diff") 463 | search_diff = 0.5 if not search_diff else search_diff 464 | if sm.ratio() >= search_diff: 465 | result.append(i) 466 | return render_template("search-words/result.html", result=result) 467 | 468 | 469 | @app.route("/wrong-words", methods=["GET", "POST"]) 470 | def wrong_words(): 471 | """ 472 | Usage:: 473 | 474 | The page for show wrong words from wrongwords database. Accept get 475 | and post for deleting. 476 | """ 477 | if request.method == "POST": 478 | delete_all = request.form.get("delete-all") 479 | if delete_all: 480 | words = WrongWords.query.all() 481 | for i in words: 482 | db.session.delete(i) 483 | db.session.commit() 484 | return redirect("/wrong-words") 485 | else: 486 | english = request.form.get("english") 487 | chinese = request.form.get("chinese") 488 | u = WrongWords.query.filter_by( 489 | english=english, chinese=chinese).first() 490 | db.session.delete(u) 491 | db.session.commit() 492 | return render_template( 493 | "wrong-words/wrong.html", words=WrongWords.query.all()) 494 | else: 495 | return render_template("wrong-words/wrong.html", 496 | words=WrongWords.query.all()) 497 | 498 | @app.route("/wrong-words/delete-all", methods=["POST"]) 499 | def wrong_words_delete_all(): 500 | words = WrongWords.query.all() 501 | for i in words: 502 | db.session.delete(i) 503 | db.session.commit() 504 | flash("删除完成!") 505 | return redirect("/wrong-words") 506 | 507 | @app.route("/settings", methods=["GET", "POST"]) 508 | def settings(): 509 | """ 510 | Usage:: 511 | 512 | The settings page. Settings contains the words count you want to recite 513 | one time, the scrapy program for two websites. 514 | """ 515 | youdictform = SpiderForm() 516 | hujiangform = SpiderForm() 517 | if request.method == "GET": 518 | return render_template("/settings/settings.html", words_count=session.get("words_count"), 519 | search_diff=session.get("search_diff"), 520 | youdictform=youdictform, hujiangform=hujiangform) 521 | else: 522 | search_diff = request.form.get("search_diff") 523 | words_count = request.form.get("words_count") 524 | try: 525 | search_diff = float(search_diff) 526 | words_count = int(words_count) 527 | except Exception: 528 | flash("数值不合法") 529 | return redirect("/settings") 530 | if search_diff > 1 or search_diff < 0: 531 | flash("查询单词相似度数值不合法!") 532 | return redirect("/settings") 533 | if words_count <= 0 or words_count % 1 != 0: 534 | flash("每次背诵单词数数值不合法!") 535 | return redirect("/settings") 536 | session["search_diff"] = search_diff 537 | session["words_count"] = words_count 538 | flash("修改设置成功") 539 | return render_template("/settings/settings.html", words_count=session.get("words_count"), 540 | search_diff=session.get("search_diff"), 541 | youdictform=youdictform, hujiangform=hujiangform) 542 | 543 | 544 | @app.route("/spider/", methods=["POST"]) 545 | def youdict_spider_post(website): 546 | """ 547 | :param website: the website name to scrapy 548 | 549 | Usage:: 550 | 551 | The page for scrapy website operation. Accept post 552 | with website name. 553 | """ 554 | # print(website) 555 | youdictform = SpiderForm() 556 | hujiangform = SpiderForm() 557 | link_list = [] 558 | if website == "youdict": 559 | page_number_begin = youdictform.page_number_begin.data 560 | page_number_all = youdictform.page_number_all.data 561 | 562 | finish = page_number_begin + page_number_all 563 | elif website == "hujiang": 564 | page_number_begin = hujiangform.page_number_begin.data 565 | page_number_all = hujiangform.page_number_all.data 566 | 567 | finish = page_number_begin + page_number_all 568 | 569 | if finish > 2239 or finish < 0 or finish % 1 != 0: 570 | flash("非法的页数") 571 | return redirect("/settings") 572 | 573 | for i in range(page_number_begin, finish): 574 | if website == "youdict": 575 | url = f"https://www.youdict.com/ciku/id_0_0_0_0_{i}.html" 576 | elif website == "hujiang": 577 | url = f"https://www.hujiang.com/ciku/zuixinyingyudanci_{i}" 578 | link_list.append(url) 579 | # print(link_list) 580 | 581 | threadList = ["Thread-1", "Thread-2", "Thread-3", "Thread-4", "Thread-5"] 582 | workQueue = queue.Queue(2500) 583 | threads = [] 584 | 585 | for tName in threadList: 586 | thread = SpiderThread(tName, workQueue, website) 587 | thread.start() 588 | threads.append(thread) 589 | 590 | for url in link_list: 591 | workQueue.put(url) 592 | 593 | for t in threads: 594 | t.join() 595 | 596 | return redirect("/see-words") 597 | 598 | 599 | @app.route("/word-books") 600 | def word_books(): 601 | """ 602 | Usage:: 603 | 604 | The word books page. The word books is used for 605 | download the prepared words. It is faster than scrapy. 606 | """ 607 | return render_template("word-books/books.html") 608 | 609 | 610 | @app.route("/word-books/download/", methods=["POST"]) 611 | def word_books_download(name): 612 | """ 613 | :param name: the name of the word book 614 | 615 | Usage:: 616 | 617 | The word books download page for only post method. Word books can be 618 | download here and added to the words databases. 619 | """ 620 | file = "./static/word-books/" + name + ".txt" 621 | f = open(file, "r", encoding="utf-8").read() 622 | url = url_for("new", _external=True) 623 | requests.post(url, data={"words": f}) 624 | return redirect("/see-words") 625 | 626 | 627 | @app.route("/add-new-word/hand") 628 | def hand(): 629 | """ 630 | Usage:: 631 | 632 | The page for add new word by hand. Have a post for add new word 633 | index page to transmit word data. 634 | """ 635 | return render_template("add-new-word/hand.html") 636 | 637 | 638 | @app.route("/add-new-word/file", methods=["GET", "POST"]) 639 | def file(): 640 | if request.method == "POST": 641 | try: 642 | f = request.files['file'] 643 | filename = secure_filename(f.filename) 644 | f.save(filename) 645 | with open(filename, "r", encoding="utf-8") as f: 646 | success, errors = words_validate(f.read()) 647 | os.remove(filename) 648 | for data in success: 649 | word = Words(data[0], data[1], data[2]) 650 | db.session.add(word) 651 | db.session.commit() 652 | os.remove(filename) 653 | return render_template("see-words/see.html", 654 | words=Words.query.all()) 655 | 656 | except UnicodeDecodeError: 657 | return render_template( 658 | "add-new-word/failure1.html", filename=filename) 659 | except Exception: 660 | return render_template( 661 | "add-new-word/failure2.html", filename=filename) 662 | else: 663 | return render_template("add-new-word/file.html") 664 | 665 | 666 | @app.errorhandler(404) 667 | def four_zero_four(exception): 668 | """ 669 | Usage:: 670 | 671 | The 404 page for this flask application. 672 | """ 673 | return render_template("error-handler/404.html", exception=exception) 674 | 675 | 676 | @app.errorhandler(500) 677 | def five_zero_zero(exception): 678 | """ 679 | Usage:: 680 | 681 | The 500 page for this flask application. 682 | """ 683 | mail(exception) 684 | return render_template("error-handler/500.html") 685 | 686 | 687 | @app.route('/favicon.ico') 688 | def favicon(): 689 | """ 690 | Usage:: 691 | 692 | The favicon for every page. 693 | """ 694 | return send_from_directory(os.path.join(app.root_path, 'static'), 695 | 'favicon.ico', mimetype='image/vnd.microsoft.icon') 696 | 697 | 698 | if __name__ == '__main__': 699 | app.run(port=8080, debug=True) 700 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import timedelta 3 | 4 | 5 | client_id = os.environ["CLIENT_ID"] 6 | client_secret = os.environ["CLIENT_SECRET"] 7 | 8 | SECRET_KEY = os.urandom(24) 9 | SEND_FILE_MAX_AGE_DEFAULT = timedelta(seconds=1) 10 | PERMANENT_SESSION_LIFETIME = timedelta(days = 365) 11 | GITHUB_CLIENT_ID = client_id 12 | GITHUB_CLIENT_SECRET = client_secret 13 | """ 14 | SQLALCHEMY_DATABASE_URI = 'sqlite:///words.sqlite3' 15 | SQLALCHEMY_TRACK_MODIFICATIONS = False 16 | SQLALCHEMY_BINDS = { 17 | 'wrongwords': 'sqlite:///wrongwords.sqlite3', 18 | "users": "sqlite:///users.sqlite3" 19 | } 20 | """ 21 | SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"] 22 | SQLALCHEMY_TRACK_MODIFICATIONS = False 23 | SQLALCHEMY_BINDS = { 24 | 'wrongwords': os.environ["HEROKU_POSTGRESQL_AMBER_URL"], 25 | "github-users": os.environ["HEROKU_POSTGRESQL_NAVY_URL"], 26 | "admin-users": os.environ["HEROKU_POSTGRESQL_ROSE_URL"] 27 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask-sqlalchemy 3 | flask-wtf 4 | github-flask 5 | psycopg2 6 | requests 7 | gunicorn 8 | python-dotenv -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | heroku local -e=.env -f=Procfile.windows -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from .forms import SpiderForm 2 | from .spider import youdict, hujiang 3 | from .validate import words_validate 4 | from .error_email import mail -------------------------------------------------------------------------------- /src/error_email.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | from email.mime.text import MIMEText 3 | from email.header import Header 4 | import traceback 5 | 6 | def mail(exception): 7 | sender = 'pynickle@sina.com' 8 | receivers = 'pynickle@sina.com' 9 | 10 | msg = "" 11 | msg += "

Exception happened when others using your cishen application:

" 12 | msg += "
" + str(exception) + "
" 13 | 14 | message = MIMEText(msg, 'html', 'utf-8') 15 | message['From'] = sender 16 | message['To'] = receivers 17 | 18 | subject = 'cishen error occured' 19 | message['Subject'] = Header(subject, 'utf-8') 20 | 21 | try: 22 | smtp = smtplib.SMTP() 23 | smtp.connect("smtp.sina.com", 25) 24 | smtp.login("pynickle@sina.com", "32149d81f58043e6") 25 | smtp.sendmail(sender, receivers, message.as_string()) 26 | smtp.quit() 27 | return True 28 | except Exception as e: 29 | # traceback.print_exc() 30 | return False 31 | 32 | if __name__ == "__main__": 33 | mail("error") -------------------------------------------------------------------------------- /src/forms.py: -------------------------------------------------------------------------------- 1 | from wtforms import validators 2 | from flask_wtf import FlaskForm 3 | from wtforms import TextAreaField, SubmitField, IntegerField 4 | 5 | 6 | class SpiderForm(FlaskForm): 7 | page_number_begin = IntegerField("输入开始页数", [validators.DataRequired()]) 8 | page_number_all = IntegerField("输入爬取页数", [validators.DataRequired(), validators.Length(1, 2240)]) 9 | submit = SubmitField("开始", render_kw = {"class": "button is-link is-outlined is-rounded"}) 10 | -------------------------------------------------------------------------------- /src/spider.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | import time 4 | import random 5 | import pprint 6 | import os 7 | 8 | 9 | headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3858.0 Safari/537.36"} 10 | 11 | def youdict(threadName, q): 12 | res = [] 13 | index = 0 14 | url = q.get(timeout = 2) 15 | index += 1 16 | r = requests.get(url, headers = headers, timeout = 5) 17 | html = str(r.content, encoding="utf-8").replace("\n", "").replace(" ", "").replace('[英语单词大全]', "") 18 | words = re.findall('

(.*?)[ ]?

(.*?)

', html) 19 | 20 | for word in words: 21 | res.append(word) 22 | if index%5 == 0: 23 | time.sleep(3 + random.random()) 24 | else: 25 | time.sleep(1 + random.random()) 26 | return res 27 | 28 | def hujiang(threadName, q): 29 | res = [] 30 | index = 0 31 | url = q.get(timeout = 2) 32 | 33 | index += 1 34 | r = requests.get(url, headers=headers, timeout=5) 35 | html = str(r.content, encoding="utf-8").replace("\n", "").replace(" ", "").replace('[英语单词大全]', "") 36 | words = re.findall('
  • .*?(.*?)
  • ', html) 37 | for word in words: 38 | res.append(word) 39 | 40 | if index%5 == 0: 41 | time.sleep(3 + random.random()) 42 | else: 43 | time.sleep(1 + random.random()) 44 | return res 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /src/validate.py: -------------------------------------------------------------------------------- 1 | def words_validate(data): 2 | errors = [] 3 | success = [] 4 | data = data.splitlines() 5 | for word in data: 6 | if word: 7 | x = word.split(" ", 1) 8 | if len(x) != 2: 9 | errors.append(word) 10 | else: 11 | success.append([x[0], x[1]]) 12 | return success, errors -------------------------------------------------------------------------------- /static/add-new-word/file.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | h1{ 5 | font-size:26px; 6 | text-align: center; 7 | } 8 | form{ 9 | margin: 10px; 10 | } 11 | input{ 12 | margin: 10px; 13 | } -------------------------------------------------------------------------------- /static/add-new-word/hand.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | #submit{ 5 | margin: 10px; 6 | } 7 | textarea{ 8 | margin:10px; 9 | } 10 | h1{ 11 | font-size: 28px; 12 | text-align: center; 13 | } 14 | .example{ 15 | margin-left: 10px; 16 | font-size: 20px; 17 | } 18 | .example-data{ 19 | margin: 10px; 20 | } -------------------------------------------------------------------------------- /static/add-new-word/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/add-new-word/new.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | button{ 5 | margin: 5px; 6 | } 7 | p{ 8 | margin: 10px; 9 | } -------------------------------------------------------------------------------- /static/error_handler/404.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | h1{ 5 | font-size: 26px; 6 | margin: 10px; 7 | } 8 | p{ 9 | margin: 10px; 10 | } 11 | button{ 12 | margin: 10px; 13 | } -------------------------------------------------------------------------------- /static/error_handler/500.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | h1{ 5 | font-size: 26px; 6 | margin: 10px; 7 | } 8 | button{ 9 | margin: 10px; 10 | } -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pynickle/cishen/0f64d80650bdd56d9151afd8365eb2f572149059/static/favicon.ico -------------------------------------------------------------------------------- /static/jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
    ",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0 -------------------------------------------------------------------------------- /static/login/login.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: monospace; 3 | } 4 | 5 | .others { 6 | text-align: center; 7 | } 8 | 9 | .admin-login { 10 | margin: 20px; 11 | } 12 | 13 | .title { 14 | font-size: 24px; 15 | text-align: center; 16 | margin-top: 10px; 17 | } 18 | 19 | .input { 20 | margin-top: 10px; 21 | margin-bottom: 10px; 22 | margin-right: 100px; 23 | } 24 | 25 | .image { 26 | margin: 0 auto; 27 | transition: all 0.5s; 28 | } 29 | 30 | .image:hover{ 31 | transform: scale(1.1); 32 | } 33 | 34 | .third-party { 35 | text-align: center; 36 | font-size: 12px; 37 | color: rgba(0, 0, 0, 0.5); 38 | margin-bottom: 10px; 39 | } -------------------------------------------------------------------------------- /static/login/login.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pynickle/cishen/0f64d80650bdd56d9151afd8365eb2f572149059/static/login/login.js -------------------------------------------------------------------------------- /static/main.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: monospace; 3 | } 4 | 5 | button { 6 | margin: 5px; 7 | } 8 | 9 | .title { 10 | margin-top: 5px; 11 | font-size: 28px; 12 | text-align: center; 13 | } 14 | 15 | .msg { 16 | margin: 10px; 17 | } 18 | 19 | #info { 20 | margin: 10px; 21 | } 22 | #box-one{ 23 | width: 100%; 24 | height: 500px; 25 | } 26 | .introduction{ 27 | width: 45%; 28 | float: left; 29 | } 30 | .intro{ 31 | font-size: 30px; 32 | text-align: center; 33 | margin-top: 50px; 34 | } 35 | .recite{ 36 | margin-top: 100px; 37 | margin-left: 100px; 38 | } 39 | .logo{ 40 | width: 45%; 41 | float: left; 42 | margin-left: 20px; 43 | } 44 | .logo img{ 45 | margin-top: 20px; 46 | } 47 | .btn{ 48 | margin-top: 10%; 49 | align-items: center; 50 | text-align: center; 51 | } -------------------------------------------------------------------------------- /static/main.js: -------------------------------------------------------------------------------- 1 | $("#button").click(function () { 2 | $("#navbar-item").animate(0, function () { 3 | if ($("#navbar-item").is(":visible")) { 4 | $("#button").attr("class", "navbar-burger burger") 5 | $("#navbar-item").attr("class", "navbar-menu") 6 | } else { 7 | $("#button").attr("class", "navbar-burger burger is-active") 8 | $("#navbar-item").attr("class", "navbar-menu is-active") 9 | } 10 | }) 11 | }) -------------------------------------------------------------------------------- /static/recite-words/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/recite-words/recite.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | p{ 5 | margin: 10px; 6 | } 7 | input{ 8 | margin: 10px; 9 | } 10 | .data{ 11 | width: 200px; 12 | } 13 | .form{ 14 | text-align: center; 15 | } 16 | .chinese{ 17 | margin-top: 25px; 18 | font-size: 24px; 19 | } 20 | .right{ 21 | display: none; 22 | font-size: 48px; 23 | margin: 0 auto; 24 | margin-top: 50px; 25 | } 26 | .wrong{ 27 | display: none; 28 | font-size: 48px; 29 | margin: 0 auto; 30 | margin-top: 50px; 31 | } -------------------------------------------------------------------------------- /static/recite-words/recite.js: -------------------------------------------------------------------------------- 1 | function getArgs(name) { 2 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); 3 | var r = window.location.search.substr(1).match(reg); 4 | if (r != null) { 5 | return unescape(r[2]); 6 | } 7 | return null; 8 | } 9 | 10 | function sleep(ms) { 11 | return new Promise(resolve => setTimeout(resolve, ms)); 12 | } 13 | 14 | async function submit() { 15 | var input = document.getElementById("input").value; 16 | var english = document.getElementById("english").innerText; 17 | var choice = parseInt(getArgs("choice")); 18 | console.log(choice) 19 | if(!choice){ 20 | choice = 0 21 | } 22 | choice += 1; 23 | console.log(choice); 24 | if (input.toUpperCase() == english.toUpperCase()) { 25 | document.getElementById("right").style.display = "block"; 26 | await sleep(500); 27 | if(!document.getElementById("come")){ 28 | window.location = "/recite-words?choice=" + choice.toString() + "&wrong=False"; 29 | } else{ 30 | window.location = "/recite-wrong-words?choice=" + choice.toString() + "&wrong=False"; 31 | } 32 | } else{ 33 | document.getElementById("wrong").style.display = "block"; 34 | await sleep(500); 35 | if(!document.getElementById("come")){ 36 | window.location = "/recite-words?choice=" + choice.toString() + "&wrong=True"; 37 | } else{ 38 | window.location = "/recite-wrong-words?choice=" + choice.toString() + "&wrong=True"; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /static/recite-words/result.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-size: monospace; 3 | } 4 | p{ 5 | margin: 10px; 6 | } 7 | input{ 8 | margin: 10px; 9 | } 10 | table{ 11 | margin: 15px; 12 | } 13 | .button{ 14 | display: block; 15 | margin: 0 auto; 16 | } 17 | .status{ 18 | font-size: 24px; 19 | text-align: center; 20 | } -------------------------------------------------------------------------------- /static/registe/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/registe/registe.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: monospace; 3 | } 4 | 5 | .others { 6 | text-align: center; 7 | } 8 | 9 | .admin-login { 10 | margin: 20px; 11 | } 12 | 13 | .title { 14 | font-size: 24px; 15 | text-align: center; 16 | margin-top: 10px; 17 | } 18 | 19 | .input { 20 | margin-top: 10px; 21 | margin-bottom: 10px; 22 | margin-right: 100px; 23 | } 24 | 25 | .image { 26 | margin: 0 auto; 27 | transition: all 0.5s; 28 | } 29 | 30 | .image:hover{ 31 | transform: scale(1.1); 32 | } 33 | 34 | .third-party { 35 | text-align: center; 36 | font-size: 12px; 37 | color: rgba(0, 0, 0, 0.5); 38 | margin-bottom: 10px; 39 | } -------------------------------------------------------------------------------- /static/search-words/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/search-words/result.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | table{ 5 | margin: 10px; 6 | } 7 | h1{ 8 | font-size: 26px; 9 | text-align: center; 10 | margin: 10px; 11 | } -------------------------------------------------------------------------------- /static/search-words/search.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | input{ 5 | margin: 10px; 6 | } -------------------------------------------------------------------------------- /static/see-words/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/see-words/see.css: -------------------------------------------------------------------------------- 1 | table{ 2 | margin: 10px; 3 | } 4 | button{ 5 | margin: 10px; 6 | } 7 | h1{ 8 | margin-top: 5px; 9 | font-size: 24px; 10 | text-align: center; 11 | } 12 | .return-index{ 13 | float: left; 14 | } 15 | .delete-all{ 16 | float: left; 17 | margin-top: 10px; 18 | } 19 | .prev-next{ 20 | text-align: center; 21 | } -------------------------------------------------------------------------------- /static/settings/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/settings/settings.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | h1{ 5 | font-size: 26px; 6 | text-align: center; 7 | margin: 10px; 8 | } 9 | p{ 10 | margin: 10px; 11 | } 12 | input{ 13 | margin: 10px; 14 | } 15 | .youdict{ 16 | float:left; 17 | margin: 10px; 18 | } 19 | .hujiang{ 20 | float:left; 21 | margin: 10px; 22 | } 23 | .spider-progress{ 24 | margin: 10px; 25 | width: 50%; 26 | } 27 | article{ 28 | margin: 10px; 29 | } -------------------------------------------------------------------------------- /static/word-books/books.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | input{ 5 | margin: 10px; 6 | } 7 | h1{ 8 | font-size: 24px; 9 | text-align: center; 10 | } -------------------------------------------------------------------------------- /static/word-books/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/wrong-words/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/wrong-words/recite.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | p{ 5 | margin: 10px; 6 | } 7 | input{ 8 | margin: 10px; 9 | } 10 | .data{ 11 | width: 200px; 12 | } -------------------------------------------------------------------------------- /static/wrong-words/result.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | p{ 5 | margin: 10px; 6 | } 7 | input{ 8 | margin: 10px; 9 | } -------------------------------------------------------------------------------- /static/wrong-words/wrong.css: -------------------------------------------------------------------------------- 1 | *{ 2 | font-family: monospace; 3 | } 4 | table{ 5 | margin: 10px; 6 | } 7 | button{ 8 | margin: 10px; 9 | } 10 | h1{ 11 | margin-top: 5px; 12 | font-size: 24px; 13 | text-align: center; 14 | } -------------------------------------------------------------------------------- /templates/add-new-word/failure1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

    你的文件:{{filename}}读取失败

    10 | 11 | 12 | -------------------------------------------------------------------------------- /templates/add-new-word/failure2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

    你的文件:{{filename}}上传失败

    8 | 9 | -------------------------------------------------------------------------------- /templates/add-new-word/file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    上传文件

    12 |
    13 | 14 |
    15 | 16 |
    17 | 18 | -------------------------------------------------------------------------------- /templates/add-new-word/hand.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    11 | 12 |
    13 | 14 |
    15 |
    16 |

    示例:

    17 |
    18 |

    book 书本

    19 |

    write 写,书写

    20 |
    21 |
    22 | 23 | -------------------------------------------------------------------------------- /templates/add-new-word/new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    注意:爬取单词功能请在设置中查看!

    12 | 13 | 14 | -------------------------------------------------------------------------------- /templates/error-handler/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

    404:

    10 |

    {{exception}}

    11 | 12 | 13 | -------------------------------------------------------------------------------- /templates/error-handler/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

    500

    10 | 11 | 12 | -------------------------------------------------------------------------------- /templates/login/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for msg in get_flashed_messages() %} 12 |
    13 |
    14 |

    消息

    15 | 17 |
    18 |
    19 | {{ msg }} 20 |
    21 |
    22 | {% endfor %} 23 |

    登录

    24 | 33 |
    34 |

    -- 第三方账号登录 --

    35 |
    36 | 38 |
    39 |
    40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 词神 9 | 10 | 11 | 12 | {% for msg in get_flashed_messages() %} 13 |
    14 |
    15 |

    消息

    16 | 18 |
    19 |
    20 | {{ msg }} 21 |
    22 |
    23 | {% endfor %} 24 | 94 |
    95 |
    96 |

    词神,一个专注于
    背单词的网站

    97 |
    98 |

    99 | 100 |
    101 |
    102 | 105 |
    106 | 107 | 108 | 109 | 129 | 130 | -------------------------------------------------------------------------------- /templates/recite-words/recite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 |

    {{ word[1] }}

    14 | 15 |
    16 | 17 | 18 | 19 | 20 | {% if come %} 21 | 22 | {% endif %} 23 |
    24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/recite-words/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    {{ status }}

    12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    原词你的答案正确答案
    {{ word.chinese }}{{ data }}{{ word.english }}
    28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /templates/registe/registe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for msg in get_flashed_messages() %} 12 |
    13 |
    14 |

    消息

    15 | 17 |
    18 |
    19 | {{ msg }} 20 |
    21 |
    22 | {% endfor %} 23 |

    注册

    24 | 33 |
    34 |

    -- 第三方账号登录 --

    35 |
    36 | 38 |
    39 |
    40 | 41 | 42 | -------------------------------------------------------------------------------- /templates/search-words/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    查询结果

    12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for word in result %} 21 | 22 | 23 | 24 | 25 | {% endfor %} 26 | 27 |
    英语中文
    {{ word.english }}{{ word.chinese }}
    28 | 29 | 30 | -------------------------------------------------------------------------------- /templates/search-words/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 | 11 |
    12 | 13 |
    14 | 15 | -------------------------------------------------------------------------------- /templates/see-words/see.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    总单词表

    12 | 13 |
    14 | 15 |
    16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for word in words.items %} 26 | 27 | 28 | 29 | 36 | 37 | {% endfor %} 38 | 39 |
    英语中文删除
    {{ word.english }}{{ word.chinese }} 30 |
    31 | 32 | 33 | 34 |
    35 |
    40 |
    41 | {% if words.has_prev %} 42 | 43 | {% endif %} 44 | 45 | {% if words.has_next %} 46 | 47 | {% endif %} 48 |
    49 | 50 | 69 | 70 | -------------------------------------------------------------------------------- /templates/settings/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% for msg in get_flashed_messages() %} 12 |
    13 |
    14 |

    消息

    15 | 17 |
    18 |
    19 | {{ msg }} 20 |
    21 |
    22 | {% endfor %} 23 |

    设置

    24 | 25 |
    26 |

    每次背诵单词数

    27 | 28 |

    查询单词相似度(0-1)

    29 | 30 |
    31 | 32 |
    33 |
    34 |

    优词网站爬取

    35 |
    36 | {{ youdictform.page_number_begin.label }} 37 | {{ youdictform.page_number_begin }} 38 |
    39 | {{ youdictform.page_number_all.label }} 40 | {{ youdictform.page_number_all }} 41 |
    42 | {{ youdictform.submit }} 43 |
    44 |
    45 |
    46 |

    沪江网站爬取

    47 |
    48 | {{ hujiangform.page_number_begin.label }} 49 | {{ hujiangform.page_number_begin }} 50 |
    51 | {{ hujiangform.page_number_all.label }} 52 | {{ hujiangform.page_number_all }} 53 |
    54 | {{ hujiangform.submit }} 55 |
    56 |
    57 |









    58 |
    59 |

    爬取过程中请勿关闭页面!

    60 | 61 |
    62 | 63 | -------------------------------------------------------------------------------- /templates/word-books/books.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

    词书

    10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 |
    17 | 18 |
    19 |
    20 | 21 |
    22 |
    23 | 24 |
    25 |
    26 | 27 |
    28 |
    29 | 30 |
    31 |
    32 | 33 |
    34 | 35 | -------------------------------------------------------------------------------- /templates/wrong-words/wrong.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

    错题表

    12 | 13 |
    14 | 15 |
    16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for word in words %} 26 | 27 | 28 | 29 | 36 | 37 | {% endfor %} 38 | 39 |
    英语中文删除
    {{ word.english }}{{ word.chinese }} 30 |
    31 | 32 | 33 | 34 |
    35 |
    40 | 41 | 42 | -------------------------------------------------------------------------------- /test/test-output.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_flask.py: -------------------------------------------------------------------------------- 1 | import app 2 | import pytest 3 | import os 4 | import tempfile 5 | import sys 6 | 7 | path = "\\".join(__file__.split("\\")[:-1]) + "/../" 8 | sys.path.append(path) 9 | 10 | 11 | @pytest.fixture 12 | def client(): 13 | db_fd, app.app.config['DATABASE'] = tempfile.mkstemp() 14 | app.app.config['TESTING'] = True 15 | client = app.app.test_client() 16 | 17 | with app.app.app_context(): 18 | app.db.create_all() 19 | 20 | yield client 21 | 22 | os.close(db_fd) 23 | os.unlink(app.app.config['DATABASE']) 24 | 25 | 26 | def test_index(client): 27 | rv = client.get('/') 28 | assert '词神' in rv.data.decode(encoding="utf-8") 29 | --------------------------------------------------------------------------------