├── .github ├── ISSUE_TEMPLATE │ ├── -bug--------.md │ └── -featurerequest--------.md └── workflows │ ├── CI.yml │ └── release.yml ├── .gitignore ├── CHANGELOG ├── LICENSE ├── README-zh.md ├── README.md ├── database ├── artist.md ├── build.py ├── character.md ├── cosplayer.md ├── female.md ├── group.md ├── language.md ├── male.md ├── misc.md ├── mixed.md ├── other.md ├── parody.md ├── reclass.md ├── rows.md └── translate.json ├── res ├── appimage │ └── ehentai.AppRun │ │ ├── .DirIcon │ │ ├── AppRun │ │ ├── EHentai.desktop │ │ └── EHentai.png ├── icon │ ├── Icon.icns │ ├── bubble_r_9.9.png │ ├── clear_off.png │ ├── clear_on.png │ ├── fold1.png │ ├── fold2.png │ ├── ic_get_app_black_36dp.png │ ├── ic_settings_black_36dp.png │ ├── icon.ico │ ├── icon_comment.png │ ├── icon_like.png │ ├── icon_like_off.png │ ├── loading.svg │ ├── loading │ │ ├── loading_1.png │ │ ├── loading_10.png │ │ ├── loading_2.png │ │ ├── loading_3.png │ │ ├── loading_4.png │ │ ├── loading_5.png │ │ ├── loading_6.png │ │ ├── loading_7.png │ │ ├── loading_8.png │ │ └── loading_9.png │ ├── loading_gif.gif │ ├── loading_gif.webp │ ├── logo_round.png │ ├── new.svg │ └── waifu2x.svg ├── images.qrc ├── json │ └── translate.json ├── theme │ ├── dark_pink.qss │ ├── dark_pink.rcc │ ├── dark_pink │ │ ├── disabled │ │ │ ├── base.svg │ │ │ ├── branch-closed.svg │ │ │ ├── branch-end.svg │ │ │ ├── branch-more.svg │ │ │ ├── branch-open.svg │ │ │ ├── checkbox_checked.svg │ │ │ ├── checkbox_checked_invert.svg │ │ │ ├── checkbox_indeterminate.svg │ │ │ ├── checkbox_indeterminate_invert.svg │ │ │ ├── checkbox_unchecked.svg │ │ │ ├── checkbox_unchecked_invert.svg │ │ │ ├── checklist.svg │ │ │ ├── checklist_indeterminate.svg │ │ │ ├── checklist_indeterminate_invert.svg │ │ │ ├── checklist_invert.svg │ │ │ ├── close.svg │ │ │ ├── downarrow.svg │ │ │ ├── downarrow2.svg │ │ │ ├── float.svg │ │ │ ├── leftarrow.svg │ │ │ ├── leftarrow2.svg │ │ │ ├── radiobutton_checked.svg │ │ │ ├── radiobutton_checked_invert.svg │ │ │ ├── radiobutton_unchecked.svg │ │ │ ├── radiobutton_unchecked_invert.svg │ │ │ ├── rightarrow.svg │ │ │ ├── rightarrow2.svg │ │ │ ├── sizegrip.svg │ │ │ ├── slider.svg │ │ │ ├── splitter-horizontal.svg │ │ │ ├── splitter-vertical.svg │ │ │ ├── tab_close.svg │ │ │ ├── toolbar-handle-horizontal.svg │ │ │ ├── toolbar-handle-vertical.svg │ │ │ ├── uparrow.svg │ │ │ ├── uparrow2.svg │ │ │ └── vline.svg │ │ └── primary │ │ │ ├── base.svg │ │ │ ├── branch-closed.svg │ │ │ ├── branch-end.svg │ │ │ ├── branch-more.svg │ │ │ ├── branch-open.svg │ │ │ ├── checkbox_checked.svg │ │ │ ├── checkbox_checked_invert.svg │ │ │ ├── checkbox_indeterminate.svg │ │ │ ├── checkbox_indeterminate_invert.svg │ │ │ ├── checkbox_unchecked.svg │ │ │ ├── checkbox_unchecked_invert.svg │ │ │ ├── checklist.svg │ │ │ ├── checklist_indeterminate.svg │ │ │ ├── checklist_indeterminate_invert.svg │ │ │ ├── checklist_invert.svg │ │ │ ├── close.svg │ │ │ ├── downarrow.svg │ │ │ ├── downarrow2.svg │ │ │ ├── float.svg │ │ │ ├── leftarrow.svg │ │ │ ├── leftarrow2.svg │ │ │ ├── radiobutton_checked.svg │ │ │ ├── radiobutton_checked_invert.svg │ │ │ ├── radiobutton_unchecked.svg │ │ │ ├── radiobutton_unchecked_invert.svg │ │ │ ├── rightarrow.svg │ │ │ ├── rightarrow2.svg │ │ │ ├── sizegrip.svg │ │ │ ├── slider.svg │ │ │ ├── splitter-horizontal.svg │ │ │ ├── splitter-vertical.svg │ │ │ ├── tab_close.svg │ │ │ ├── toolbar-handle-horizontal.svg │ │ │ ├── toolbar-handle-vertical.svg │ │ │ ├── uparrow.svg │ │ │ ├── uparrow2.svg │ │ │ └── vline.svg │ ├── dark_teal.qrc │ ├── dark_teal.qss │ ├── light_pink.qss │ ├── light_pink.rcc │ ├── light_pink │ │ ├── disabled │ │ │ ├── base.svg │ │ │ ├── branch-closed.svg │ │ │ ├── branch-end.svg │ │ │ ├── branch-more.svg │ │ │ ├── branch-open.svg │ │ │ ├── checkbox_checked.svg │ │ │ ├── checkbox_checked_invert.svg │ │ │ ├── checkbox_indeterminate.svg │ │ │ ├── checkbox_indeterminate_invert.svg │ │ │ ├── checkbox_unchecked.svg │ │ │ ├── checkbox_unchecked_invert.svg │ │ │ ├── checklist.svg │ │ │ ├── checklist_indeterminate.svg │ │ │ ├── checklist_indeterminate_invert.svg │ │ │ ├── checklist_invert.svg │ │ │ ├── close.svg │ │ │ ├── downarrow.svg │ │ │ ├── downarrow2.svg │ │ │ ├── float.svg │ │ │ ├── leftarrow.svg │ │ │ ├── leftarrow2.svg │ │ │ ├── radiobutton_checked.svg │ │ │ ├── radiobutton_checked_invert.svg │ │ │ ├── radiobutton_unchecked.svg │ │ │ ├── radiobutton_unchecked_invert.svg │ │ │ ├── rightarrow.svg │ │ │ ├── rightarrow2.svg │ │ │ ├── sizegrip.svg │ │ │ ├── slider.svg │ │ │ ├── splitter-horizontal.svg │ │ │ ├── splitter-vertical.svg │ │ │ ├── tab_close.svg │ │ │ ├── toolbar-handle-horizontal.svg │ │ │ ├── toolbar-handle-vertical.svg │ │ │ ├── uparrow.svg │ │ │ ├── uparrow2.svg │ │ │ └── vline.svg │ │ └── primary │ │ │ ├── base.svg │ │ │ ├── branch-closed.svg │ │ │ ├── branch-end.svg │ │ │ ├── branch-more.svg │ │ │ ├── branch-open.svg │ │ │ ├── checkbox_checked.svg │ │ │ ├── checkbox_checked_invert.svg │ │ │ ├── checkbox_indeterminate.svg │ │ │ ├── checkbox_indeterminate_invert.svg │ │ │ ├── checkbox_unchecked.svg │ │ │ ├── checkbox_unchecked_invert.svg │ │ │ ├── checklist.svg │ │ │ ├── checklist_indeterminate.svg │ │ │ ├── checklist_indeterminate_invert.svg │ │ │ ├── checklist_invert.svg │ │ │ ├── close.svg │ │ │ ├── downarrow.svg │ │ │ ├── downarrow2.svg │ │ │ ├── float.svg │ │ │ ├── leftarrow.svg │ │ │ ├── leftarrow2.svg │ │ │ ├── radiobutton_checked.svg │ │ │ ├── radiobutton_checked_invert.svg │ │ │ ├── radiobutton_unchecked.svg │ │ │ ├── radiobutton_unchecked_invert.svg │ │ │ ├── rightarrow.svg │ │ │ ├── rightarrow2.svg │ │ │ ├── sizegrip.svg │ │ │ ├── slider.svg │ │ │ ├── splitter-horizontal.svg │ │ │ ├── splitter-vertical.svg │ │ │ ├── tab_close.svg │ │ │ ├── toolbar-handle-horizontal.svg │ │ │ ├── toolbar-handle-vertical.svg │ │ │ ├── uparrow.svg │ │ │ ├── uparrow2.svg │ │ │ └── vline.svg │ ├── light_teal.qrc │ ├── light_teal.qss │ └── scrollbar │ │ ├── scrollbar_arrowdown_down.png │ │ ├── scrollbar_arrowdown_highlight.png │ │ ├── scrollbar_arrowdown_normal.png │ │ ├── scrollbar_arrowleft_down.png │ │ ├── scrollbar_arrowleft_highlight.png │ │ ├── scrollbar_arrowleft_normal.png │ │ ├── scrollbar_arrowright_down.png │ │ ├── scrollbar_arrowright_highlight.png │ │ ├── scrollbar_arrowright_normal.png │ │ ├── scrollbar_arrowup_down.png │ │ ├── scrollbar_arrowup_highlight.png │ │ ├── scrollbar_arrowup_normal.png │ │ ├── scrollbar_bar_highlight.png │ │ ├── scrollbar_bar_normal.png │ │ ├── scrollbar_bkg.png │ │ ├── scrollbar_horzbar_down.png │ │ ├── scrollbar_horzbar_highlight.png │ │ └── scrollbar_horzbar_normal.png ├── title_bar │ ├── black_close_button_57_40.png │ ├── black_down_button_57_40.png │ ├── black_max_button_57_40.png │ ├── black_max_button_pressed_57_40.png │ ├── black_min_button_57_40.png │ ├── black_min_button_pressed_57_40.png │ ├── black_return_button_60_40.png │ ├── black_return_button_hover_60_40.png │ ├── black_return_button_pressed_60_40.png │ ├── close_button_hover_57_40.png │ ├── close_button_pressed_57_40.png │ ├── down_button_pressed_57_40.png │ ├── green_down_button_hover_57_40.png │ ├── green_max_button_hover_57_40.png │ ├── green_min_button_hover_57_40.png │ ├── open_black_close_button_57_40.png │ ├── open_black_max_button_57_40.png │ ├── open_black_min_button_57_40.png │ ├── open_white_close_button_57_40.png │ ├── white_close_button_57_40.png │ ├── white_down_57_40.png │ ├── white_down_button_57_40.png │ ├── white_max_button_57_40.png │ ├── white_min_button_57_40.png │ ├── white_return_button_60_40.png │ ├── white_return_button_hover_60_40.png │ └── white_return_button_pressed_60_40.png └── tr │ ├── str_en.qm │ ├── str_en.ts │ ├── str_hk.qm │ ├── str_hk.ts │ ├── tr_en.qm │ ├── tr_hk.qm │ ├── ui_en.qm │ ├── ui_en.ts │ ├── ui_hk.qm │ └── ui_hk.ts ├── script ├── build_qrc.py ├── build_translate.py └── build_ui.py ├── src ├── component │ ├── __init__.py │ ├── box │ │ ├── __init__.py │ │ ├── wheel_combo_box.py │ │ ├── wheel_double_spin_box.py │ │ ├── wheel_slider.py │ │ └── wheel_spin_box.py │ ├── button │ │ ├── __init__.py │ │ ├── icon_tool_button.py │ │ └── switch_button.py │ ├── dialog │ │ ├── __init__.py │ │ ├── base_mask_dialog.py │ │ └── loading_dialog.py │ ├── label │ │ ├── __init__.py │ │ ├── gif_group_label.py │ │ ├── gif_label.py │ │ ├── head_label.py │ │ ├── loading_label.py │ │ └── msg_label.py │ ├── layout │ │ ├── __init__.py │ │ └── flow_layout.py │ ├── line_edit │ │ ├── __init__.py │ │ └── search_line_edit.py │ ├── list │ │ ├── __init__.py │ │ ├── base_list_widget.py │ │ ├── category_list_widget.py │ │ ├── comic_list_widget.py │ │ ├── search_setting_item_widget.py │ │ ├── tag_list_widget.py │ │ └── user_list_widget.py │ ├── progress_bar │ │ ├── __init__.py │ │ └── dwater_progress_bar.py │ ├── scroll │ │ ├── __init__.py │ │ ├── read_scroll.py │ │ ├── smooth_scroll.py │ │ └── smooth_scroll_bar.py │ ├── scroll_area │ │ ├── __init__.py │ │ └── smooth_scroll_area.py │ ├── system_tray_icon │ │ ├── __init__.py │ │ └── my_system_tray_icon.py │ └── widget │ │ ├── __init__.py │ │ ├── animation_stack_widget.py │ │ ├── comic_item_widget.py │ │ ├── comment_item_widget.py │ │ ├── comment_widget.py │ │ ├── main_widget.py │ │ ├── navigation_widget.py │ │ ├── pop_stack_widget.py │ │ ├── setting_item_widget.py │ │ ├── stack_widget.py │ │ ├── title_bar_widget.py │ │ └── windows │ │ ├── __init__.py │ │ ├── c_structures.py │ │ ├── frame_less_widget.py │ │ └── window_effect.py ├── config │ ├── __init__.py │ ├── config.py │ └── setting.py ├── images_rc.py ├── interface │ ├── __init__.py │ ├── ui_book_info.py │ ├── ui_category.py │ ├── ui_comic_item.py │ ├── ui_comment.py │ ├── ui_comment_item.py │ ├── ui_doh_dns.py │ ├── ui_download.py │ ├── ui_download_dir.py │ ├── ui_favorite_info.py │ ├── ui_favority.py │ ├── ui_help.py │ ├── ui_help_log_widget.py │ ├── ui_history.py │ ├── ui_host.py │ ├── ui_index.py │ ├── ui_line_edit_help_widget.py │ ├── ui_local.py │ ├── ui_local_eps.py │ ├── ui_local_fold.py │ ├── ui_login.py │ ├── ui_login_proxy_widget.py │ ├── ui_login_widget.py │ ├── ui_main.py │ ├── ui_main_windows.py │ ├── ui_navigation.py │ ├── ui_rank.py │ ├── ui_read_tool.py │ ├── ui_search.py │ ├── ui_setting_item.py │ ├── ui_setting_item_theme.py │ ├── ui_setting_new.py │ ├── ui_title_bar.py │ └── ui_waifu2x_tool.py ├── qt_error.py ├── qt_owner.py ├── requirements.txt ├── server │ ├── __init__.py │ ├── req.py │ ├── res.py │ ├── server.py │ └── user_handler.py ├── start.py ├── task │ ├── __init__.py │ ├── qt_task.py │ ├── task_download.py │ ├── task_http.py │ ├── task_local.py │ ├── task_qimage.py │ └── task_waifu2x.py ├── tools │ ├── __init__.py │ ├── book.py │ ├── langconv.py │ ├── log.py │ ├── login_proxy.py │ ├── qt_domain.py │ ├── singleton.py │ ├── status.py │ ├── str.py │ ├── tool.py │ └── zh_wiki.py ├── util │ ├── __init__.py │ ├── status.py │ └── str.py └── view │ ├── __init__.py │ ├── comment │ ├── __init__.py │ └── comment_view.py │ ├── download │ ├── __init__.py │ ├── download_db.py │ ├── download_dir_view.py │ ├── download_item.py │ ├── download_status.py │ └── download_view.py │ ├── help │ ├── __init__.py │ ├── help_log_widget.py │ └── help_view.py │ ├── info │ ├── __init__.py │ └── book_info_view.py │ ├── main │ ├── __init__.py │ └── main_view.py │ ├── read │ ├── __init__.py │ ├── read_enum.py │ ├── read_frame.py │ ├── read_graphics.py │ ├── read_pool.py │ ├── read_proxy.py │ ├── read_qgraphics_proxy_widget.py │ ├── read_tool.py │ └── read_view.py │ ├── search │ ├── __init__.py │ ├── rank_view.py │ └── search_view.py │ ├── setting │ ├── __init__.py │ └── setting_view.py │ ├── tool │ ├── __init__.py │ ├── doh_dns_view.py │ ├── local_fold_view.py │ ├── local_read_db.py │ ├── local_read_eps_view.py │ ├── local_read_view.py │ └── waifu2x_tool_view.py │ └── user │ ├── __init__.py │ ├── favorite_info_view.py │ ├── favorite_view.py │ ├── history_view.py │ ├── login_host_widget.py │ ├── login_proxy_widget.py │ ├── login_view.py │ └── login_widget.py ├── translate ├── str_en.ts ├── str_hk.ts ├── ui_en.qm ├── ui_en.ts └── ui_hk.ts └── ui ├── component ├── ui_comic_item.ui ├── ui_comment_item.ui ├── ui_download_dir.ui ├── ui_help_log_widget.ui ├── ui_host.ui ├── ui_line_edit_help_widget.ui ├── ui_local_fold.ui ├── ui_login_proxy_widget.ui ├── ui_login_widget.ui ├── ui_navigation.ui ├── ui_read_tool.ui └── ui_title_bar.ui ├── ui_book_info.ui ├── ui_category.ui ├── ui_comment.ui ├── ui_doh_dns.ui ├── ui_download.ui ├── ui_favorite_info.ui ├── ui_favority.ui ├── ui_help.ui ├── ui_history.ui ├── ui_index.ui ├── ui_local.ui ├── ui_local_eps.ui ├── ui_login.ui ├── ui_main.ui ├── ui_main_windows.ui ├── ui_rank.ui ├── ui_search.ui ├── ui_setting_new.ui └── ui_waifu2x_tool.ui /.github/ISSUE_TEMPLATE/-bug--------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[BUG] 问题提交模板" 3 | about: 请从符号">"后面开始填写内容 4 | title: "" 5 | labels: bug 6 | 7 | --- 8 | 9 | ### 操作系统(如MacOS 10.15) 10 | > 11 | 12 | 13 | ### 网络环境(移动,联通,电信,移动宽带,联通宽带,电信宽带,等等..) 14 | > 15 | 16 | 17 | ### 问题描述(简要描述发生的问题) 18 | > 19 | 20 | 21 | ### 复现步骤(详细描述导致问题产生的操作步骤,如果能稳定复现) 22 | > 23 | 24 | 25 | 26 | ### 日志提交(问题截图或者日志,日志在logs目录) 27 | > 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-featurerequest--------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[FeatureRequest] 功能请求模板" 3 | about: 提交你希望增加的功能 4 | title: "" 5 | labels: enhancement 6 | 7 | --- 8 | 9 | ### 功能描述(请清晰的、详细的描述你想要的功能) 10 | > 11 | 12 | ### 附加信息(其他的与功能相关的附加信息) 13 | > 14 | 15 | ### 效果演示(可以提供可借鉴的图片) 16 | > 17 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | windows: 14 | runs-on: windows-latest 15 | env: 16 | PACKAGENAME: ehentai_py37_windows_x64 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.7 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.7 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install pyinstaller==4.8 27 | pip install pywin32==302 28 | pip install -r src\requirements.txt 29 | - name: Build 30 | run: | 31 | cd src 32 | cp ..\res\icon\icon.ico .\ 33 | 34 | pyinstaller -F -w -i icon.ico start.py 35 | mv dist ehentai 36 | cp ..\LICENSE ehentai\ 37 | cp ..\CHANGELOG ehentai\ 38 | cd .. 39 | mkdir ${{ env.PACKAGENAME }} 40 | mv src\ehentai ${{ env.PACKAGENAME }} 41 | 7z a -r "$($Env:PACKAGENAME + '.zip')" "ehentai" 42 | - name: Upload 43 | uses: actions/upload-artifact@v2 44 | with: 45 | name: ${{ env.PACKAGENAME }} 46 | path: ${{ env.PACKAGENAME }} 47 | retention-days: 7 48 | 49 | windows7: 50 | runs-on: windows-latest 51 | env: 52 | PACKAGENAME: ehentai_py37_windows7_x64 53 | steps: 54 | - uses: actions/checkout@v2 55 | - name: Set up Python 3.7 56 | uses: actions/setup-python@v2 57 | with: 58 | python-version: 3.7 59 | - name: Install dependencies 60 | run: | 61 | python -m pip install --upgrade pip 62 | pip install pyinstaller==4.6 63 | pip install pywin32==302 64 | pip install PySide6==6.1.3 65 | pip install urllib3==1.26.15 66 | pip install requests==2.28.2 67 | pip install pillow==8.3.2 68 | pip install Pysocks==1.7.1 69 | pip install bs4==0.0.1 70 | pip install lxml==4.6.4 71 | pip install natsort==8.2.0 72 | pip install pycryptodomex==3.12.0 73 | pip install waifu2x-vulkan==1.1.6 74 | - name: Build 75 | run: | 76 | cd src 77 | cp ..\res\icon\icon.ico .\ 78 | pyinstaller -F -w -i icon.ico start.py 79 | mv dist ehentai 80 | cp ..\LICENSE ehentai\ 81 | cp ..\CHANGELOG ehentai\ 82 | cd .. 83 | mkdir ${{ env.PACKAGENAME }} 84 | mv src\ehentai ${{ env.PACKAGENAME }} 85 | 7z a -r "$($Env:PACKAGENAME + '.zip')" "ehentai" 86 | - name: Upload 87 | uses: actions/upload-artifact@v2 88 | with: 89 | name: ${{ env.PACKAGENAME }} 90 | path: ${{ env.PACKAGENAME }} 91 | retention-days: 7 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | 8 | 9 | # Distribution / packaging 10 | src/test 11 | .idea 12 | .Python 13 | test/ 14 | build/ 15 | src/data 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # Cache Files for picacg 135 | cache/ 136 | config.ini 137 | download.db 138 | history.db 139 | 140 | # Downloaded Commies for PicACG 141 | commies/ 142 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # update log file. 2 | # tonquer 3 | ###################################################################################### 4 | # Version: v1.1.1 5 | # 2023/3/19 6 | # 1)修复非滚动模式模糊问题 7 | # 2)新增排行榜 8 | # 3)优化搜索 9 | 10 | # Version: v1.1.0 11 | # 2023/2/26 12 | # 1)取消DOH,SNI功能,使用IP直连 13 | # 2)取消QWebEngineView依赖,升级Qt6,本版本开始无法支持账号登录,仅支持cookie登录 14 | # 3)优化看图流畅度,新增等宽模式 15 | # 4)新增本地漫画功能 16 | # 5)修复tag历史记录搜索,多出来的转义字符 17 | # 6)新增IP分流 18 | 19 | # Version: v1.0.8 20 | # 2021/11/18 21 | # 1)修复搜索页分类标签点了出错 22 | # 2)waifu2x从1.1.1升级到1.1.4 23 | # 2)waifu2x新增tile设置,手动调低该值吗,解决小显存显卡报错 24 | # 3)修复表里站,首页和收藏解析出错了 25 | # 4)支持离线模式,可看下载完成的章节 26 | # 4)下载代码优化,支持下载失败重试,下载和转换可同时进行 27 | # 5)下载多选可以点击开始,部分错误修复 28 | # 6)下载支持目录结构设置,请在设置中修改 29 | # 7)修复MacOs已损坏提示,标签显示不全 30 | # 8)画廊支持预览 31 | # 9)部分错误修复 32 | # 10)DoH自动最优ip选择,请打开软件后等待几秒 33 | # 11)设置中新增字体设置,最小化托盘设置 34 | # 12)看图新增了部分快捷键 35 | 36 | # Version: v1.0.7 37 | # 2021/06/18 38 | # 1)有更新时,不再弹框更新,而在帮助上显示NEW 39 | # 2)看图添加了部分快捷键 40 | # 3)看图放大后,滚轮将滚动最下方才会跳转下一页 41 | # 4)修复MacOS漫画详情页面,tag和分类显示不全 42 | # 4)修复MacOS已损坏提示 43 | # 5)添加观看历史记录 44 | # 6)画廊标签分栏 45 | # 7)搜索显示历史记录 46 | # 8)搜索框输入网址自动跳转 47 | # 9)添加自动登录 48 | # 10)修复留言区顺序 49 | # 11)修复画廊分类重复问题 50 | # 12)修复帮助页issue 跳转按钮点了没反应 51 | # 13)修复图片缓存没生效 52 | 53 | # Version: v1.0.6 54 | # 2021/03/6 55 | # 1)修复部分exhentai解析错误 56 | # 2)支持GIF图 57 | # 3)修复看图下Waifu2x错误 58 | # 4)Fix bugs 59 | 60 | # Version: v1.0.5 61 | # 2022/01/18 62 | # 1)Fix exhentai parse 63 | # 2)Fix not found QtWebEngineProcess.exe 64 | # 3)Add sock5 proxy 65 | # 4)Update ui 66 | 67 | # Version: v1.0.4 68 | # 2021/12/12 69 | # 1)全新UI 70 | 71 | # Version: 1.0.3 72 | # 2021/09/12 73 | 1、Support exhentai 74 | 2、Add Download 75 | 3、Add cookie login 76 | 4、Use DOH,disable SNI。 77 | 5、Fix bug 78 | 简体中文: 79 | 1、新增支持里站 80 | 2、新增下载功能 81 | 3、新增cookie登录 82 | 4、新增DOH查询DNS,禁用SNI,使得大部分地区可以直连E-hentai 83 | 5、大量bug修复 84 | 6、修复不用代理,无法显示google验证 85 | 7、1.0.3.1修复部分帐号打开漫画出现解析失败 86 | 87 | 88 | # Version: 1.0.1 89 | # 2021/08/08 90 | 1、优化账号登录,如需google验证会跳转网页,本地保存cookie 91 | 2、优化看图,可以滚动翻页 92 | 3、设置新增主题 93 | 4、优化漫画详情加载速度,tag可点击 94 | 5、新增分类搜索 95 | 6、新增收藏(需登录) 96 | 97 | 98 | # Version: 1.0.1 99 | # 2021/05/30 100 | 1、目前只有搜索,看图功能 101 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # Ehentai-windows 2 | 3 | ## 简体中文 | [EngLish](README.md) 4 | 5 | [![GitHub](https://img.shields.io/github/license/tonquer/ehentai-windows)](https://raw.githubusercontent.com/tonquer/ehentai-windows/master/LICENSE.txt) 6 | [![GitHub](https://img.shields.io/github/workflow/status/tonquer/ehentai-windows/CI?label=CI)](https://github.com/tonquer/ehentai-windows/actions) 7 | ![Relese](https://img.shields.io/badge/Python-3.7.9%2B-brightgreen) 8 | [![Relese](https://img.shields.io/github/v/release/tonquer/ehentai-windows)](https://github.com/tonquer/ehentai-windows/releases) 9 | ![Relese](https://img.shields.io/github/downloads/tonquer/ehentai-windows/latest/total) 10 | ![Relese](https://img.shields.io/github/downloads/tonquer/ehentai-windows/total) 11 | 12 | - e-hentai, exhentai, PC客户端(支持window、Linux和macOS),界面使用QT。 13 | - 该项目仅供技术研究使用,请勿用于其他用途。 14 | - 如果觉得本项目对你有所帮助,请点个star关注,感谢支持 15 | - 如有使用中遇到问题,欢迎提ISSUE 16 | ## 功能 17 | - 支持搜索,收藏,看图和下载 18 | - 支持表站和里站 19 | - 支持DOH防止DNS污染,默认关闭SNI,防止SNI劫持,大部分地区可不使用代理直连。 20 | 21 | ## 我的其他项目 22 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=tonquer&repo=picacg-windows)](https://github.com/tonquer/picacg-windows) 23 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=tonquer&repo=waifu2x-ncnn-vulkan-python)](https://github.com/tonquer/waifu2x-ncnn-vulkan-python) 24 | ## 如何使用 25 | ### Windows (测试使用win10) 26 | 1. 下载最新的版本 https://github.com/tonquer/ehentai-windows/releases 27 | 2. 解压zip 28 | 3. 打开start.exe 29 | 4. 后续有更新,只需要下载最新版本覆盖原目录即可 30 | 5. 如果无法初始化waifu2x,请更新显卡驱动,安装 [Vs运行库](https://download.visualstudio.microsoft.com/download/pr/366c0fb9-fe05-4b58-949a-5bc36e50e370/015EDD4E5D36E053B23A01ADB77A2B12444D3FB6ECCEFE23E3A8CD6388616A16/VC_redist.x64.exe),如果还是无法启用,说明你的电脑不支持vulkan。 31 | ### macOS (测试使用 macOS 10.15.7) 32 | 1. 下载最新的版本 https://github.com/tonquer/ehentai-windows/releases 33 | 2. 解压 7z 34 | 3. 将解压出的 Ehentai 拖入访达 (Finder) 左侧侧栏的应用程序 (Applications) 文件夹中 35 | 4. 从启动台 (Launchpad) 中找到并打开 Ehentai 36 | #### 对于 M1 Mac 用户 37 | * 作者没有 Arm Mac, 所以没有办法提供已经打包好的应用程序 38 | * 如果您拥有 M1 Mac, 可以尝试参考下面的过程手动运行或者进行打包 39 | ### Linux (测试使用deepin 20.2) 40 | 1. 下载qt依赖, http://ftp.br.debian.org/debian/pool/main/x/xcb-util/libxcb-util1_0.4.0-1+b1_amd64.deb 41 | 2. 安装依赖,sudo dpkg -i ./libxcb-util1_0.4.0-1+b1_amd64.deb 42 | 3. 下载最新的版本 https://github.com/tonquer/ehentai-windows/releases 43 | 4. 解压tar -zxvf bika.tar.gz 44 | 5. cd bika && chmod +x start 45 | 6. ./start 46 | 7. 要想使用waifu2x请确定你的设备支持vulkan,然后安装vulkan驱动包,sudo apt install mesa-vulkan-drivers 47 | ### Beta版本 48 | 1. 请在此处下载 https://github.com/tonquer/ehentai-windows/actions 49 | 50 | ## 如何编译 51 | ### 使用Git Action编译 52 | 1. 查看编译结果[Git Action编译](https://github.com/tonquer/ehentai-windows/actions) 53 | 54 | ## 界面 55 | 56 | 57 | ## 感谢以下项目 58 | ### waifu2x功能 59 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=nagadomi&repo=waifu2x)](https://github.com/nagadomi/waifu2x) 60 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=nihui&repo=waifu2x-ncnn-vulkan)](https://github.com/nagadomi/waifu2x-ncnn-vulkan) 61 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=tonquer&repo=waifu2x-ncnn-vulkan-python)](https://github.com/tonquer/waifu2x-ncnn-vulkan-python) 62 | ### Qt功能 63 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=PyQt5&repo=PyQt)](https://github.com/PyQt5/PyQt) 64 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=PyQt5&repo=PyQtClient)](https://github.com/PyQt5/PyQtClient) 65 | ### Qt皮肤 66 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=satchelwu&repo=QSS-Skin-Builder)](https://github.com/satchelwu/QSS-Skin-Builder) 67 | ### Qt实现平滑滚动 68 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=zhiyiYo&repo=Groove)](https://github.com/zhiyiYo/Groove) 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ehentai-windows 2 | 3 | ## [简体中文](README-zh.md) | EngLish 4 | 5 | [![GitHub](https://img.shields.io/github/license/tonquer/ehentai-windows)](https://raw.githubusercontent.com/tonquer/ehentai-windows/master/LICENSE.txt) 6 | [![GitHub](https://img.shields.io/github/workflow/status/tonquer/ehentai-windows/CI?label=CI)](https://github.com/tonquer/ehentai-windows/actions) 7 | ![Relese](https://img.shields.io/badge/Python-3.7.9%2B-brightgreen) 8 | [![Relese](https://img.shields.io/github/v/release/tonquer/ehentai-windows)](https://github.com/tonquer/ehentai-windows/releases) 9 | ![Relese](https://img.shields.io/github/downloads/tonquer/ehentai-windows/latest/total) 10 | ![Relese](https://img.shields.io/github/downloads/tonquer/ehentai-windows/total) 11 | 12 | - Ehentai PC client (Support Window, Linux and macOS). Interface using QT. 13 | - Only supplies the technical reference, please don't use it for other purposes.。 14 | - If you think this project is helpful to you, please click a Star, thanks. 15 | - If you encounter any problems during use, please mention ISSUE 16 | ## Function 17 | - Support search, collection, view and download 18 | - Support e-hentai and exhentai 19 | - Support DOH, Disable SNI. 20 | 21 | ## My other projects 22 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=tonquer&repo=picacg-windows)](https://github.com/tonquer/picacg-windows) 23 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=tonquer&repo=waifu2x-ncnn-vulkan-python)](https://github.com/tonquer/waifu2x-ncnn-vulkan-python) 24 | ## How to use 25 | ### Windows (Test use win10) 26 | 1. Download the latest version https://github.com/tonquer/ehentai-windows/releases 27 | 2. Unzip 28 | 3. Open start.exe 29 | ### macOS (Test use macOS 10.15.7) 30 | 1. Download the latest version https://github.com/tonquer/ehentai-windows/releases 31 | 2. Unzip 32 | 3. Drag Ehentai into the Applications folder on the left sidebar of the Finder 33 | 4. Find and open Ehentai from the Launchpad 34 | ### Linux (Test use deepin 20.2) 35 | 1. Download qt dependencies http://ftp.br.debian.org/debian/pool/main/x/xcb-util/libxcb-util1_0.4.0-1+b1_amd64.deb 36 | 2. Installation dependencies,sudo dpkg -i ./libxcb-util1_0.4.0-1+b1_amd64.deb 37 | 3. Download the latest version https://github.com/tonquer/Ehentai-windows/releases 38 | 4. Unzip tar -zxvf bika.tar.gz 39 | 5. cd bika && chmod +x start 40 | 6. ./start 41 | 7. If you want to use waifu2x, please make sure your device supports vulkan, and then install the vulkan driver package, sudo apt install mesa-vulkan-drivers 42 | 43 | ## Hot to build 44 | ### Use Git Action 45 | 1. Look result [Git Action](https://github.com/tonquer/ehentai-windows/actions) 46 | 47 | ## Interface 48 | 49 | ## Thanks for the following project 50 | ### Waifu2x 51 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=nagadomi&repo=waifu2x)](https://github.com/nagadomi/waifu2x) 52 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=nihui&repo=waifu2x-ncnn-vulkan)](https://github.com/nagadomi/waifu2x-ncnn-vulkan) 53 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=tonquer&repo=waifu2x-ncnn-vulkan-python)](https://github.com/tonquer/waifu2x-ncnn-vulkan-python) 54 | ### Qt 55 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=PyQt5&repo=PyQt)](https://github.com/PyQt5/PyQt) 56 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=PyQt5&repo=PyQtClient)](https://github.com/PyQt5/PyQtClient) 57 | ### Qt skin 58 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=satchelwu&repo=QSS-Skin-Builder)](https://github.com/satchelwu/QSS-Skin-Builder) 59 | ### Qt scroll 60 | [![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=zhiyiYo&repo=Groove)](https://github.com/zhiyiYo/Groove) 61 | -------------------------------------------------------------------------------- /database/build.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | # fw = open("resources.py", "w+") 5 | # fw.write("import base64\n\n\n") 6 | # fw.write("class DataMgr(object):\n") 7 | 8 | files = {} 9 | for root, dirs, filenames in os.walk("./"): 10 | for name in filenames: 11 | if name[-2:] != "md": 12 | continue 13 | if len(name.split(".")) >= 3: 14 | continue 15 | f = open(name, "r", encoding='utf-8') 16 | # fw.write("def get():\r\n return ) 17 | i = 0 18 | info = {} 19 | info['data'] = {} 20 | while True: 21 | data = f.readline() 22 | if not data: 23 | break 24 | data = data.replace("\\|", "、") 25 | data2 = data.split("|") 26 | if len(data2) <= 4: 27 | data2 = data.split(":") 28 | if len(data2) < 2: 29 | continue 30 | elif data2[0] == "name": 31 | name = data2[1].strip("\n").strip(" ") 32 | info['name'] = name 33 | elif data2[0] == "key": 34 | key = data2[1].strip("\n").strip(" ") 35 | info['key'] = key 36 | elif data2[0] == "description": 37 | description = data2[1].strip("\n").strip(" ") 38 | info['description'] = description 39 | continue 40 | else: 41 | # 原始标签 42 | srcLabel = data2[1].strip(" ") 43 | if srcLabel == "原始标签" or srcLabel == "" or srcLabel == "--------": 44 | continue 45 | # 名称 46 | destLabel = data2[2].strip(" ") 47 | if "!" in destLabel: 48 | destLabelList = destLabel.split(")") 49 | if len(destLabelList) >= 2: 50 | destLabel = destLabelList[1] 51 | 52 | # 描述 53 | misc = data2[3].strip(" ") 54 | # link 55 | link = data2[4].strip(" ") 56 | info["data"][srcLabel] = { 57 | "src": srcLabel, 58 | "dest": destLabel, 59 | "description": misc, 60 | "link": link 61 | } 62 | pass 63 | 64 | files[info.get("key")] = info 65 | # data = base64.b64encode(f.read()) 66 | # if i % 2 == 0: 67 | # fw.write("\\x") 68 | # fw.write(" {}".format(name[:-4])) 69 | # files.append(name[:-4]) 70 | # fw.write(" = \"") 71 | # fw.write(data.decode("utf-8")) 72 | # fw.write("\"\n\n") 73 | 74 | # f.close() 75 | f = open("translate.json", "w") 76 | f.write(json.dumps(files)) 77 | f.close() 78 | # fw.write(" files = {}\n\n".format(files)) 79 | # fw.write(" @classmethod\n") 80 | # fw.write(" def GetData(cls, name):\n") 81 | # fw.write(" data = getattr(cls, name)\n") 82 | # fw.write(" return base64.b64decode(data.encode('utf-8'))\n") 83 | # fw.close() 84 | -------------------------------------------------------------------------------- /database/mixed.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 混合 3 | description: 两性/中性的恋物标签。 4 | key: mixed 5 | abbr: x 6 | copyright: > 7 | 除有特殊说明外,本文的简介文本翻译自 EHWiki,遵循原始许可协议(即 GNU 自由文档许可证)进行二次分发。 8 | 9 | 10 | Copyright (c) 2022 EhTagTranslation. Permission is granted to copy, distribute 11 | and/or modify this document under the terms of the GNU Free Documentation 12 | License, Version 1.2 or any later version published by the Free Software 13 | Foundation; with no Invariant Sections, no Front-Cover Texts, and no 14 | Back-Cover Texts. A copy of the license is included in the section entitled 15 | "GNU Free Documentation License". 16 | 17 | 18 | 对于标有(*)的条目,其简介文本复制/翻译自维基百科,遵循原始许可协议(即知识共享(Creative Commons) 署名-相同方式共享 19 | 3.0协议)进行二次分发。 20 | 21 | 22 | 对于标有(**)的条目,其简介文本复制/翻译自萌娘百科,遵循原始许可协议(即知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 23 | 3.0 协议)进行二次分发。 24 | 25 | 26 | 本文的其他内容,遵循知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 协议提供。 27 | rules: 28 | - >- 29 | 为方便查找理解同类性癖的标签,请尽量按[恋物标签分类](https://ehwiki.org/wiki/Fetish_Listing)归类存放,将类别写成注释。 30 | - 若有属于多个分类的,放入最相关的那一个分类。 31 | example: 32 | raw: group 33 | name: 乱交 34 | intro: > 35 | 两个以上的人同时进行性行为。是它们的前置标签:`harem`、`layer cake`、`mtf threesome`、`mmt 36 | threesome`、`mmf threesome`、`ttm threesome`、`ttf threesome`、`ffm 37 | threesome`和`fft threesome`等 3P 标签。 38 | links: '' 39 | --- 40 | 41 | | 原始标签 | 名称 | 描述 | 外部链接 | 42 | | -------- | ---- | ---- | -------- | 43 | | | == 身体 == | | | 44 | | | === 身体 > 生物 === | | | 45 | | | ==== 身体 > 生物 > 动物 ==== | | | 46 | | animal on animal | 兽兽 | 动物间性交。 | | 47 | | | == 头部 == | | | 48 | | | === 头部 > 思维 === | | | 49 | | body swap | 换身 | 灵魂互换,该标签不应与`possession`混淆也不能标为`gender change`。 | | 50 | | | === 头部 > 嘴 === | | | 51 | | multimouth blowjob | 多口口交 | 涉及一个阴茎和两个或多个嘴的性行为。需要`blowjob`标签。不应与`double blowjob`混淆。 | | 52 | | | == 手臂 == | | | 53 | | | === 手臂 > 手 === | | | 54 | | multiple handjob | 多重打手枪 | 多人为另一人人手淫。需要`handjob`标签。 | | 55 | | | == 下半身 == | | | 56 | | | === 下半身 > 阴部 === | | | 57 | | | ==== 下半身 > 阴部 > 阴茎 ==== | | | 58 | | frottage | 阴茎摩擦🤺 | 两根及以上的阴茎相互摩擦。
![例](# "https://ehgt.org/72/a9/72a9f8c2fc1346ec17bfbffc40cc23373e4f1e11-1193994-1412-1000-jpg_l.jpg") | | 59 | | | === 下半身 > 臀部 === | | | 60 | | multiple assjob | 多重尻交 | 多个阴茎参与的尻交。需要`assjob`标签。 | | 61 | | | == 足 == | | | 62 | | multiple footjob | 多重足交 | 一个以上的人同时用脚对同一个参与者进行性刺激。需要`footjob`标签。 | | 63 | | | == 服装 == | | | 64 | | nudism | 裸体主义 | 两个或多个角色的生殖器完全暴露在彼此面前,同时没有进行性活动。不要与`exhibitionism`相混淆。 | | 65 | | | == 多人活动 == | | | 66 | | ffm threesome | 女男女3P | ♀♂♀,2 女 1 男 3P。需要`group`标签。 | | 67 | | group | 乱交 | 两个以上的人同时进行性行为。是它们的前置标签:`harem`、`layer cake`、`mtf threesome`、`mmt threesome`、`mmf threesome`、`ttm threesome`、`ttf threesome`、`ffm threesome`和`fft threesome`等 3P 标签。其中 t 为变性人(transsexual),包含`futanari`和`shemale`,这里为了书写方便用“扶”代指。两个或两个以上的伴侣有单独性行为或一群女孩站在周围不算。
![图](# "https://ehgt.org/eb/8a/eb8a92af97e65f81c552ca217fea67262afa0e24-2159382-1381-2000-jpg_l.jpg") | | 68 | | mmf threesome | 男女男3P | ♂♀♂,2 男 1 女 3P。需要`group`标签。不算`yaoi`。 | | 69 | | mmt threesome | 男扶男3P | ♂⚥♂,2 男 1 扶 3P。需要`group`标签。不算`yaoi`。 | | 70 | | mtf threesome | 男扶女3P | ♂⚥♀,1 男 1 扶 1 女 3P。需要`group`标签。不算`yaoi`。 | | 71 | | oyakodon | 亲子丼 | 母亲(父亲)和儿子(女儿)与同一个性伴侣产生性行为。需要`group`标签。
![](# "https://ehgt.org/0c/70/0c702600eb54c7f3291dfe4e154c4dc42d38daec-98260-1050-1492-jpg_l.jpg") ![](# "https://ehgt.org/0b/06/0b0678d8efd49ec032e5ea20283624f364d5b689-1135571-1200-900-png_l.jpg") ![](# "https://ehgt.org/df/5a/df5a79d0968345f1dd480e5eb4203fb26adc9bfa-4683866-2133-3000-jpg_l.jpg") | | 72 | | shimaidon | 手足丼 | 具有血缘关系的兄弟姐妹两人(或以上)同时与同一个性伴侣产生性行为,需要`group`标签。 | | 73 | | ttm threesome | 扶扶男3P | ⚥♂⚥,2 扶 1 男 3P。需要`group`标签。不算`yaoi`。 | | 74 | | twins | 双胞胎 | 双胞胎与第三者或双胞胎间性交。可能是`group`。 | | 75 | | | == 上下文 == | | | 76 | | | === 上下文 > 亲属 === | | | 77 | | incest | 乱伦 | 亲属之间的性行为,包括无血缘关系的人。 | | 78 | | inseki | 姻亲 | 无血缘关系的亲属之间的性行为。需要`incest`标签。 | | 79 | 80 | 84 | -------------------------------------------------------------------------------- /database/reclass.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 重新分类 3 | description: 用于分类出错的图库,当某个重新分类标签权重达到 100,将移动图库至对应分类。 4 | key: reclass 5 | abbr: r 6 | copyright: > 7 | 除有特殊说明外,本文的简介文本翻译自 EHWiki,遵循原始许可协议(即 GNU 自由文档许可证)进行二次分发。 8 | 9 | 10 | Copyright (c) 2022 EhTagTranslation. Permission is granted to copy, distribute 11 | and/or modify this document under the terms of the GNU Free Documentation 12 | License, Version 1.2 or any later version published by the Free Software 13 | Foundation; with no Invariant Sections, no Front-Cover Texts, and no 14 | Back-Cover Texts. A copy of the license is included in the section entitled 15 | "GNU Free Documentation License". 16 | 17 | 18 | 本文的其他内容,遵循知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 协议提供。 19 | rules: 20 | - 参考[图库分类](https://ehwiki.org/wiki/Gallery_Categories)撰写翻译。 21 | --- 22 | 23 | | 原始标签 | 名称 | 描述 | 外部链接 | 24 | | -------- | ---- | ---- | -------- | 25 | | doujinshi | 同人志 | 任何**自费出版**或自行发布(例如通过 pixiv)的 H 漫画或色情`comic`,通常包含格子和文本。经常涉及到模仿现有的动画、漫画、游戏和其他可识别的角色或吉祥物(例如 `vocaloid`)。 | | 26 | | manga | 漫画 | 由第三方出版色情漫画。通常只包含原创内容。大多数的`tankoubon`和`anthology`属于此分类。分类名称是 H 漫画(Hentai Manga)的简写。 | | 27 | | artistcg | 画师CG | 电脑作画的画师作品集,往往是单独的全彩图像,很少有手工绘制的,并有更复杂的背景。不适用于从一个艺术家各个作品中抽出的零散作品,图像必须是一个特定的被出售或分发的集合,尤其是为了叙述一系列事件。 | | 28 | | gamecg | 游戏CG | 计算机生成的作品,往往从色情游戏(エロゲー)提取。一般有大量图像。不适用于游戏`screenshots`。 | | 29 | | non-h | 无H | 非色情性质的绘制内容。单张具有性内容的图像将使整个图库失去资格。允许少量非色情性质的裸体(例如露点)。西方的这类是`misc`类别。 | | 30 | | imageset | 图集 | 图集是一个通用的图库。他们大多是日本人绘制的内容,但不属于漫画或同人志。这些图库内的图像通常具有单个主题(例如特定艺术家,特定系列的人物,或特定恋物癖)。 | | 31 | | western | 西方 | 所有西方色情艺术,包括图像、`western cg`和`comic`。 | | 32 | | cosplay | Cosplay | 真人装扮成动画、漫画或游戏中的角色,用于一般表演(非裸体也是可以接受的)或专门为性目的拍摄。打扮为一般普通护士或女佣等,而不是特定角色的**不**算。 | | 33 | | misc | 杂项 | 不符合任何其他类别的图库。包含`3d`图库;非色情的`western`图库;实物(如`figure`、`paperchild`、`real doll`等)图库;`game manual`、指南、攻略和游戏杂志图库;或者包含大量混合内容以致不符合其他任何分类的要求。从这些图库获得的 GP 数量减少到上传者通常会收到的 25%。 | | 34 | | asianporn | 亚洲色情 | 将被淘汰的老分类,2012-02-15 开始禁止使用,会逐渐删除。 | | 35 | | private | 私有 | 私有图库是一个非正式的类别,允许用户不与 E-Hentai 社区其他成员分享他们的内容。从私有图库获得的 GP 数量减少到上传者通常会收到的金额的 10%。要创建私有图库,用户必须拥有铜星或更高星级。 | | 36 | -------------------------------------------------------------------------------- /database/rows.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 内容索引 3 | description: 标签列表的行名,即标签的命名空间。 4 | key: rows 5 | rules: 6 | - 参考[命名空间](https://ehwiki.org/wiki/Namespace)撰写翻译。 7 | --- 8 | 9 | | 原始标签 | 名称 | 描述 | 外部链接 | 10 | | -------- | ---- | ---- | -------- | 11 | | female | 女性 | 女性角色相关的恋物标签。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/female.md) | 12 | | male | 男性 | 男性角色相关的恋物标签。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/male.md) | 13 | | mixed | 混合 | 两性/中性的恋物标签。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/mixed.md) | 14 | | language | 语言 | 作品的语言。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/language.md) | 15 | | other | 其他 | 经过确认的技术标签。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/other.md) | 16 | | group | 团队 | 制作社团或公司。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/group.md) | 17 | | artist | 艺术家 | 绘画作者/写手。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/artist.md) | 18 | | cosplayer | Coser | 角色扮演者。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/cosplayer.md) | 19 | | parody | 原作 | 同人作品模仿的原始作品。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/parody.md) | 20 | | character | 角色 | 作品中出现的角色。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/character.md) | 21 | | reclass | 重新分类 | 用于分类出错的图库,当某个重新分类标签权重达到 100,将移动图库至对应分类。 | [数据库页面](https://github.com/EhTagTranslation/Database/blob/master/database/reclass.md) | 22 | | temp | 临时 | 尚未正式添加至 E-Hentai 标签系统的标签。在提供翻译前,需要在 [E-Hentai 论坛](https://forums.e-hentai.org/index.php?showtopic=246656)发帖将该标签移动到合适的命名空间。 | | 23 | -------------------------------------------------------------------------------- /res/appimage/ehentai.AppRun/.DirIcon: -------------------------------------------------------------------------------- 1 | link EHentai.png -------------------------------------------------------------------------------- /res/appimage/ehentai.AppRun/AppRun: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SELF=$(readlink -f "$0") 3 | HERE=${SELF%/*} 4 | export PATH="${HERE}/usr/bin/:${HERE}/usr/sbin/:${HERE}/usr/games/:${HERE}/bin/:${HERE}/sbin/${PATH:+:$PATH}" 5 | export LD_LIBRARY_PATH="${HERE}/usr/lib/:${HERE}/usr/lib/i386-linux-gnu/:${HERE}/usr/lib/x86_64-linux-gnu/:${HERE}/usr/lib32/:${HERE}/usr/lib64/:${HERE}/lib/:${HERE}/lib/i386-linux-gnu/:${HERE}/lib/x86_64-linux-gnu/:${HERE}/lib32/:${HERE}/lib64/${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" 6 | export PYTHONPATH="${HERE}/usr/share/pyshared/${PYTHONPATH:+:$PYTHONPATH}" 7 | export XDG_DATA_DIRS="${HERE}/usr/share/${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}" 8 | export PERLLIB="${HERE}/usr/share/perl5/:${HERE}/usr/lib/perl5/${PERLLIB:+:$PERLLIB}" 9 | export GSETTINGS_SCHEMA_DIR="${HERE}/usr/share/glib-2.0/schemas/${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}" 10 | export QT_PLUGIN_PATH="${HERE}/usr/lib/qt4/plugins/:${HERE}/usr/lib/i386-linux-gnu/qt4/plugins/:${HERE}/usr/lib/x86_64-linux-gnu/qt4/plugins/:${HERE}/usr/lib32/qt4/plugins/:${HERE}/usr/lib64/qt4/plugins/:${HERE}/usr/lib/qt5/plugins/:${HERE}/usr/lib/i386-linux-gnu/qt5/plugins/:${HERE}/usr/lib/x86_64-linux-gnu/qt5/plugins/:${HERE}/usr/lib32/qt5/plugins/:${HERE}/usr/lib64/qt5/plugins/${QT_PLUGIN_PATH:+:$QT_PLUGIN_PATH}" 11 | EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1) 12 | echo $EXEC 13 | exec "${EXEC}" "$@" --no-sandbox 14 | -------------------------------------------------------------------------------- /res/appimage/ehentai.AppRun/EHentai.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=EHentai 4 | Exec=EHentai 5 | Comment=EHentai 6 | Icon=EHentai 7 | Categories=Graphics; 8 | -------------------------------------------------------------------------------- /res/appimage/ehentai.AppRun/EHentai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/appimage/ehentai.AppRun/EHentai.png -------------------------------------------------------------------------------- /res/icon/Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/Icon.icns -------------------------------------------------------------------------------- /res/icon/bubble_r_9.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/bubble_r_9.9.png -------------------------------------------------------------------------------- /res/icon/clear_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/clear_off.png -------------------------------------------------------------------------------- /res/icon/clear_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/clear_on.png -------------------------------------------------------------------------------- /res/icon/fold1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/fold1.png -------------------------------------------------------------------------------- /res/icon/fold2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/fold2.png -------------------------------------------------------------------------------- /res/icon/ic_get_app_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/ic_get_app_black_36dp.png -------------------------------------------------------------------------------- /res/icon/ic_settings_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/ic_settings_black_36dp.png -------------------------------------------------------------------------------- /res/icon/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/icon.ico -------------------------------------------------------------------------------- /res/icon/icon_comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/icon_comment.png -------------------------------------------------------------------------------- /res/icon/icon_like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/icon_like.png -------------------------------------------------------------------------------- /res/icon/icon_like_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/icon_like_off.png -------------------------------------------------------------------------------- /res/icon/loading/loading_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_1.png -------------------------------------------------------------------------------- /res/icon/loading/loading_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_10.png -------------------------------------------------------------------------------- /res/icon/loading/loading_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_2.png -------------------------------------------------------------------------------- /res/icon/loading/loading_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_3.png -------------------------------------------------------------------------------- /res/icon/loading/loading_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_4.png -------------------------------------------------------------------------------- /res/icon/loading/loading_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_5.png -------------------------------------------------------------------------------- /res/icon/loading/loading_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_6.png -------------------------------------------------------------------------------- /res/icon/loading/loading_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_7.png -------------------------------------------------------------------------------- /res/icon/loading/loading_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_8.png -------------------------------------------------------------------------------- /res/icon/loading/loading_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading/loading_9.png -------------------------------------------------------------------------------- /res/icon/loading_gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading_gif.gif -------------------------------------------------------------------------------- /res/icon/loading_gif.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/loading_gif.webp -------------------------------------------------------------------------------- /res/icon/logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/icon/logo_round.png -------------------------------------------------------------------------------- /res/icon/new.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/theme/dark_pink/disabled/uparrow2.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 60 | 72 | 73 | 75 | 76 | 78 | image/svg+xml 79 | 81 | 82 | 83 | 84 | 85 | 90 | 95 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /res/theme/dark_pink/primary/downarrow2.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 60 | 72 | 73 | 75 | 76 | 78 | image/svg+xml 79 | 81 | 82 | 83 | 84 | 85 | 90 | 95 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /res/theme/dark_pink/primary/uparrow2.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 60 | 72 | 73 | 75 | 76 | 78 | image/svg+xml 79 | 81 | 82 | 83 | 84 | 85 | 90 | 95 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /res/theme/light_pink/disabled/uparrow2.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 60 | 72 | 73 | 75 | 76 | 78 | image/svg+xml 79 | 81 | 82 | 83 | 84 | 85 | 90 | 95 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /res/theme/light_pink/primary/uparrow2.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 60 | 72 | 73 | 75 | 76 | 78 | image/svg+xml 79 | 81 | 82 | 83 | 84 | 85 | 90 | 95 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowdown_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowdown_down.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowdown_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowdown_highlight.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowdown_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowdown_normal.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowleft_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowleft_down.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowleft_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowleft_highlight.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowleft_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowleft_normal.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowright_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowright_down.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowright_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowright_highlight.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowright_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowright_normal.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowup_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowup_down.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowup_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowup_highlight.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_arrowup_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_arrowup_normal.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_bar_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_bar_highlight.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_bar_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_bar_normal.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_bkg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_bkg.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_horzbar_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_horzbar_down.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_horzbar_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_horzbar_highlight.png -------------------------------------------------------------------------------- /res/theme/scrollbar/scrollbar_horzbar_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/theme/scrollbar/scrollbar_horzbar_normal.png -------------------------------------------------------------------------------- /res/title_bar/black_close_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_close_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/black_down_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_down_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/black_max_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_max_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/black_max_button_pressed_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_max_button_pressed_57_40.png -------------------------------------------------------------------------------- /res/title_bar/black_min_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_min_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/black_min_button_pressed_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_min_button_pressed_57_40.png -------------------------------------------------------------------------------- /res/title_bar/black_return_button_60_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_return_button_60_40.png -------------------------------------------------------------------------------- /res/title_bar/black_return_button_hover_60_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_return_button_hover_60_40.png -------------------------------------------------------------------------------- /res/title_bar/black_return_button_pressed_60_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/black_return_button_pressed_60_40.png -------------------------------------------------------------------------------- /res/title_bar/close_button_hover_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/close_button_hover_57_40.png -------------------------------------------------------------------------------- /res/title_bar/close_button_pressed_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/close_button_pressed_57_40.png -------------------------------------------------------------------------------- /res/title_bar/down_button_pressed_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/down_button_pressed_57_40.png -------------------------------------------------------------------------------- /res/title_bar/green_down_button_hover_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/green_down_button_hover_57_40.png -------------------------------------------------------------------------------- /res/title_bar/green_max_button_hover_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/green_max_button_hover_57_40.png -------------------------------------------------------------------------------- /res/title_bar/green_min_button_hover_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/green_min_button_hover_57_40.png -------------------------------------------------------------------------------- /res/title_bar/open_black_close_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/open_black_close_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/open_black_max_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/open_black_max_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/open_black_min_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/open_black_min_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/open_white_close_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/open_white_close_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/white_close_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_close_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/white_down_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_down_57_40.png -------------------------------------------------------------------------------- /res/title_bar/white_down_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_down_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/white_max_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_max_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/white_min_button_57_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_min_button_57_40.png -------------------------------------------------------------------------------- /res/title_bar/white_return_button_60_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_return_button_60_40.png -------------------------------------------------------------------------------- /res/title_bar/white_return_button_hover_60_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_return_button_hover_60_40.png -------------------------------------------------------------------------------- /res/title_bar/white_return_button_pressed_60_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/title_bar/white_return_button_pressed_60_40.png -------------------------------------------------------------------------------- /res/tr/str_en.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/tr/str_en.qm -------------------------------------------------------------------------------- /res/tr/str_hk.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/tr/str_hk.qm -------------------------------------------------------------------------------- /res/tr/tr_en.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/tr/tr_en.qm -------------------------------------------------------------------------------- /res/tr/tr_hk.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/tr/tr_hk.qm -------------------------------------------------------------------------------- /res/tr/ui_en.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/tr/ui_en.qm -------------------------------------------------------------------------------- /res/tr/ui_hk.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/res/tr/ui_hk.qm -------------------------------------------------------------------------------- /script/build_qrc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | sts = os.system("pyside6-rcc.exe -o ../src/images_rc.py ../res/images.qrc") -------------------------------------------------------------------------------- /script/build_translate.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tools.langconv import Converter 3 | os.system("chcp 65001") 4 | sts = os.system("pyside6-lupdate.exe -no-obsolete -source-language zh_CN -target-language zh_HK ../src/tools/str.py -ts ../translate/str_hk.ts") 5 | sts = os.system("pyside6-lupdate.exe -no-obsolete -source-language zh_CN -target-language en_US ../src/tools/str.py -ts ../translate/str_en.ts") 6 | 7 | sts = os.system("pyside6-lupdate.exe -no-obsolete -source-language zh_CN -target-language zh_HK ../ui -ts ../translate/ui_hk.ts") 8 | sts = os.system("pyside6-lupdate.exe -no-obsolete -source-language zh_CN -target-language en_US ../ui -ts ../translate/ui_en.ts") 9 | # 10 | for tsFile in ["../translate/str_hk.ts", "../translate/ui_hk.ts"]: 11 | f = open(tsFile, "r", encoding="utf-8") 12 | data = "" 13 | nextName = "" 14 | for srcLine in f.readlines(): 15 | line = srcLine.strip() 16 | if "" in line: 17 | name = line.replace("", "").replace("", "") 18 | nextName = Converter('zh-hant').convert(name) 19 | if "" in line: 20 | srcLine = srcLine.replace("", "{}".format(nextName)) 21 | elif "" in line: 22 | oldName = line.replace("", "").replace("", "").replace("\n", "") 23 | srcLine = srcLine.replace(oldName, nextName) 24 | elif "" in line: 25 | oldName = line.replace("", "").replace("", "").replace("\n", "") 26 | srcLine = srcLine.replace(" type=\"vanished\"", "").replace(oldName, nextName) 27 | data += srcLine 28 | 29 | f.close() 30 | f = open(tsFile, "w+", encoding="utf-8") 31 | f.write(data) 32 | f.close() 33 | 34 | sts = os.system("pyside6-lrelease.exe ../translate/str_en.ts ../translate/ui_en.ts -qm ../res/tr/tr_en.qm") 35 | sts = os.system("pyside6-lrelease.exe ../translate/str_hk.ts ../translate/ui_hk.ts -qm ../res/tr/tr_hk.qm") 36 | -------------------------------------------------------------------------------- /script/build_ui.py: -------------------------------------------------------------------------------- 1 | import os 2 | # import subprocess 3 | 4 | for root, dirs, filenames in os.walk("../ui/"): 5 | for name in filenames: 6 | if name[-2:] != "ui": 7 | continue 8 | outName = name[:-3] 9 | sts = os.system("pyside6-uic.exe {}.ui -o {}.py".format(os.path.join(root, outName), os.path.join( 10 | "../src/interface/", outName))) 11 | # sts = os.system("..\env\Scripts\PySide2-uic.exe {}.interface -o {}.py".format(outName, outName)) 12 | # proc = subprocess.Popen(["PySide2-uic.exe {}.interface -o {}.py".format(outName, outName)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 13 | # while True: 14 | # buff = proc.stdout.readline() 15 | # buff = buff.decode("gbk", "ignore") 16 | # if buff == '' and proc.poll() != None : 17 | # break 18 | # elif buff != '': 19 | # print(buff) 20 | pass 21 | 22 | print('Finished!') 23 | -------------------------------------------------------------------------------- /src/component/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/__init__.py -------------------------------------------------------------------------------- /src/component/box/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/box/__init__.py -------------------------------------------------------------------------------- /src/component/box/wheel_combo_box.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QComboBox 2 | 3 | 4 | class WheelComboBox(QComboBox): 5 | def __init__(self, parent=None): 6 | QComboBox.__init__(self, parent) 7 | 8 | def wheelEvent(self, event): 9 | event.ignore() 10 | 11 | -------------------------------------------------------------------------------- /src/component/box/wheel_double_spin_box.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QDoubleSpinBox 2 | 3 | 4 | class WheelDoubleSpinBox(QDoubleSpinBox): 5 | def __init__(self, parent=None): 6 | QDoubleSpinBox.__init__(self, parent) 7 | 8 | def wheelEvent(self, event): 9 | event.ignore() 10 | 11 | -------------------------------------------------------------------------------- /src/component/box/wheel_slider.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QSlider 2 | 3 | 4 | class WheelSlider(QSlider): 5 | def __init__(self, parent=None): 6 | QSlider.__init__(self, parent) 7 | 8 | def wheelEvent(self, event): 9 | event.ignore() 10 | -------------------------------------------------------------------------------- /src/component/box/wheel_spin_box.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QSpinBox 2 | 3 | 4 | class WheelSpinBox(QSpinBox): 5 | def __init__(self, parent=None): 6 | QSpinBox.__init__(self, parent) 7 | 8 | def wheelEvent(self, event): 9 | event.ignore() 10 | 11 | -------------------------------------------------------------------------------- /src/component/button/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/button/__init__.py -------------------------------------------------------------------------------- /src/component/button/icon_tool_button.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QToolButton 2 | 3 | 4 | class IconToolButton(QToolButton): 5 | def __init__(self, parent=None): 6 | QToolButton.__init__(self, parent) -------------------------------------------------------------------------------- /src/component/button/switch_button.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import QRect, Signal, Qt 2 | from PySide6.QtGui import QPen, QColor, QBrush, QPainter, QFont 3 | from PySide6.QtWidgets import QWidget 4 | 5 | 6 | class SwitchButton(QWidget): 7 | """自定义Switch按钮""" 8 | Switch = Signal(bool) 9 | 10 | def __init__(self, parent=None, state=True): 11 | super(SwitchButton, self).__init__(parent) 12 | 13 | # 设置无边框和背景透明 14 | self.setWindowFlag(Qt.WindowType.FramelessWindowHint, True) 15 | self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) 16 | 17 | self.resize(70, 30) 18 | self.setFixedSize(70, 30) 19 | self.state = state # 按钮状态:True表示开,False表示关 20 | 21 | def mousePressEvent(self, event): 22 | """鼠标点击事件:用于切换按钮状态""" 23 | super(SwitchButton, self).mousePressEvent(event) 24 | 25 | state = False if self.state else True 26 | self.SetState(state) 27 | self.Switch.emit(self.state) 28 | 29 | def SetState(self, state): 30 | self.state = state 31 | self.update() 32 | 33 | def paintEvent(self, event): 34 | """绘制按钮""" 35 | super(SwitchButton, self).paintEvent(event) 36 | 37 | # 创建绘制器并设置抗锯齿和图片流畅转换 38 | painter = QPainter(self) 39 | painter.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform) 40 | 41 | # 定义字体样式 42 | font = QFont('Microsoft YaHei') 43 | font.setPixelSize(14) 44 | painter.setFont(font) 45 | 46 | # 开关为开的状态 47 | if self.state: 48 | # 绘制背景 49 | painter.setPen(Qt.NoPen) 50 | brush = QBrush(QColor('#FF475D')) 51 | painter.setBrush(brush) 52 | painter.drawRoundedRect(0, 0, self.width(), self.height(), self.height() // 2, self.height() // 2) 53 | 54 | # 绘制圆圈 55 | painter.setPen(Qt.PenStyle.NoPen) 56 | brush.setColor(QColor('#ffffff')) 57 | painter.setBrush(brush) 58 | painter.drawRoundedRect(43, 3, 24, 24, 12, 12) 59 | 60 | # 绘制文本 61 | painter.setPen(QPen(QColor('#ffffff'))) 62 | painter.setBrush(Qt.BrushStyle.NoBrush) 63 | painter.drawText(QRect(18, 4, 50, 20), Qt.AlignmentFlag.AlignLeft, '开') 64 | # 开关为关的状态 65 | else: 66 | # 绘制背景 67 | painter.setPen(Qt.PenStyle.NoPen) 68 | brush = QBrush(QColor('#FFFFFF')) 69 | painter.setBrush(brush) 70 | painter.drawRoundedRect(0, 0, self.width(), self.height(), self.height()//2, self.height()//2) 71 | 72 | # 绘制圆圈 73 | pen = QPen(QColor('#999999')) 74 | pen.setWidth(1) 75 | painter.setPen(pen) 76 | painter.drawRoundedRect(3, 3, 24, 24, 12, 12) 77 | 78 | # 绘制文本 79 | painter.setBrush(Qt.BrushStyle.NoBrush) 80 | painter.drawText(QRect(38, 4, 50, 20), Qt.AlignmentFlag.AlignLeft, '关') -------------------------------------------------------------------------------- /src/component/dialog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/dialog/__init__.py -------------------------------------------------------------------------------- /src/component/dialog/base_mask_dialog.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from PySide6.QtCore import QEasingCurve, QPropertyAnimation, Qt, Signal 3 | from PySide6.QtWidgets import (QDialog, QGraphicsDropShadowEffect, 4 | QGraphicsOpacityEffect, QWidget, QSpacerItem, QSizePolicy, QVBoxLayout) 5 | 6 | 7 | class BaseMaskDialog(QDialog): 8 | """ 带遮罩的对话框抽象基类 """ 9 | closed = Signal() 10 | 11 | def __init__(self, parent): 12 | QDialog.__init__(self, parent=parent) 13 | self.vBoxLayout = QVBoxLayout(self) 14 | self.windowMask = QWidget(self) 15 | # 蒙版中间的对话框,所有小部件以他为父级窗口 16 | self.widget = QWidget(self, objectName='centerWidget') 17 | self.setWindowFlags(Qt.WindowType.FramelessWindowHint| Qt.WindowType.Window) 18 | self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) 19 | self.setGeometry(self.parent().geometry()) 20 | self.windowMask.resize(self.size()) 21 | 22 | self.windowMask.setStyleSheet('background:rgba(255, 255, 255, 0.6)') 23 | self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) 24 | self.vBoxLayout.addItem(self.verticalSpacer) 25 | self.vBoxLayout.addWidget(self.widget, 0, Qt.AlignmentFlag.AlignHCenter) 26 | self.verticalSpacer2 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) 27 | self.vBoxLayout.addItem(self.verticalSpacer2) 28 | 29 | self.__setShadowEffect() 30 | 31 | def reject(self): 32 | self.close() 33 | 34 | def closeEvent(self, arg__1) -> None: 35 | self.closed.emit() 36 | arg__1.accept() 37 | 38 | def __setShadowEffect(self): 39 | """ 添加阴影 """ 40 | shadowEffect = QGraphicsDropShadowEffect(self.widget) 41 | shadowEffect.setBlurRadius(50) 42 | shadowEffect.setOffset(0, 5) 43 | self.widget.setGraphicsEffect(shadowEffect) 44 | 45 | def showEvent(self, e): 46 | """ 淡入 """ 47 | opacityEffect = QGraphicsOpacityEffect(self) 48 | self.setGraphicsEffect(opacityEffect) 49 | opacityAni = QPropertyAnimation(opacityEffect, b'opacity', self) 50 | opacityAni.setStartValue(0) 51 | opacityAni.setEndValue(1) 52 | opacityAni.setDuration(200) 53 | opacityAni.setEasingCurve(QEasingCurve.InSine) 54 | opacityAni.finished.connect(opacityEffect.deleteLater) 55 | opacityAni.start() 56 | super().showEvent(e) 57 | 58 | # def closeEvent(self, e): 59 | # """ 淡出 """ 60 | # self.widget.setGraphicsEffect(None) 61 | # opacityEffect = QGraphicsOpacityEffect(self) 62 | # self.setGraphicsEffect(opacityEffect) 63 | # opacityAni = QPropertyAnimation(opacityEffect, b'opacity', self) 64 | # opacityAni.setStartValue(1) 65 | # opacityAni.setEndValue(0) 66 | # opacityAni.setDuration(100) 67 | # opacityAni.setEasingCurve(QEasingCurve.OutCubic) 68 | # opacityAni.finished.connect(self.deleteLater) 69 | # opacityAni.start() 70 | # e.ignore() 71 | -------------------------------------------------------------------------------- /src/component/dialog/loading_dialog.py: -------------------------------------------------------------------------------- 1 | from PySide6 import QtWidgets, QtCore 2 | from PySide6.QtCore import QTimer 3 | from PySide6.QtWidgets import QGridLayout 4 | 5 | from component.label.gif_group_label import GifGroupLabel 6 | from component.label.gif_label import GifLabel 7 | 8 | 9 | class LoadingDialog(QtWidgets.QDialog): 10 | def __init__(self, owner): 11 | super(self.__class__, self).__init__(owner) 12 | self.gridLayout = QGridLayout(self) 13 | self.setWindowFlag(QtCore.Qt.WindowType.FramelessWindowHint) 14 | self.setWindowFlag(QtCore.Qt.WindowType.Dialog) 15 | self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground) 16 | self.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) 17 | self.label = GifGroupLabel(self) 18 | self.gridLayout.setContentsMargins(0, 0, 0, 0) 19 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1) 20 | 21 | self.timer = QTimer(self.label) 22 | self.timer.setInterval(100) 23 | self.label.Init() 24 | self.label.resize(250, 250) 25 | self.cnt = 0 26 | self.closeCnt = 50 27 | self.timer.timeout.connect(self.UpdatePic) 28 | self.resize(250, 250) 29 | 30 | def show(self) -> None: 31 | self.timer.start() 32 | self.cnt = 0 33 | super(self.__class__, self).show() 34 | 35 | def close(self): 36 | self.timer.stop() 37 | super(self.__class__, self).close() 38 | 39 | def UpdatePic(self): 40 | self.cnt += 1 41 | self.label.ShowNextPixMap() 42 | if self.cnt >= self.closeCnt: 43 | self.close() 44 | pass 45 | -------------------------------------------------------------------------------- /src/component/label/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/label/__init__.py -------------------------------------------------------------------------------- /src/component/label/gif_group_label.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import QByteArray, QBuffer, QTimer, Qt 2 | from PySide6.QtGui import QMovie, QPixmap, QImage 3 | from PySide6.QtWidgets import QLabel 4 | 5 | 6 | class GifGroupLabel(QLabel): 7 | def __init__(self, parent): 8 | QLabel.__init__(self, parent) 9 | self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) 10 | self.setWindowFlags(self.windowFlags() | Qt.WindowType.FramelessWindowHint) 11 | self.images = [] 12 | self.index = 0 13 | 14 | def Init(self): 15 | for i in range(10): 16 | image = QImage(":/png/icon/loading/loading_{}.png".format(i+1)) 17 | self.images.append(image) 18 | 19 | def ShowNextPixMap(self): 20 | if not self.images: 21 | return 22 | if self.index >= len(self.images): 23 | self.index = 0 24 | image = self.images[self.index] 25 | p = QPixmap(image) 26 | radio = self.devicePixelRatioF() 27 | p.setDevicePixelRatio(radio) 28 | self.setPixmap(p) 29 | self.index += 1 30 | -------------------------------------------------------------------------------- /src/component/label/gif_label.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import QByteArray, QBuffer, Qt, QTimer 2 | from PySide6.QtGui import QMovie, QPixmap 3 | from PySide6.QtWidgets import QLabel 4 | 5 | 6 | class GifLabel(QLabel): 7 | def __init__(self, parent): 8 | QLabel.__init__(self, parent) 9 | self.movie = QMovie() 10 | self.byteArray = None 11 | self.bBuffer = None 12 | self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) 13 | self.setWindowFlags(self.windowFlags() | Qt.WindowType.FramelessWindowHint) 14 | # self.movie.frameChanged.connect(self.FrameChange) 15 | self.timer = QTimer() 16 | self.timer.setInterval(250) 17 | self.timer.timeout.connect(self.DelayTimeout) 18 | 19 | def FrameChange(self): 20 | currentPixmap = self.movie.currentPixmap() 21 | size = currentPixmap.size() 22 | radioF = self.devicePixelRatioF() 23 | pixmap = QPixmap() 24 | pixmap.setDevicePixelRatio(radioF) 25 | self.setPixmap(pixmap) 26 | return 27 | 28 | def delayHide(self, delay=250): 29 | if self.timer.isActive(): 30 | return 31 | if self.isHidden(): 32 | return 33 | self.timer.setInterval(delay) 34 | self.timer.start() 35 | return 36 | 37 | def DelayTimeout(self): 38 | self.timer.stop() 39 | self.hide() 40 | 41 | def Init(self, data): 42 | self.resize(250, 250) 43 | self.byteArray = QByteArray(data) 44 | self.bBuffer = QBuffer(self.byteArray) 45 | 46 | # self.movie.setFormat(QByteArray(b"WEBP")) 47 | 48 | self.movie.setCacheMode(QMovie.CacheMode.CacheNone) 49 | self.movie.setDevice(self.bBuffer) 50 | # self.movie.setSpeed(100) 51 | self.setMovie(self.movie) 52 | 53 | self.movie.start() 54 | self.setScaledContents(True) 55 | 56 | def InitByFileName(self, name): 57 | self.resize(300, 300) 58 | self.movie = QMovie(name) 59 | # self.movie.setFileName(name) 60 | self.movie.setFormat(QByteArray(b"GIF")) 61 | self.movie.start() 62 | return 63 | -------------------------------------------------------------------------------- /src/component/label/head_label.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import Qt 2 | from PySide6.QtGui import QPainterPath, QPixmap, QPainter 3 | from PySide6.QtWidgets import QLabel 4 | 5 | 6 | class HeadLabel(QLabel): 7 | def __init__(self, *args, **kwargs): 8 | super(HeadLabel, self).__init__(*args, **kwargs) 9 | self.antialiasing = True 10 | self.radius = 100 11 | self.target = None 12 | 13 | def SetPicture(self, data, titleData=None): 14 | self.target = QPixmap(self.size()) # 大小和控件一样 15 | self.target.fill(Qt.GlobalColor.transparent) # 填充背景为透明 16 | q = QPixmap() 17 | q.loadFromData(data) 18 | radio = self.devicePixelRatioF() 19 | q.setDevicePixelRatio(radio) 20 | 21 | offset = 0 22 | if titleData: 23 | offset = 20 24 | 25 | p = q.scaled( # 加载图片并缩放和控件一样大 26 | (self.width()-offset)*radio, (self.height()-offset)*radio, Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) 27 | 28 | painter = QPainter(self.target) 29 | if self.antialiasing: 30 | painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) 31 | # painter.setRenderHint(QPainter.HighQualityAntialiasing, True) 32 | painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) 33 | 34 | path = QPainterPath() 35 | path.addRoundedRect( 36 | offset//2, offset//2, self.width()-offset, self.height()-offset, self.radius, self.radius) 37 | painter.setClipPath(path) 38 | 39 | painter.drawPixmap(offset//2, offset//2, p) 40 | self.setPixmap(self.target) 41 | 42 | if titleData: 43 | p = QPixmap() 44 | p.loadFromData(titleData) 45 | p.setDevicePixelRatio(self.devicePixelRatioF()) 46 | titleP = p.scaled( # 加载图片并缩放和控件一样大 47 | self.height(), self.width(), Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) 48 | painter = QPainter() 49 | painter.begin(titleP) 50 | painter.setCompositionMode(QPainter.CompositionMode_DestinationOver) 51 | painter.drawPixmap(0, 0, self.target) 52 | painter.end() 53 | 54 | self.target = QPixmap(self.size()) # 大小和控件一样 55 | self.target.fill(Qt.GlobalColor.transparent) # 填充背景为透明 56 | painter.begin(self.target) 57 | painter.drawPixmap(0, 0, titleP) 58 | painter.end() 59 | self.setPixmap(self.target) -------------------------------------------------------------------------------- /src/component/label/loading_label.py: -------------------------------------------------------------------------------- 1 | from PySide6 import QtCore 2 | from PySide6.QtCore import QByteArray, QBuffer, Qt, QTimer 3 | from PySide6.QtGui import QMovie, QPixmap 4 | from PySide6.QtSvgWidgets import QSvgWidget 5 | from PySide6.QtWidgets import QLabel 6 | 7 | 8 | class LoadingLabel(QSvgWidget): 9 | def __init__(self, parent): 10 | QSvgWidget.__init__(self, parent) 11 | self.load(":/png/icon/loading.svg") 12 | self.resize(self.renderer().defaultSize()) 13 | self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) 14 | self.setWindowFlags(self.windowFlags() | Qt.WindowType.FramelessWindowHint) 15 | isGif = self.renderer().animated() 16 | isGif -------------------------------------------------------------------------------- /src/component/layout/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/layout/__init__.py -------------------------------------------------------------------------------- /src/component/layout/flow_layout.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import QRect, QSize, QPoint, QMargins 2 | from PySide6.QtGui import Qt 3 | from PySide6.QtWidgets import QLayout, QSizePolicy 4 | 5 | 6 | class FlowLayout(QLayout): 7 | def __init__(self, parent=None): 8 | super().__init__(parent) 9 | 10 | if parent is not None: 11 | self.setContentsMargins(QMargins(0, 0, 0, 0)) 12 | 13 | self._item_list = [] 14 | 15 | def __del__(self): 16 | item = self.takeAt(0) 17 | while item: 18 | item = self.takeAt(0) 19 | 20 | def addItem(self, item): 21 | self._item_list.append(item) 22 | 23 | def count(self): 24 | return len(self._item_list) 25 | 26 | def itemAt(self, index): 27 | if 0 <= index < len(self._item_list): 28 | return self._item_list[index] 29 | 30 | return None 31 | 32 | def takeAt(self, index): 33 | if 0 <= index < len(self._item_list): 34 | return self._item_list.pop(index) 35 | 36 | return None 37 | 38 | def expandingDirections(self): 39 | return Qt.Orientation(0) 40 | 41 | def hasHeightForWidth(self): 42 | return True 43 | 44 | def heightForWidth(self, width): 45 | height = self._do_layout(QRect(0, 0, width, 0), True) 46 | return height 47 | 48 | def setGeometry(self, rect): 49 | super(FlowLayout, self).setGeometry(rect) 50 | self._do_layout(rect, False) 51 | 52 | def sizeHint(self): 53 | return self.minimumSize() 54 | 55 | def minimumSize(self): 56 | size = QSize() 57 | 58 | for item in self._item_list: 59 | size = size.expandedTo(item.minimumSize()) 60 | 61 | size += QSize(2 * self.contentsMargins().top(), 2 * self.contentsMargins().top()) 62 | return size 63 | 64 | def _do_layout(self, rect, test_only): 65 | x = rect.x() 66 | y = rect.y() 67 | line_height = 0 68 | spacing = self.spacing() 69 | 70 | for item in self._item_list: 71 | style = item.widget().style() 72 | layout_spacing_x = style.layoutSpacing( 73 | QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal 74 | ) 75 | layout_spacing_y = style.layoutSpacing( 76 | QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical 77 | ) 78 | space_x = spacing + layout_spacing_x 79 | space_y = spacing + layout_spacing_y 80 | next_x = x + item.sizeHint().width() + space_x 81 | if next_x - space_x > rect.right() and line_height > 0: 82 | x = rect.x() 83 | y = y + line_height + space_y 84 | next_x = x + item.sizeHint().width() + space_x 85 | line_height = 0 86 | 87 | if not test_only: 88 | item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) 89 | 90 | x = next_x 91 | line_height = max(line_height, item.sizeHint().height()) 92 | 93 | return y + line_height - rect.y() -------------------------------------------------------------------------------- /src/component/line_edit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/line_edit/__init__.py -------------------------------------------------------------------------------- /src/component/list/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/list/__init__.py -------------------------------------------------------------------------------- /src/component/list/search_setting_item_widget.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QWidget, QHBoxLayout, QLabel 2 | 3 | from component.button.switch_button import SwitchButton 4 | 5 | 6 | class SearchSettingItemWidget(QWidget): 7 | def __init__(self, parent=None, text="", state=True): 8 | QWidget.__init__(self, parent) 9 | self.horizontalLayout = QHBoxLayout(self) 10 | self.label = QLabel(text) 11 | self.horizontalLayout.addWidget(self.label) 12 | self.switchButton = SwitchButton(state=state) 13 | self.horizontalLayout.addWidget(self.switchButton) 14 | -------------------------------------------------------------------------------- /src/component/list/tag_list_widget.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import Qt, QSize 2 | from PySide6.QtGui import QFont, QFontMetrics 3 | from PySide6.QtWidgets import QListWidgetItem, QLabel, QAbstractItemView 4 | 5 | from component.list.base_list_widget import BaseListWidget 6 | from component.scroll.smooth_scroll_bar import SmoothScrollBar 7 | 8 | 9 | class TagListWidget(BaseListWidget): 10 | def __init__(self, parent): 11 | BaseListWidget.__init__(self, parent) 12 | self.setViewMode(self.ListMode) 13 | self.setFlow(self.LeftToRight) 14 | self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 15 | self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 16 | self.setMaximumHeight(30) 17 | self.setFocusPolicy(Qt.NoFocus) 18 | self.hScrollBar = SmoothScrollBar() 19 | self.hScrollBar.setOrientation(Qt.Horizontal) 20 | self.setHorizontalScrollBar(self.hScrollBar) 21 | self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) 22 | self.horizontalScrollBar().setSingleStep(30) 23 | 24 | def wheelEvent(self, arg__1) -> None: 25 | if not self.wheelStatus: 26 | return 27 | if -arg__1.angleDelta().y() > 0 and abs(self.horizontalScrollBar().value() - self.horizontalScrollBar().maximum()) <= 2: 28 | arg__1.ignore() 29 | return 30 | elif -arg__1.angleDelta().y() < 0 and abs(self.horizontalScrollBar().value() - self.horizontalScrollBar().minimum()) <= 2: 31 | arg__1.ignore() 32 | return 33 | self.hScrollBar.ScrollValue(-arg__1.angleDelta().y()) 34 | 35 | def AddItem(self, name, isSelectable=False): 36 | label = QLabel(name) 37 | label.setAlignment(Qt.AlignCenter) 38 | label.setStyleSheet("color:#d5577c") 39 | # font = QFont() 40 | # font.setPointSize(12) 41 | # font.setBold(True) 42 | # label.setFont(font) 43 | 44 | item = QListWidgetItem(self) 45 | item.setTextAlignment(Qt.AlignCenter) 46 | # item.setBackground(QColor(87, 195, 194)) 47 | # item.setBackground(QColor(0, 0, 0, 0)) 48 | fm = QFontMetrics(item.font()) 49 | 50 | width = fm.boundingRect(name).width() 51 | height = fm.height() 52 | self.setItemWidget(item, label) 53 | item.setSizeHint(QSize(width, height) + QSize(20, 0)) 54 | if not isSelectable: 55 | item.setFlags(item.flags() & (~Qt.ItemIsSelectable)) 56 | 57 | self.addItem(item) 58 | return item -------------------------------------------------------------------------------- /src/component/progress_bar/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/progress_bar/__init__.py -------------------------------------------------------------------------------- /src/component/scroll/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/scroll/__init__.py -------------------------------------------------------------------------------- /src/component/scroll/read_scroll.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import QPropertyAnimation, QEasingCurve, QAbstractAnimation 2 | from PySide6.QtWidgets import QScrollBar 3 | 4 | from qt_owner import QtOwner 5 | 6 | 7 | class ReadScroll(QScrollBar): 8 | def __init__(self): 9 | QScrollBar.__init__(self) 10 | self.animation = QPropertyAnimation() 11 | self.animation.setTargetObject(self) 12 | self.animation.setPropertyName(b"value") 13 | self.scrollTime = 1000 14 | self.animation.setDuration(self.scrollTime) 15 | self.animation.setEasingCurve(QEasingCurve.Type.Linear) 16 | self.animationValue = self.value() 17 | self.backTick = 0 18 | self.laveValue = 0 19 | self.lastV = 0 20 | self.animation.finished.connect(self.Finished) 21 | 22 | def Finished(self): 23 | QtOwner().owner.readView.frame.scrollArea.OnValueChange(self.value()) 24 | 25 | def StopScroll(self): 26 | self.backTick = 0 27 | self.animation.stop() 28 | 29 | def Scroll(self, value, time=0): 30 | if self.animation.state() == QAbstractAnimation.State.Running: 31 | self.animation.stop() 32 | oldValue = self.value() 33 | self.animation.setStartValue(oldValue) 34 | if not time: 35 | self.animation.setDuration(self.scrollTime) 36 | else: 37 | self.animation.setDuration(time) 38 | self.animation.setEndValue(oldValue + value) 39 | self.animation.start() 40 | 41 | -------------------------------------------------------------------------------- /src/component/scroll/smooth_scroll_bar.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import QPropertyAnimation, QEasingCurve, Signal 2 | from PySide6.QtWidgets import QScrollBar 3 | 4 | 5 | class SmoothScrollBar(QScrollBar): 6 | MoveEvent = Signal() 7 | 8 | def __init__(self, parent=None): 9 | QScrollBar.__init__(self, parent) 10 | self.__animation = QPropertyAnimation() 11 | self.__animation.setTargetObject(self) 12 | self.__animation.setPropertyName(b"value") 13 | self.__animation.setEasingCurve(QEasingCurve.Type.OutQuint) 14 | self.__animation.setDuration(500) 15 | self.__value = self.value() 16 | self.__animation.finished.connect(self._Finished) 17 | 18 | def _Finished(self): 19 | self.MoveEvent.emit() 20 | return 21 | 22 | def setValue(self, value): 23 | # if self.__animation.state == QPropertyAnimation.State.Running: 24 | # self.__animation.stop() 25 | # self.MoveEvent.emit() 26 | if value == self.value(): 27 | return 28 | self.__animation.stop() 29 | self.MoveEvent.emit() 30 | self.__animation.setStartValue(self.value()) 31 | self.__animation.setEndValue(value) 32 | self.__animation.start() 33 | 34 | def ScrollValue(self, value): 35 | self.__value += value 36 | self.__value = max(self.minimum(), self.__value) 37 | self.__value = min(self.maximum(), self.__value) 38 | self.setValue(self.__value) 39 | 40 | def ScrollTo(self, value): 41 | self.__value = value 42 | self.__value = max(self.minimum(), self.__value) 43 | self.__value = min(self.maximum(), self.__value) 44 | self.setValue(self.__value) 45 | 46 | def ResetValue(self, value): 47 | self.__value = value 48 | 49 | def mousePressEvent(self, event): 50 | self.__animation.stop() 51 | QScrollBar.mousePressEvent(self, event) 52 | self.__value = self.value() 53 | 54 | def mouseReleaseEvent(self, event): 55 | self.__animation.stop() 56 | QScrollBar.mouseReleaseEvent(self, event) 57 | self.__value = self.value() 58 | 59 | def mouseMoveEvent(self, event): 60 | self.__animation.stop() 61 | QScrollBar.mouseMoveEvent(self, event) 62 | self.__value = self.value() 63 | -------------------------------------------------------------------------------- /src/component/scroll_area/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/scroll_area/__init__.py -------------------------------------------------------------------------------- /src/component/scroll_area/smooth_scroll_area.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import Qt 2 | from PySide6.QtGui import QWheelEvent 3 | from PySide6.QtWidgets import QScrollArea 4 | 5 | from component.scroll.smooth_scroll_bar import SmoothScrollBar 6 | 7 | 8 | class SmoothScrollArea(QScrollArea): 9 | """ 一个可以平滑滚动的区域 """ 10 | 11 | def __init__(self, parent=None): 12 | super().__init__(parent) 13 | self.count = 0 14 | self.vScrollBar = SmoothScrollBar() 15 | self.vScrollBar.setOrientation(Qt.Orientation.Vertical) 16 | self.setVerticalScrollBar(self.vScrollBar) 17 | 18 | def wheelEvent(self, arg__1: QWheelEvent) -> None: 19 | self.vScrollBar.ScrollValue(-arg__1.angleDelta().y()) 20 | -------------------------------------------------------------------------------- /src/component/system_tray_icon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/system_tray_icon/__init__.py -------------------------------------------------------------------------------- /src/component/system_tray_icon/my_system_tray_icon.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtGui import QIcon, QAction 2 | from PySide6.QtWidgets import QSystemTrayIcon, QMenu, QApplication 3 | 4 | from qt_owner import QtOwner 5 | from tools.str import Str 6 | 7 | 8 | class MySystemTrayIcon(QSystemTrayIcon): 9 | def __init__(self, parent=None): 10 | super().__init__(parent) 11 | self.count = 0 12 | self.setIcon(QIcon(":/png/icon/logo_round.png")) 13 | self.setToolTip("PicACG") 14 | self.menu = QMenu() 15 | mainUi = QAction(Str.GetStr(Str.MainUi), self) 16 | mainUi.triggered.connect(self.ShowMainUi) 17 | 18 | showMin = QAction(Str.GetStr(Str.ShowMin), self) 19 | showMin.triggered.connect(self.ShowMin) 20 | 21 | quit = QAction(Str.GetStr(Str.Exit), self) 22 | quit.triggered.connect(self.Close) 23 | 24 | self.menu.addAction(mainUi) 25 | self.menu.addAction(showMin) 26 | self.menu.addAction(quit) 27 | self.setContextMenu(self.menu) 28 | self.activated.connect(self.OnActive) 29 | 30 | def ShowMainUi(self): 31 | QtOwner().owner.show() 32 | pass 33 | 34 | def ShowMin(self): 35 | QtOwner().owner.hide() 36 | pass 37 | 38 | def Close(self): 39 | QtOwner().closeType = 3 40 | QtOwner().owner.close() 41 | 42 | def OnActive(self, reason): 43 | if reason == QSystemTrayIcon.Trigger: 44 | return 45 | elif reason == QSystemTrayIcon.DoubleClick: 46 | self.ShowMainUi() 47 | return 48 | pass -------------------------------------------------------------------------------- /src/component/widget/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/widget/__init__.py -------------------------------------------------------------------------------- /src/component/widget/comment_item_widget.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from PySide6 import QtWidgets 4 | from PySide6.QtCore import QEvent, Qt, QSize, QFile 5 | from PySide6.QtGui import QPixmap, QIcon 6 | 7 | from interface.ui_comment_item import Ui_CommentItem 8 | from qt_owner import QtOwner 9 | from tools.log import Log 10 | from tools.str import Str 11 | 12 | 13 | class CommentItemWidget(QtWidgets.QWidget, Ui_CommentItem): 14 | def __init__(self, parent): 15 | super(self.__class__, self).__init__(parent) 16 | Ui_CommentItem.__init__(self) 17 | self.setupUi(self) 18 | self.linkId = "" 19 | self.id = "" 20 | self.url = "" 21 | self.path = "" 22 | self.isGame = False 23 | 24 | self.picIcon.setCursor(Qt.CursorShape.PointingHandCursor) 25 | self.picIcon.setScaledContents(True) 26 | self.picIcon.setWordWrap(True) 27 | 28 | f = QFile(u":/png/icon/logo_round.png") 29 | f.open(QFile.ReadOnly) 30 | self.picIcon.SetPicture(f.readAll()) 31 | f.close() 32 | 33 | self.pictureData = None 34 | self.headData = None 35 | self.picIcon.installEventFilter(self) 36 | self.commentLabel.setWordWrap(True) 37 | self.nameLabel.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) 38 | self.commentLabel.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) 39 | 40 | def SetLike(self, isLike=True): 41 | p = QPixmap() 42 | if isLike: 43 | p.load(":/png/icon/icon_comment_liked.png") 44 | else: 45 | p.load(":/png/icon/icon_comment_like.png") 46 | nums = re.findall("\d+", self.starButton.text()) 47 | if nums: 48 | num = int(nums[0]) + 1 if isLike else int(nums[0]) - 1 49 | self.starButton.setText("({})".format(str(num))) 50 | self.starButton.setIcon(QIcon(p.scaled(50, 50, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))) 51 | self.starButton.setChecked(isLike) 52 | 53 | def SetPicture(self, data): 54 | self.pictureData = data 55 | self.picIcon.SetPicture(self.pictureData, self.headData) 56 | 57 | def SetPictureErr(self, status): 58 | self.picIcon.setText(Str.GetStr(status)) 59 | 60 | def SetHeadData(self, data): 61 | self.headData = data 62 | self.picIcon.SetPicture(self.pictureData, self.headData) 63 | 64 | def eventFilter(self, obj, event): 65 | if event.type() == QEvent.Type.MouseButtonPress: 66 | if event.button() == Qt.MouseButton.LeftButton: 67 | if obj == self.picIcon: 68 | if self.pictureData: 69 | QtOwner().OpenWaifu2xTool(self.pictureData) 70 | elif obj == self.linkLabel and self.linkId: 71 | if self.isGame: 72 | QtOwner().OpenGameInfo(self.linkId) 73 | else: 74 | QtOwner().OpenBookInfo(self.linkId) 75 | return True 76 | else: 77 | return False 78 | else: 79 | return super(self.__class__, self).eventFilter(obj, event) 80 | 81 | def OpenComment(self): 82 | QtOwner().OpenSubComment(self.id, self) 83 | 84 | def KillComment(self): 85 | try: 86 | if self.parent().parent().KillBack: 87 | self.parent().parent().KillBack(self.id) 88 | except Exception as es: 89 | Log.Error(es) 90 | return 91 | -------------------------------------------------------------------------------- /src/component/widget/comment_widget.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from PySide6 import QtWidgets 4 | from PySide6.QtCore import Qt 5 | from PySide6.QtWidgets import QMessageBox 6 | 7 | from config import config 8 | from interface.ui_comment import Ui_Comment 9 | from qt_owner import QtOwner 10 | from server import req, Status 11 | from task.qt_task import QtTaskBase 12 | from tools.book import BookMgr 13 | from tools.log import Log 14 | from tools.str import Str 15 | from tools.tool import ToolUtil 16 | 17 | 18 | class CommentWidget(QtWidgets.QWidget, Ui_Comment, QtTaskBase): 19 | def __init__(self, parent=None): 20 | QtWidgets.QWidget.__init__(self, parent) 21 | Ui_Comment.__init__(self) 22 | QtTaskBase.__init__(self) 23 | self.setupUi(self) 24 | self.spinBox.setMinimum(1) 25 | self.spinBox.setMaximum(1) 26 | self.spinBox.setVisible(False) 27 | self.skipButton.setVisible(False) 28 | 29 | self.bookId = "" 30 | self.site = "" 31 | self.pushButton.clicked.connect(self.SendComment) 32 | 33 | def SwitchCurrent(self, **kwargs): 34 | bookId = kwargs.get("bookId") 35 | site = kwargs.get("site") 36 | refresh = kwargs.get("refresh") 37 | if not bookId and self.listWidget.count() > 0 and not refresh: 38 | return 39 | 40 | self.bookId = bookId 41 | self.site = site 42 | self.LoadComment() 43 | pass 44 | 45 | def ClearCommnetList(self): 46 | self.listWidget.SetWheelStatus(True) 47 | self.listWidget.clear() 48 | self.listWidget.UpdatePage(1, 1) 49 | self.listWidget.UpdateState() 50 | # self.spinBox.setValue(1) 51 | # self.nums.setText("分页:{}/{}".format(str(1), str(1))) 52 | self.ClearTask() 53 | 54 | def LoadComment(self): 55 | # QtOwner().ShowLoading() 56 | self.ClearCommnetList() 57 | info = BookMgr().GetBookBySite(self.bookId, self.site) 58 | if not info: 59 | QtOwner().ShowError(Str.GetStr(Str.Error)) 60 | return 61 | data = [] 62 | for v in info.pageInfo.comment: 63 | tick, name = ToolUtil.ConvertEhentaiDate(v[0]) 64 | data.append((tick, name, v[1])) 65 | 66 | data.sort(key=lambda a: a[0], reverse=True) 67 | for index, v in enumerate(data): 68 | floor = len(data) - index 69 | tick, name, comment = v 70 | self.listWidget.AddUserItem(ToolUtil.ConvertDate(tick) + " by "+name, comment, floor) 71 | # self.AddHttpTask(self.reqGetComment(self.bookId, self.listWidget.page), self.GetCommnetBack) 72 | return 73 | 74 | def SendComment(self): 75 | data = self.commentLine.text() 76 | if not data: 77 | return 78 | if not config.IsLogin: 79 | QtOwner().ShowError(Str.GetStr(Str.NotLogin)) 80 | return 81 | QtOwner().ShowLoading() 82 | self.AddHttpTask(req.SendCommentReq(self.bookId, data), callBack=self.SendCommentBack) 83 | 84 | def SendCommentBack(self, raw): 85 | QtOwner().CloseLoading() 86 | try: 87 | st = raw["st"] 88 | msg = raw.get("msg") 89 | if st != Status.Ok: 90 | QtOwner().ShowError(Str.GetStr(st)) 91 | return 92 | if msg: 93 | QtOwner().ShowError(msg) 94 | return 95 | self.commentLine.setText("") 96 | self.LoadComment() 97 | except Exception as es: 98 | Log.Error(es) 99 | -------------------------------------------------------------------------------- /src/component/widget/main_widget.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from PySide6.QtCore import Qt 5 | 6 | from config.setting import Setting 7 | from tools.log import Log 8 | 9 | MainType = 1 10 | 11 | Main = None 12 | 13 | if sys.platform == "win32" and not Setting.IsNotUseTitleBar.value: 14 | try: 15 | from interface.ui_main_windows import Ui_MainWindows 16 | from .windows.frame_less_widget import FrameLessWidget 17 | 18 | class MainWidget(FrameLessWidget, Ui_MainWindows): 19 | def __init__(self): 20 | FrameLessWidget.__init__(self) 21 | Ui_MainWindows.__init__(self) 22 | self.setupUi(self) 23 | self.totalStackWidget.setAttribute(Qt.WA_TranslucentBackground) 24 | self.widget.setAttribute(Qt.WA_TranslucentBackground) 25 | 26 | def showFullScreen(self): 27 | self.widget.setVisible(False) 28 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 29 | return FrameLessWidget.showFullScreen(self) 30 | 31 | def showNormal(self): 32 | self.widget.setVisible(True) 33 | self.verticalLayout.setContentsMargins(3, 3, 3, 3) 34 | return FrameLessWidget.showNormal(self) 35 | 36 | def showMaximized(self): 37 | self.widget.setVisible(True) 38 | self.verticalLayout.setContentsMargins(3, 3, 3, 3) 39 | return FrameLessWidget.showMaximized(self) 40 | 41 | def setSubTitle(self, text): 42 | self.widget.subTitle.setText(text) 43 | return 44 | 45 | Main = MainWidget 46 | MainType = 2 47 | except Exception as es: 48 | Log.Error(es) 49 | 50 | if not Main: 51 | from interface.ui_main import Ui_Main 52 | from PySide6.QtWidgets import QMainWindow 53 | 54 | class MainWidget(QMainWindow, Ui_Main): 55 | def __init__(self): 56 | QMainWindow.__init__(self) 57 | Ui_Main.__init__(self) 58 | self.setupUi(self) 59 | 60 | def setSubTitle(self, text): 61 | return 62 | 63 | Main = MainWidget 64 | -------------------------------------------------------------------------------- /src/component/widget/setting_item_widget.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtCore import Qt 2 | from PySide6.QtWidgets import QWidget 3 | 4 | from interface.ui_setting_item import Ui_SettingItem 5 | 6 | 7 | class SettingItemWidget(QWidget): 8 | def __init__(self, parent=None): 9 | QWidget.__init__(self, parent) 10 | self.setStyleSheet(""" 11 | QWidget 12 | { 13 | background-color: rgb(253, 253, 253); 14 | 15 | border:2px solid rgb(234,234,234); 16 | border-radius:5px 17 | } 18 | """) -------------------------------------------------------------------------------- /src/component/widget/stack_widget.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | from PySide6.QtCore import QPropertyAnimation 4 | from PySide6.QtWidgets import QGraphicsOpacityEffect, QStackedWidget 5 | 6 | 7 | class OpacityAniStackedWidget(QStackedWidget): 8 | """ 带淡入淡出动画效果的堆叠窗口类 """ 9 | 10 | def __init__(self, parent=None): 11 | super().__init__(parent) 12 | # 记录动画完成后需要切换到的窗口下标 13 | self.__nextIndex = 0 14 | # 给第二个窗口添加的淡入淡出动画 15 | self.__opacityEffect = QGraphicsOpacityEffect(self) 16 | self.__opacityAni = QPropertyAnimation( 17 | self.__opacityEffect, b'opacity') 18 | # 初始化动画 19 | self.__opacityEffect.setOpacity(0) 20 | self.__opacityAni.setDuration(220) 21 | self.__opacityAni.finished.connect(self.__aniFinishedSlot) 22 | 23 | def addWidget(self, widget): 24 | """ 向窗口中添加堆叠窗口 """ 25 | if self.count() == 2: 26 | raise Exception('最多只能有两个堆叠窗口') 27 | super().addWidget(widget) 28 | # 给第二个窗口设置淡入淡出效果 29 | if self.count() == 2: 30 | self.widget(1).setGraphicsEffect(self.__opacityEffect) 31 | 32 | def setCurrentIndex(self, index: int): 33 | """ 切换当前堆叠窗口 """ 34 | # 如果当前下标等于目标下标就直接返回 35 | if index == self.currentIndex(): 36 | return 37 | if index == 1: 38 | self.__opacityAni.setStartValue(0) 39 | self.__opacityAni.setEndValue(1) 40 | super().setCurrentIndex(1) 41 | elif index == 0: 42 | self.__opacityAni.setStartValue(1) 43 | self.__opacityAni.setEndValue(0) 44 | else: 45 | raise Exception('下标不能超过1') 46 | # 强行显示被隐藏的 widget(0) 47 | self.widget(0).show() 48 | self.__nextIndex = index 49 | self.__opacityAni.start() 50 | 51 | def setCurrentWidget(self, widget): 52 | """ 切换当前堆叠窗口 """ 53 | self.setCurrentIndex(self.indexOf(widget)) 54 | 55 | def __aniFinishedSlot(self): 56 | """ 动画完成后切换当前窗口 """ 57 | super().setCurrentIndex(self.__nextIndex) 58 | -------------------------------------------------------------------------------- /src/component/widget/title_bar_widget.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from PySide6.QtCore import Qt 3 | from PySide6.QtWidgets import QWidget 4 | 5 | from interface.ui_title_bar import Ui_TitleBar 6 | 7 | 8 | class TitleBarWidget(QWidget, Ui_TitleBar): 9 | """ 定义标题栏 """ 10 | 11 | def __init__(self, parent): 12 | super().__init__(parent) 13 | Ui_TitleBar.__init__(self) 14 | self.setupUi(self) 15 | self.label.setText("EHentai") 16 | self.setAttribute(Qt.WA_StyledBackground, True) 17 | self.resize(1360, 40) 18 | self.maxBt.clicked.connect(self._ShowRestoreWindow) 19 | self.closeButton.clicked.connect(self._Close) 20 | self.minButton.clicked.connect(self._ShowMinimized) 21 | 22 | def _ShowRestoreWindow(self): 23 | self.__showRestoreWindow() 24 | return 25 | 26 | def mouseDoubleClickEvent(self, event): 27 | self._ShowRestoreWindow() 28 | 29 | def __showRestoreWindow(self): 30 | if self.window().isMaximized(): 31 | self.maxBt.setProperty("isMax", False) 32 | self.maxBt.style().unpolish(self.maxBt) 33 | self.maxBt.style().polish(self.maxBt) 34 | self.window().showNormal() 35 | else: 36 | self.maxBt.setProperty("isMax", True) 37 | self.maxBt.style().unpolish(self.maxBt) 38 | self.maxBt.style().polish(self.maxBt) 39 | self.window().showMaximized() 40 | 41 | def _Close(self): 42 | return self.window().close() 43 | 44 | def _ShowMinimized(self): 45 | return self.window().showMinimized() 46 | 47 | def mousePressEvent(self, event): 48 | """ 移动窗口 """ 49 | # 判断鼠标点击位置是否允许拖动 50 | from win32.win32gui import ReleaseCapture 51 | from win32.win32api import SendMessage 52 | from win32.lib import win32con 53 | if self.__isPointInDragRegion(event.pos()): 54 | ReleaseCapture() 55 | SendMessage( 56 | self.window().winId(), 57 | win32con.WM_SYSCOMMAND, 58 | win32con.SC_MOVE + win32con.HTCAPTION, 59 | 0, 60 | ) 61 | event.ignore() 62 | 63 | def __isPointInDragRegion(self, pos) -> bool: 64 | """ 检查鼠标按下的点是否属于允许拖动的区域 """ 65 | x = pos.x() 66 | left = 0 67 | # 如果最小化按钮看不见也意味着最大化按钮看不见 68 | right = self.width() - 57 69 | return left < x < right -------------------------------------------------------------------------------- /src/component/widget/windows/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/component/widget/windows/__init__.py -------------------------------------------------------------------------------- /src/component/widget/windows/c_structures.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | from ctypes import POINTER, Structure, c_int 4 | from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT 5 | from enum import Enum 6 | 7 | 8 | class WINDOWCOMPOSITIONATTRIB(Enum): 9 | WCA_UNDEFINED = 0, 10 | WCA_NCRENDERING_ENABLED = 1, 11 | WCA_NCRENDERING_POLICY = 2, 12 | WCA_TRANSITIONS_FORCEDISABLED = 3, 13 | WCA_ALLOW_NCPAINT = 4, 14 | WCA_CAPTION_BUTTON_BOUNDS = 5, 15 | WCA_NONCLIENT_RTL_LAYOUT = 6, 16 | WCA_FORCE_ICONIC_REPRESENTATION = 7, 17 | WCA_EXTENDED_FRAME_BOUNDS = 8, 18 | WCA_HAS_ICONIC_BITMAP = 9, 19 | WCA_THEME_ATTRIBUTES = 10, 20 | WCA_NCRENDERING_EXILED = 11, 21 | WCA_NCADORNMENTINFO = 12, 22 | WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, 23 | WCA_VIDEO_OVERLAY_ACTIVE = 14, 24 | WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, 25 | WCA_DISALLOW_PEEK = 16, 26 | WCA_CLOAK = 17, 27 | WCA_CLOAKED = 18, 28 | WCA_ACCENT_POLICY = 19, 29 | WCA_FREEZE_REPRESENTATION = 20, 30 | WCA_EVER_UNCLOAKED = 21, 31 | WCA_VISUAL_OWNER = 22, 32 | WCA_LAST = 23 33 | 34 | 35 | class ACCENT_STATE(Enum): 36 | """ 客户区状态枚举类 """ 37 | ACCENT_DISABLED = 0, 38 | ACCENT_ENABLE_GRADIENT = 1, 39 | ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, 40 | ACCENT_ENABLE_BLURBEHIND = 3, # Aero效果 41 | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, # 亚克力效果 42 | ACCENT_INVALID_STATE = 5 43 | 44 | 45 | class ACCENT_POLICY(Structure): 46 | """ 设置客户区的具体属性 """ 47 | 48 | _fields_ = [ 49 | ("AccentState", DWORD), 50 | ("AccentFlags", DWORD), 51 | ("GradientColor", DWORD), 52 | ("AnimationId", DWORD), 53 | ] 54 | 55 | 56 | class WINDOWCOMPOSITIONATTRIBDATA(Structure): 57 | _fields_ = [ 58 | ("Attribute", DWORD), 59 | # POINTER()接收任何ctypes类型,并返回一个指针类型 60 | ("Data", POINTER(ACCENT_POLICY)), 61 | ("SizeOfData", ULONG), 62 | ] 63 | 64 | 65 | class DWMNCRENDERINGPOLICY(Enum): 66 | DWMNCRP_USEWINDOWSTYLE = 0 67 | DWMNCRP_DISABLED = 1 68 | DWMNCRP_ENABLED = 2 69 | DWMNCRP_LAS = 3 70 | 71 | 72 | class DWMWINDOWATTRIBUTE(Enum): 73 | DWMWA_NCRENDERING_ENABLED = 1 74 | DWMWA_NCRENDERING_POLICY = 2 75 | DWMWA_TRANSITIONS_FORCEDISABLED = 3 76 | DWMWA_ALLOW_NCPAINT = 4 77 | DWMWA_CAPTION_BUTTON_BOUNDS = 5 78 | DWMWA_NONCLIENT_RTL_LAYOUT = 6 79 | DWMWA_FORCE_ICONIC_REPRESENTATION = 7 80 | DWMWA_FLIP3D_POLICY = 8 81 | DWMWA_EXTENDED_FRAME_BOUNDS = 9 82 | DWMWA_HAS_ICONIC_BITMAP = 10 83 | DWMWA_DISALLOW_PEEK = 11 84 | DWMWA_EXCLUDED_FROM_PEEK = 12 85 | DWMWA_CLOAK = 13 86 | DWMWA_CLOAKED = 14 87 | DWMWA_FREEZE_REPRESENTATION = 25 88 | DWMWA_LAST = 16 89 | 90 | 91 | class MARGINS(Structure): 92 | _fields_ = [ 93 | ("cxLeftWidth", c_int), 94 | ("cxRightWidth", c_int), 95 | ("cyTopHeight", c_int), 96 | ("cyBottomHeight", c_int), 97 | ] 98 | 99 | 100 | class MINMAXINFO(Structure): 101 | _fields_ = [ 102 | ("ptReserved", POINT), 103 | ("ptMaxSize", POINT), 104 | ("ptMaxPosition", POINT), 105 | ("ptMinTrackSize", POINT), 106 | ("ptMaxTrackSize", POINT), 107 | ] 108 | 109 | 110 | class PWINDOWPOS(Structure): 111 | _fields_ = [ 112 | ('hWnd', HWND), 113 | ('hwndInsertAfter', HWND), 114 | ('x', c_int), 115 | ('y', c_int), 116 | ('cx', c_int), 117 | ('cy', c_int), 118 | ('flags', UINT) 119 | ] 120 | 121 | 122 | class NCCALCSIZE_PARAMS(Structure): 123 | _fields_ = [ 124 | ('rgrc', RECT*3), 125 | ('lppos', POINTER(PWINDOWPOS)) 126 | ] 127 | -------------------------------------------------------------------------------- /src/component/widget/windows/window_effect.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | from ctypes import POINTER, WinDLL, byref, c_bool, c_int, pointer, sizeof 4 | from ctypes.wintypes import DWORD, LONG, LPCVOID 5 | 6 | from win32 import win32api, win32gui 7 | from win32.lib import win32con 8 | 9 | from .c_structures import (ACCENT_POLICY, ACCENT_STATE, DWMNCRENDERINGPOLICY, 10 | DWMWINDOWATTRIBUTE, MARGINS, 11 | WINDOWCOMPOSITIONATTRIB, 12 | WINDOWCOMPOSITIONATTRIBDATA) 13 | 14 | 15 | class WindowEffect: 16 | """ 调用windows api实现窗口效果 """ 17 | 18 | def __init__(self): 19 | # 调用api 20 | self.user32 = WinDLL("user32") 21 | self.dwmapi = WinDLL("dwmapi") 22 | self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute 23 | self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea 24 | self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute 25 | self.SetWindowCompositionAttribute.restype = c_bool 26 | self.DwmExtendFrameIntoClientArea.restype = LONG 27 | self.DwmSetWindowAttribute.restype = LONG 28 | self.SetWindowCompositionAttribute.argtypes = [ 29 | c_int, 30 | POINTER(WINDOWCOMPOSITIONATTRIBDATA), 31 | ] 32 | self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD] 33 | self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)] 34 | # 初始化结构体 35 | # self.accentPolicy = ACCENT_POLICY() 36 | # self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA() 37 | # self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value[ 38 | # 0 39 | # ] 40 | # self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy) 41 | # self.winCompAttrData.Data = pointer(self.accentPolicy) 42 | 43 | @staticmethod 44 | def addWindowAnimation(hWnd): 45 | """ 打开窗口动画效果 46 | 47 | Parameters 48 | ---------- 49 | hWnd : int or `sip.voidptr` 50 | 窗口句柄 51 | """ 52 | style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE) 53 | win32gui.SetWindowLong( 54 | hWnd, 55 | win32con.GWL_STYLE, 56 | style 57 | | win32con.WS_MAXIMIZEBOX 58 | | win32con.WS_CAPTION 59 | | win32con.CS_DBLCLKS 60 | | win32con.WS_THICKFRAME, 61 | ) 62 | -------------------------------------------------------------------------------- /src/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/config/__init__.py -------------------------------------------------------------------------------- /src/config/config.py: -------------------------------------------------------------------------------- 1 | Url = "https://e-hentai.org" # 域名 2 | ExUrl = "https://exhentai.org" # 域名 3 | FormUrl = "https://forums.e-hentai.org" # 域名 4 | ApiUrl = "https://api.e-hentai.org/api.php" 5 | 6 | ThreadNum = 10 # 线程 7 | DownloadThreadNum = 5 # 下载线程 8 | ConvertThreadNum = 3 # 同时转换数量 9 | 10 | SavePath = '' 11 | SavePathDir = "commies" # 下载目录 12 | ResetCnt = 5 # 下载重试次数 13 | 14 | IsUseCache = True # 是否使用cache 15 | CachePathDir = "cache" # cache目录 16 | # CacheExpired = 24 * 60 * 60 # cache过期时间24小时 17 | PreLoading = 10 # 预加载5页 18 | PreLook = 4 # 预显示 19 | 20 | IsLoadingPicture = True 21 | 22 | UpdateUrl = "https://github.com/tonquer/ehentai-qt/releases/latest" 23 | UpdateUrlApi = "https://api.github.com/repos/tonquer/ehentai-qt/releases" 24 | UpdateUrlBack = "https://github.com/tonquer/ehentai-qt" 25 | 26 | UpdateUrl2 = "https://hub.ggo.icu/tonquer/ehentai-qt/releases/latest" 27 | UpdateUrl2Api = "https://api.ggo.icu/repos/tonquer/ehentai-qt/releases" 28 | UpdateUrl2Back = "https://hub.ggo.icu/tonquer/ehentai-qt" 29 | 30 | UpdateUrl3 = "https://hub.fastgit.xyz/tonquer/ehentai-qt/releases/latest" 31 | UpdateUrl3Api = "https://api.fastgit.xyz/repos/tonquer/ehentai-qt/releases" 32 | UpdateUrl3Back = "https://hub.fastgit.xyz/tonquer/ehentai-qt" 33 | 34 | 35 | Issues1 = "https://github.com/tonquer/ehentai-qt/issues" 36 | Issues2 = "https://hub.ggo.icu/tonquer/ehentai-qt/issues" 37 | Issues3 = "https://hub.fastgit.xyz/tonquer/ehentai-qt/issues" 38 | 39 | UpdateVersion = "v1.1.5" 40 | RealVersion = "v1.1.5" 41 | Waifu2xVersion = "1.1.6" 42 | TimeVersion = "2023-8-13" 43 | 44 | CurSite = "e-hentai" # 当前站点 45 | CurLoginName = "" # 当前登录名 46 | IsLogin=False 47 | 48 | # waifu2x 49 | CanWaifu2x = False 50 | ErrorMsg = "" 51 | 52 | Encode = 0 # 当前正在使用的索引 53 | UseCpuNum = 0 54 | EncodeGpu = "" 55 | 56 | Waifu2xPath = "waifu2x" 57 | 58 | IsTips = 1 59 | 60 | # https://ehwiki.org/wiki/IPs 61 | LocalProxyPort = 0 62 | 63 | DomainDns = { 64 | "e-hentai.org": "104.20.135.21", 65 | "exhentai.org": "178.175.128.254", 66 | "ehgt.org": "37.48.89.44", 67 | "forums.e-hentai.org": "104.20.135.21", 68 | "gt0.ehgt.org": "37.48.89.44", 69 | "api.e-hentai.org": "104.20.135.21", 70 | } 71 | 72 | DomainMapping = { 73 | "gt0.ehgt.org": "ehgt.org", 74 | "forums.e-hentai.org": "e-hentai.org" 75 | } 76 | 77 | Hosts = { 78 | } 79 | -------------------------------------------------------------------------------- /src/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/interface/__init__.py -------------------------------------------------------------------------------- /src/interface/ui_category.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_category.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QListWidgetItem, QSizePolicy, QVBoxLayout, 19 | QWidget) 20 | 21 | from component.list.category_list_widget import CategoryListWidget 22 | 23 | class Ui_Category(object): 24 | def setupUi(self, Category): 25 | if not Category.objectName(): 26 | Category.setObjectName(u"Category") 27 | Category.resize(400, 300) 28 | self.verticalLayout = QVBoxLayout(Category) 29 | self.verticalLayout.setObjectName(u"verticalLayout") 30 | self.bookList = CategoryListWidget(Category) 31 | self.bookList.setObjectName(u"bookList") 32 | 33 | self.verticalLayout.addWidget(self.bookList) 34 | 35 | 36 | self.retranslateUi(Category) 37 | 38 | QMetaObject.connectSlotsByName(Category) 39 | # setupUi 40 | 41 | def retranslateUi(self, Category): 42 | Category.setWindowTitle(QCoreApplication.translate("Category", u"\u5206\u7c7b", None)) 43 | # retranslateUi 44 | 45 | -------------------------------------------------------------------------------- /src/interface/ui_comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_comment.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QLineEdit, 19 | QListWidgetItem, QPushButton, QSizePolicy, QSpacerItem, 20 | QSpinBox, QVBoxLayout, QWidget) 21 | 22 | from component.list.user_list_widget import UserListWidget 23 | 24 | class Ui_Comment(object): 25 | def setupUi(self, Comment): 26 | if not Comment.objectName(): 27 | Comment.setObjectName(u"Comment") 28 | Comment.resize(400, 300) 29 | self.verticalLayout = QVBoxLayout(Comment) 30 | self.verticalLayout.setObjectName(u"verticalLayout") 31 | self.listWidget = UserListWidget(Comment) 32 | self.listWidget.setObjectName(u"listWidget") 33 | 34 | self.verticalLayout.addWidget(self.listWidget) 35 | 36 | self.horizontalLayout = QHBoxLayout() 37 | self.horizontalLayout.setObjectName(u"horizontalLayout") 38 | self.commentLine = QLineEdit(Comment) 39 | self.commentLine.setObjectName(u"commentLine") 40 | 41 | self.horizontalLayout.addWidget(self.commentLine) 42 | 43 | self.pushButton = QPushButton(Comment) 44 | self.pushButton.setObjectName(u"pushButton") 45 | 46 | self.horizontalLayout.addWidget(self.pushButton) 47 | 48 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 49 | 50 | self.horizontalLayout.addItem(self.horizontalSpacer) 51 | 52 | self.nums = QLabel(Comment) 53 | self.nums.setObjectName(u"nums") 54 | 55 | self.horizontalLayout.addWidget(self.nums) 56 | 57 | self.spinBox = QSpinBox(Comment) 58 | self.spinBox.setObjectName(u"spinBox") 59 | 60 | self.horizontalLayout.addWidget(self.spinBox) 61 | 62 | self.skipButton = QPushButton(Comment) 63 | self.skipButton.setObjectName(u"skipButton") 64 | 65 | self.horizontalLayout.addWidget(self.skipButton) 66 | 67 | 68 | self.verticalLayout.addLayout(self.horizontalLayout) 69 | 70 | 71 | self.retranslateUi(Comment) 72 | 73 | QMetaObject.connectSlotsByName(Comment) 74 | # setupUi 75 | 76 | def retranslateUi(self, Comment): 77 | Comment.setWindowTitle(QCoreApplication.translate("Comment", u"\u8bc4\u8bba", None)) 78 | self.pushButton.setText(QCoreApplication.translate("Comment", u"\u56de\u590d", None)) 79 | #if QT_CONFIG(shortcut) 80 | self.pushButton.setShortcut(QCoreApplication.translate("Comment", u"Return", None)) 81 | #endif // QT_CONFIG(shortcut) 82 | self.nums.setText(QCoreApplication.translate("Comment", u"TextLabel", None)) 83 | self.skipButton.setText(QCoreApplication.translate("Comment", u"\u8df3\u8f6c", None)) 84 | # retranslateUi 85 | 86 | -------------------------------------------------------------------------------- /src/interface/ui_doh_dns.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_doh_dns.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QHBoxLayout, QHeaderView, QPushButton, 19 | QSizePolicy, QTableWidget, QTableWidgetItem, QVBoxLayout, 20 | QWidget) 21 | 22 | class Ui_DohDns(object): 23 | def setupUi(self, DohDns): 24 | if not DohDns.objectName(): 25 | DohDns.setObjectName(u"DohDns") 26 | DohDns.resize(400, 300) 27 | self.verticalLayout = QVBoxLayout(DohDns) 28 | self.verticalLayout.setObjectName(u"verticalLayout") 29 | self.tableWidget = QTableWidget(DohDns) 30 | if (self.tableWidget.columnCount() < 2): 31 | self.tableWidget.setColumnCount(2) 32 | __qtablewidgetitem = QTableWidgetItem() 33 | self.tableWidget.setHorizontalHeaderItem(0, __qtablewidgetitem) 34 | __qtablewidgetitem1 = QTableWidgetItem() 35 | self.tableWidget.setHorizontalHeaderItem(1, __qtablewidgetitem1) 36 | self.tableWidget.setObjectName(u"tableWidget") 37 | 38 | self.verticalLayout.addWidget(self.tableWidget) 39 | 40 | self.horizontalLayout = QHBoxLayout() 41 | self.horizontalLayout.setObjectName(u"horizontalLayout") 42 | self.pushButton = QPushButton(DohDns) 43 | self.pushButton.setObjectName(u"pushButton") 44 | 45 | self.horizontalLayout.addWidget(self.pushButton) 46 | 47 | 48 | self.verticalLayout.addLayout(self.horizontalLayout) 49 | 50 | 51 | self.retranslateUi(DohDns) 52 | 53 | QMetaObject.connectSlotsByName(DohDns) 54 | # setupUi 55 | 56 | def retranslateUi(self, DohDns): 57 | DohDns.setWindowTitle(QCoreApplication.translate("DohDns", u"Doh DNS", None)) 58 | ___qtablewidgetitem = self.tableWidget.horizontalHeaderItem(0) 59 | ___qtablewidgetitem.setText(QCoreApplication.translate("DohDns", u"Host", None)); 60 | ___qtablewidgetitem1 = self.tableWidget.horizontalHeaderItem(1) 61 | ___qtablewidgetitem1.setText(QCoreApplication.translate("DohDns", u"IP", None)); 62 | self.pushButton.setText(QCoreApplication.translate("DohDns", u"\u5173\u95ed", None)) 63 | # retranslateUi 64 | 65 | -------------------------------------------------------------------------------- /src/interface/ui_host.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_host.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QPlainTextEdit, 19 | QSizePolicy, QSpacerItem, QVBoxLayout, QWidget) 20 | 21 | class Ui_LoginHostWidget(object): 22 | def setupUi(self, LoginHostWidget): 23 | if not LoginHostWidget.objectName(): 24 | LoginHostWidget.setObjectName(u"LoginHostWidget") 25 | LoginHostWidget.resize(400, 272) 26 | self.verticalLayout = QVBoxLayout(LoginHostWidget) 27 | self.verticalLayout.setObjectName(u"verticalLayout") 28 | self.horizontalLayout = QHBoxLayout() 29 | self.horizontalLayout.setObjectName(u"horizontalLayout") 30 | self.label = QLabel(LoginHostWidget) 31 | self.label.setObjectName(u"label") 32 | 33 | self.horizontalLayout.addWidget(self.label) 34 | 35 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 36 | 37 | self.horizontalLayout.addItem(self.horizontalSpacer) 38 | 39 | 40 | self.verticalLayout.addLayout(self.horizontalLayout) 41 | 42 | self.plainTextEdit = QPlainTextEdit(LoginHostWidget) 43 | self.plainTextEdit.setObjectName(u"plainTextEdit") 44 | 45 | self.verticalLayout.addWidget(self.plainTextEdit) 46 | 47 | self.horizontalLayout_2 = QHBoxLayout() 48 | self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") 49 | 50 | self.verticalLayout.addLayout(self.horizontalLayout_2) 51 | 52 | 53 | self.retranslateUi(LoginHostWidget) 54 | 55 | QMetaObject.connectSlotsByName(LoginHostWidget) 56 | # setupUi 57 | 58 | def retranslateUi(self, LoginHostWidget): 59 | LoginHostWidget.setWindowTitle(QCoreApplication.translate("LoginHostWidget", u"Form", None)) 60 | self.label.setText(QCoreApplication.translate("LoginHostWidget", u"\u81ea\u5b9a\u4e49IP\uff1a\u8bf7\u4f7f\u7528\u57df\u540d:IP\u7684\u683c\u5f0f", None)) 61 | # retranslateUi 62 | 63 | -------------------------------------------------------------------------------- /src/interface/ui_index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_index.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QSizePolicy, 19 | QVBoxLayout, QWidget) 20 | 21 | from component.list.comic_list_widget import ComicListWidget 22 | 23 | class Ui_Index(object): 24 | def setupUi(self, Index): 25 | if not Index.objectName(): 26 | Index.setObjectName(u"Index") 27 | Index.resize(400, 300) 28 | Index.setStyleSheet(u"QWidget#Index{\n" 29 | "border: 3px solid rgb(229, 229, 229);\n" 30 | "border-radius: 15px;\n" 31 | "}\n" 32 | "QLabel#titleLabel {\n" 33 | " font: 14px 'Microsoft YaHei Light';\n" 34 | " border: 0px;\n" 35 | " /* padding: 10px 15px 10px 15px; */\n" 36 | "}\n" 37 | "QListWidget {\n" 38 | " border: 0px;\n" 39 | "}\n" 40 | "*{\n" 41 | "background-color: rgb(249,249,249);\n" 42 | "}") 43 | self.verticalLayout_2 = QVBoxLayout(Index) 44 | self.verticalLayout_2.setSpacing(0) 45 | self.verticalLayout_2.setObjectName(u"verticalLayout_2") 46 | self.titleLabel = QLabel(Index) 47 | self.titleLabel.setObjectName(u"titleLabel") 48 | 49 | self.verticalLayout_2.addWidget(self.titleLabel) 50 | 51 | self.bookList = ComicListWidget(Index) 52 | self.bookList.setObjectName(u"bookList") 53 | 54 | self.verticalLayout_2.addWidget(self.bookList) 55 | 56 | 57 | self.retranslateUi(Index) 58 | 59 | QMetaObject.connectSlotsByName(Index) 60 | # setupUi 61 | 62 | def retranslateUi(self, Index): 63 | Index.setWindowTitle(QCoreApplication.translate("Index", u"Form", None)) 64 | self.titleLabel.setText(QCoreApplication.translate("Index", u"\u9996\u9875", None)) 65 | # retranslateUi 66 | 67 | -------------------------------------------------------------------------------- /src/interface/ui_line_edit_help_widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_line_edit_help_widget.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QLabel, QListView, QSizePolicy, 19 | QVBoxLayout, QWidget) 20 | 21 | class Ui_LineEditHelp(object): 22 | def setupUi(self, LineEditHelp): 23 | if not LineEditHelp.objectName(): 24 | LineEditHelp.setObjectName(u"LineEditHelp") 25 | LineEditHelp.resize(400, 440) 26 | self.verticalLayout_2 = QVBoxLayout(LineEditHelp) 27 | self.verticalLayout_2.setObjectName(u"verticalLayout_2") 28 | self.label = QLabel(LineEditHelp) 29 | self.label.setObjectName(u"label") 30 | 31 | self.verticalLayout_2.addWidget(self.label) 32 | 33 | self.listView = QListView(LineEditHelp) 34 | self.listView.setObjectName(u"listView") 35 | self.listView.setMinimumSize(QSize(0, 120)) 36 | 37 | self.verticalLayout_2.addWidget(self.listView) 38 | 39 | 40 | self.retranslateUi(LineEditHelp) 41 | 42 | QMetaObject.connectSlotsByName(LineEditHelp) 43 | # setupUi 44 | 45 | def retranslateUi(self, LineEditHelp): 46 | LineEditHelp.setWindowTitle(QCoreApplication.translate("LineEditHelp", u"Form", None)) 47 | self.label.setText(QCoreApplication.translate("LineEditHelp", u"\u8054\u60f3\u8bcd", None)) 48 | # retranslateUi 49 | 50 | -------------------------------------------------------------------------------- /src/interface/ui_local_eps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_local_eps.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout, QLabel, 19 | QListWidgetItem, QRadioButton, QSizePolicy, QSpacerItem, 20 | QWidget) 21 | 22 | from component.list.comic_list_widget import ComicListWidget 23 | 24 | class Ui_LocalEps(object): 25 | def setupUi(self, LocalEps): 26 | if not LocalEps.objectName(): 27 | LocalEps.setObjectName(u"LocalEps") 28 | LocalEps.resize(646, 391) 29 | self.gridLayout_2 = QGridLayout(LocalEps) 30 | self.gridLayout_2.setObjectName(u"gridLayout_2") 31 | self.gridLayout_3 = QGridLayout() 32 | self.gridLayout_3.setObjectName(u"gridLayout_3") 33 | self.bookList = ComicListWidget(LocalEps) 34 | self.bookList.setObjectName(u"bookList") 35 | self.bookList.setStyleSheet(u"") 36 | 37 | self.gridLayout_3.addWidget(self.bookList, 0, 0, 1, 1) 38 | 39 | 40 | self.gridLayout_2.addLayout(self.gridLayout_3, 4, 0, 1, 1) 41 | 42 | self.name = QLabel(LocalEps) 43 | self.name.setObjectName(u"name") 44 | 45 | self.gridLayout_2.addWidget(self.name, 0, 0, 1, 1) 46 | 47 | self.horizontalLayout = QHBoxLayout() 48 | self.horizontalLayout.setObjectName(u"horizontalLayout") 49 | self.showWaifu2x = QRadioButton(LocalEps) 50 | self.showWaifu2x.setObjectName(u"showWaifu2x") 51 | 52 | self.horizontalLayout.addWidget(self.showWaifu2x) 53 | 54 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 55 | 56 | self.horizontalLayout.addItem(self.horizontalSpacer) 57 | 58 | 59 | self.gridLayout_2.addLayout(self.horizontalLayout, 2, 0, 1, 1) 60 | 61 | 62 | self.retranslateUi(LocalEps) 63 | 64 | QMetaObject.connectSlotsByName(LocalEps) 65 | # setupUi 66 | 67 | def retranslateUi(self, LocalEps): 68 | LocalEps.setWindowTitle(QCoreApplication.translate("LocalEps", u"\u672c\u5730\u6f2b\u753b\u7ae0\u8282", None)) 69 | self.name.setText("") 70 | self.showWaifu2x.setText(QCoreApplication.translate("LocalEps", u"\u53ea\u663e\u793aWaifu2x", None)) 71 | # retranslateUi 72 | 73 | -------------------------------------------------------------------------------- /src/interface/ui_local_fold.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_local_fold.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.4 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QListWidget, 19 | QListWidgetItem, QPushButton, QSizePolicy, QSpacerItem, 20 | QVBoxLayout, QWidget) 21 | 22 | class Ui_LocalFold(object): 23 | def setupUi(self, LocalFold): 24 | if not LocalFold.objectName(): 25 | LocalFold.setObjectName(u"LocalFold") 26 | LocalFold.resize(400, 300) 27 | self.verticalLayout = QVBoxLayout(LocalFold) 28 | self.verticalLayout.setObjectName(u"verticalLayout") 29 | self.horizontalLayout = QHBoxLayout() 30 | self.horizontalLayout.setObjectName(u"horizontalLayout") 31 | self.label = QLabel(LocalFold) 32 | self.label.setObjectName(u"label") 33 | 34 | self.horizontalLayout.addWidget(self.label) 35 | 36 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 37 | 38 | self.horizontalLayout.addItem(self.horizontalSpacer) 39 | 40 | self.editButton = QPushButton(LocalFold) 41 | self.editButton.setObjectName(u"editButton") 42 | 43 | self.horizontalLayout.addWidget(self.editButton) 44 | 45 | 46 | self.verticalLayout.addLayout(self.horizontalLayout) 47 | 48 | self.listWidget = QListWidget(LocalFold) 49 | self.listWidget.setObjectName(u"listWidget") 50 | 51 | self.verticalLayout.addWidget(self.listWidget) 52 | 53 | self.horizontalLayout_2 = QHBoxLayout() 54 | self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") 55 | self.saveButton = QPushButton(LocalFold) 56 | self.saveButton.setObjectName(u"saveButton") 57 | 58 | self.horizontalLayout_2.addWidget(self.saveButton) 59 | 60 | self.closeButton = QPushButton(LocalFold) 61 | self.closeButton.setObjectName(u"closeButton") 62 | 63 | self.horizontalLayout_2.addWidget(self.closeButton) 64 | 65 | 66 | self.verticalLayout.addLayout(self.horizontalLayout_2) 67 | 68 | 69 | self.retranslateUi(LocalFold) 70 | 71 | QMetaObject.connectSlotsByName(LocalFold) 72 | # setupUi 73 | 74 | def retranslateUi(self, LocalFold): 75 | LocalFold.setWindowTitle(QCoreApplication.translate("LocalFold", u"Form", None)) 76 | self.label.setText(QCoreApplication.translate("LocalFold", u"\u5206\u7c7b", None)) 77 | self.editButton.setText(QCoreApplication.translate("LocalFold", u"\u7f16\u8f91", None)) 78 | self.saveButton.setText(QCoreApplication.translate("LocalFold", u"\u4fdd\u5b58", None)) 79 | self.closeButton.setText(QCoreApplication.translate("LocalFold", u"\u53d6\u6d88", None)) 80 | # retranslateUi 81 | 82 | -------------------------------------------------------------------------------- /src/interface/ui_setting_item_theme.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'ui_setting_item_theme.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.2.1 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QRadioButton, QSizePolicy, QVBoxLayout, 19 | QWidget) 20 | 21 | class Ui_SettingItemTheme(object): 22 | def setupUi(self, SettingItemTheme): 23 | if not SettingItemTheme.objectName(): 24 | SettingItemTheme.setObjectName(u"SettingItemTheme") 25 | SettingItemTheme.resize(400, 121) 26 | self.verticalLayout = QVBoxLayout(SettingItemTheme) 27 | self.verticalLayout.setObjectName(u"verticalLayout") 28 | self.radioButton = QRadioButton(SettingItemTheme) 29 | self.radioButton.setObjectName(u"radioButton") 30 | 31 | self.verticalLayout.addWidget(self.radioButton) 32 | 33 | 34 | self.retranslateUi(SettingItemTheme) 35 | 36 | QMetaObject.connectSlotsByName(SettingItemTheme) 37 | # setupUi 38 | 39 | def retranslateUi(self, SettingItemTheme): 40 | SettingItemTheme.setWindowTitle(QCoreApplication.translate("SettingItemTheme", u"Form", None)) 41 | self.radioButton.setText(QCoreApplication.translate("SettingItemTheme", u"\u6d45\u8272", None)) 42 | # retranslateUi 43 | 44 | -------------------------------------------------------------------------------- /src/qt_error.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PySide6.QtCore import Qt 4 | from PySide6.QtWidgets import QStyle, QErrorMessage, QLabel, QCheckBox, QPushButton 5 | 6 | 7 | def showError(message, app): 8 | app.setQuitOnLastWindowClosed(True) 9 | # 设置内置错误图标 10 | app.setWindowIcon(app.style().standardIcon(QStyle.SP_MessageBoxCritical)) 11 | w = QErrorMessage() 12 | w.finished.connect(lambda _: app.quit) 13 | w.resize(600, 400) 14 | # 去掉右上角? 15 | w.setWindowFlags(w.windowFlags() & ~Qt.WindowContextHelpButtonHint) 16 | w.setWindowTitle(w.tr('Error')) 17 | # 隐藏图标、勾选框、按钮 18 | w.findChild(QLabel, '').setVisible(False) 19 | w.findChild(QCheckBox, '').setVisible(False) 20 | w.findChild(QPushButton, '').setVisible(False) 21 | w.showMessage(escape(message)) 22 | app.exec_() 23 | 24 | 25 | def showError2(message, app): 26 | # app.setQuitOnLastWindowClosed(True) 27 | # # 设置内置错误图标 28 | # app.setWindowIcon(app.style().standardIcon(QStyle.SP_MessageBoxCritical)) 29 | w = QErrorMessage() 30 | w.setWindowIcon(w.style().standardIcon(QStyle.SP_MessageBoxCritical)) 31 | # w.finished.connect(lambda _: app.quit) 32 | w.resize(600, 400) 33 | # 去掉右上角? 34 | w.setWindowFlags(w.windowFlags() & ~Qt.WindowContextHelpButtonHint) 35 | w.setWindowTitle(w.tr('Error')) 36 | # 隐藏图标、勾选框、按钮 37 | w.findChild(QLabel, '').setVisible(False) 38 | w.findChild(QCheckBox, '').setVisible(False) 39 | w.findChild(QPushButton, '').setVisible(False) 40 | w.showMessage(escape(message)) 41 | w.exec_() 42 | 43 | def escape(s): 44 | s = s.replace("&", "&") 45 | s = s.replace("<", "<") 46 | s = s.replace(">", ">") 47 | s = s.replace('"', """) 48 | s = s.replace('\'', "'") 49 | s = s.replace('\n', '
') 50 | s = s.replace(' ', ' ') 51 | return s -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | PySide6==6.2.4 2 | requests==2.26.0 3 | urllib3==1.25.11 4 | pillow==8.3.2 5 | waifu2x-vulkan==1.1.6 6 | bs4==0.0.1 7 | lxml==4.6.4 8 | Pysocks==1.7.1 9 | natsort==8.2.0 10 | # pywin32==302 -------------------------------------------------------------------------------- /src/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .user_handler import * -------------------------------------------------------------------------------- /src/server/res.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from config.setting import Setting 3 | from tools.tool import ToolUtil 4 | 5 | 6 | class BaseRes(object): 7 | def __init__(self, data, isParseRes) -> None: 8 | super().__init__() 9 | 10 | self.raw = data 11 | self.data = {} 12 | self.code = 0 13 | self.message = "" 14 | self.reqBak = None 15 | self.isParseRes = isParseRes 16 | if isParseRes: 17 | ToolUtil.ParseFromData(self, self.raw.text) 18 | 19 | def __str__(self): 20 | # if Setting.LogIndex.value == 0: 21 | # return "" 22 | # elif Setting.LogIndex.value == 1: 23 | # return "code:{}".format(self.code) 24 | # else: 25 | # data = self.GetText() 26 | return "code:{}".format(self.code) 27 | 28 | def GetText(self): 29 | if hasattr(self.raw, "text"): 30 | return self.raw.text 31 | return "" 32 | -------------------------------------------------------------------------------- /src/task/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/task/__init__.py -------------------------------------------------------------------------------- /src/task/task_http.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | from types import FunctionType 3 | 4 | from tools.log import Log 5 | from task.qt_task import QtTaskQObject, TaskBase, QtHttpTask 6 | 7 | 8 | class TaskHttp(TaskBase): 9 | 10 | def __init__(self): 11 | TaskBase.__init__(self) 12 | self.taskObj.taskBack.connect(self.HandlerTask) 13 | 14 | def AddHttpTask(self, req, callBack=None, backParam=None, cleanFlag=None): 15 | self.taskId += 1 16 | info = QtHttpTask(self.taskId) 17 | info.callBack = callBack 18 | info.backParam = backParam 19 | self.tasks[self.taskId] = info 20 | if cleanFlag: 21 | info.cleanFlag = cleanFlag 22 | taskIds = self.flagToIds.setdefault(cleanFlag, set()) 23 | taskIds.add(self.taskId) 24 | 25 | if isinstance(req, FunctionType): 26 | req(self.taskId) 27 | else: 28 | from server.server import Server 29 | Server().Send(req, backParam=self.taskId) 30 | return 31 | 32 | def HandlerTask(self, taskId, data): 33 | try: 34 | info = self.tasks.get(taskId) 35 | data = pickle.loads(data) 36 | if not info: 37 | Log.Warn("[Task] not find taskId:{}, {}".format(taskId, data)) 38 | return 39 | assert isinstance(info, QtHttpTask) 40 | if info.cleanFlag: 41 | taskIds = self.flagToIds.get(info.cleanFlag, set()) 42 | taskIds.discard(info.taskId) 43 | 44 | if info.backParam is None: 45 | info.callBack(data) 46 | else: 47 | info.callBack(data, info.backParam) 48 | del info.callBack 49 | del self.tasks[taskId] 50 | except Exception as es: 51 | Log.Error(es) -------------------------------------------------------------------------------- /src/task/task_qimage.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtGui import QImage 2 | from PySide6.QtCore import Qt 3 | 4 | from task.qt_task import TaskBase 5 | from tools.log import Log 6 | 7 | 8 | class QtQImageTask(object): 9 | def __init__(self, taskId): 10 | self.taskId = taskId 11 | self.callBack = None 12 | self.backParam = None 13 | self.cleanFlag = "" 14 | self.data = "" 15 | self.radio = 1 16 | self.toH = 0 17 | self.toW = 0 18 | self.model = 0 19 | 20 | 21 | class TaskQImage(TaskBase): 22 | 23 | def __init__(self): 24 | TaskBase.__init__(self) 25 | self.taskObj.imageBack.connect(self.HandlerTask) 26 | self.thread.start() 27 | 28 | def Run(self): 29 | while True: 30 | try: 31 | v = self._inQueue.get(True) 32 | if v == "": 33 | break 34 | taskId = v 35 | except Exception as es: 36 | continue 37 | self._inQueue.task_done() 38 | 39 | if taskId < 0: 40 | break 41 | 42 | q = QImage() 43 | try: 44 | info = self.tasks.get(taskId) 45 | if not info: 46 | continue 47 | 48 | if not info.data: 49 | return 50 | q.loadFromData(info.data) 51 | q.setDevicePixelRatio(info.radio) 52 | if info.toW > 0: 53 | newQ = q.scaled(info.toW * info.radio, info.toH * info.radio, Qt.KeepAspectRatio, Qt.SmoothTransformation) 54 | else: 55 | newQ = q 56 | 57 | except Exception as es: 58 | Log.Error(es) 59 | finally: 60 | self.taskObj.imageBack.emit(taskId, newQ) 61 | 62 | def AddQImageTask(self, data, radio, toW, toH, model, callBack=None, backParam=None, cleanFlag=None): 63 | self.taskId += 1 64 | info = QtQImageTask(self.taskId) 65 | info.callBack = callBack 66 | info.backParam = backParam 67 | info.data = data 68 | info.radio = radio 69 | info.toW = toW 70 | info.toH = toH 71 | info.model = model 72 | 73 | self.tasks[self.taskId] = info 74 | if cleanFlag: 75 | info.cleanFlag = cleanFlag 76 | taskIds = self.flagToIds.setdefault(cleanFlag, set()) 77 | taskIds.add(self.taskId) 78 | self._inQueue.put(self.taskId) 79 | return self.taskId 80 | 81 | def ClearQImageTaskById(self, taskId): 82 | if taskId in self.tasks: 83 | self.tasks.pop(taskId) 84 | 85 | def HandlerTask(self, taskId, newData): 86 | try: 87 | info = self.tasks.get(taskId) 88 | if not info: 89 | Log.Warn("[Task] not find taskId:{}".format(taskId)) 90 | return 91 | assert isinstance(info, QtQImageTask) 92 | if info.cleanFlag: 93 | taskIds = self.flagToIds.get(info.cleanFlag, set()) 94 | taskIds.discard(info.taskId) 95 | if info.callBack: 96 | if info.backParam is None: 97 | info.callBack(newData) 98 | else: 99 | info.callBack(newData, info.backParam) 100 | del info.callBack 101 | del self.tasks[taskId] 102 | except Exception as es: 103 | Log.Error(es) -------------------------------------------------------------------------------- /src/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/tools/__init__.py -------------------------------------------------------------------------------- /src/tools/singleton.py: -------------------------------------------------------------------------------- 1 | class Singleton(object): 2 | _objs = {} 3 | 4 | def __new__(cls, *args, **kwargs): 5 | cls_dict = cls._objs.get(cls) 6 | if cls_dict is not None: 7 | return cls_dict['obj'] 8 | 9 | obj = object.__new__(cls) 10 | cls._objs[cls] = {'obj': obj, 'init': False} 11 | setattr(cls, '__init__', cls.decorate_init(cls.__init__)) 12 | return obj 13 | 14 | @classmethod 15 | def decorate_init(cls, func): 16 | def init_wrap(*args, **kwargs): 17 | if not cls._objs[cls]['init']: 18 | func(*args, **kwargs) 19 | cls._objs[cls]['init'] = True 20 | return 21 | 22 | return init_wrap 23 | -------------------------------------------------------------------------------- /src/tools/status.py: -------------------------------------------------------------------------------- 1 | from tools.str import Str 2 | 3 | 4 | class Status(object): 5 | Ok = Str.Ok 6 | Load = Str.Load 7 | Error = Str.Error 8 | WaitLoad = Str.WaitLoad 9 | NetError = Str.NetError 10 | UserError = Str.UserError 11 | RegisterError = Str.RegisterError 12 | UnKnowError = Str.UnKnowError 13 | NotFoundBook = Str.NotFoundBook 14 | ParseError = Str.ParseError 15 | NeedGoogle = Str.NeedGoogle 16 | TimeOut = Str.TimeOut 17 | SSLErr = Str.SSLErr 18 | ResetErr = Str.ResetErr 19 | ConnectErr = Str.ConnectErr 20 | ProxyError = Str.ProxyError 21 | DownloadFail = Str.DownloadFail 22 | OfflineModel = Str.OfflineModel 23 | 24 | UnderReviewBook = Str.UnderReviewBook 25 | SaveError = Str.SaveError 26 | AddError = Str.AddError 27 | FileError = Str.FileError 28 | PathError = Str.PathError 29 | FileFormatError = Str.FileFormatError 30 | -------------------------------------------------------------------------------- /src/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/util/__init__.py -------------------------------------------------------------------------------- /src/util/status.py: -------------------------------------------------------------------------------- 1 | from tools.str import Str 2 | 3 | 4 | class Status(object): 5 | Ok = Str.Ok 6 | Load = Str.Load 7 | Error = Str.Error 8 | WaitLoad = Str.WaitLoad 9 | NetError = Str.NetError 10 | UserError = Str.UserError 11 | RegisterError = Str.RegisterError 12 | UnKnowError = Str.UnKnowError 13 | NotFoundBook = Str.NotFoundBook 14 | ParseError = Str.ParseError 15 | NeedGoogle = Str.NeedGoogle 16 | 17 | -------------------------------------------------------------------------------- /src/util/str.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from PySide6.QtCore import QObject 4 | 5 | 6 | class QtStrObj(QObject): 7 | def __init__(self): 8 | QObject.__init__(self) 9 | 10 | 11 | class Str: 12 | obj = QtStrObj() 13 | strDict = dict() 14 | 15 | # Enum 16 | 17 | Ok = 1001 # "成功" 18 | Load = 1002 # "加载" 19 | Error = 1003 # "错误" 20 | WaitLoad = 1004 # "等待" 21 | NetError = 1005 # "网络错误,请检查代理设置" 22 | UserError = 1006 # "用户名密码错误" 23 | RegisterError = 1007 # "注册失败" 24 | UnKnowError = 1008 # "未知错误," 25 | NotFoundBook = 1009 # "未找到书籍" 26 | ParseError = 1010 # "解析出错了" 27 | NeedGoogle = 1011 # "需要谷歌验证" 28 | 29 | Success = 2001 # "下载完成" 30 | Reading = 2002 # "获取信息" 31 | ReadingEps = 2003 # "获取分页" 32 | ReadingPicture = 2004 # "获取下载地址" 33 | DownloadCover = 2005 # "正在下载封面" 34 | Downloading = 2006 # "正在下载" 35 | Waiting = 2007 # "等待中" 36 | Pause = 2008 # "暂停" 37 | DownError = 2009 # "出错了" 38 | NotFound = 2010 # "原始文件不存在" 39 | Converting = 2011 # "转换中" 40 | ConvertSuccess = 2012 # "转换成功" 41 | 42 | LoadingPicture = 1 # "图片加载中..." 43 | LoadingFail = 2 # "图片加载失败" 44 | LoginCookie = 3 # "使用Cookie登录" 45 | LoginUser = 4 # "使用账号登录" 46 | NotSpace = 5 # "不能为空" 47 | LoginFail = 6 # "登录失败" 48 | 49 | @classmethod 50 | def reload(cls): 51 | cls.strDict[cls.Ok] = cls.obj.tr("成功") 52 | cls.strDict[cls.Load] = cls.obj.tr("加载") 53 | cls.strDict[cls.Error] = cls.obj.tr("错误") 54 | cls.strDict[cls.WaitLoad] = cls.obj.tr("等待") 55 | cls.strDict[cls.NetError] = cls.obj.tr("网络错误,请检查代理设置") 56 | cls.strDict[cls.UserError] = cls.obj.tr("用户名密码错误") 57 | cls.strDict[cls.RegisterError] = cls.obj.tr("注册失败") 58 | cls.strDict[cls.UnKnowError] = cls.obj.tr("未知错误") 59 | cls.strDict[cls.NotFoundBook] = cls.obj.tr("未找到书籍") 60 | cls.strDict[cls.ParseError] = cls.obj.tr("解析出错了") 61 | cls.strDict[cls.NeedGoogle] = cls.obj.tr("需要谷歌验证") 62 | 63 | cls.strDict[cls.LoadingPicture] = cls.obj.tr("图片加载中...") 64 | cls.strDict[cls.LoadingFail] = cls.obj.tr("图片加载失败") 65 | cls.strDict[cls.LoginCookie] = cls.obj.tr("使用Cookie登录") 66 | cls.strDict[cls.LoginUser] = cls.obj.tr("使用账号登录") 67 | cls.strDict[cls.NotSpace] = cls.obj.tr("不能为空") 68 | cls.strDict[cls.LoginFail] = cls.obj.tr("登录失败") 69 | cls.strDict[cls.Success] = cls.obj.tr("下载完成") 70 | cls.strDict[cls.Reading] = cls.obj.tr("获取信息") 71 | cls.strDict[cls.ReadingEps] = cls.obj.tr("获取分页") 72 | cls.strDict[cls.ReadingPicture] = cls.obj.tr("获取下载地址") 73 | cls.strDict[cls.DownloadCover] = cls.obj.tr("正在下载封面") 74 | cls.strDict[cls.Downloading] = cls.obj.tr("正在下载") 75 | cls.strDict[cls.Waiting] = cls.obj.tr("等待中") 76 | cls.strDict[cls.Pause] = cls.obj.tr("暂停") 77 | cls.strDict[cls.DownError] = cls.obj.tr("出错了") 78 | cls.strDict[cls.NotFound] = cls.obj.tr("原始文件不存在") 79 | cls.strDict[cls.Converting] = cls.obj.tr("转换中") 80 | cls.strDict[cls.ConvertSuccess] = cls.obj.tr("转换成功") 81 | 82 | @classmethod 83 | def GetStr(cls, enumType): 84 | return cls.strDict.get(enumType) -------------------------------------------------------------------------------- /src/view/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/__init__.py -------------------------------------------------------------------------------- /src/view/comment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/comment/__init__.py -------------------------------------------------------------------------------- /src/view/comment/comment_view.py: -------------------------------------------------------------------------------- 1 | from component.widget.comment_widget import CommentWidget 2 | 3 | 4 | class CommentView(CommentWidget): 5 | def __init__(self, parent=None): 6 | CommentWidget.__init__(self, parent) -------------------------------------------------------------------------------- /src/view/download/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/download/__init__.py -------------------------------------------------------------------------------- /src/view/download/download_dir_view.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PySide6.QtCore import Signal 4 | from PySide6.QtWidgets import QFileDialog 5 | 6 | from component.dialog.base_mask_dialog import BaseMaskDialog 7 | from config import config 8 | from config.setting import Setting 9 | from interface.ui_download_dir import Ui_DownloadDir 10 | from qt_owner import QtOwner 11 | from task.qt_task import QtTaskBase 12 | from tools.str import Str 13 | 14 | 15 | class DownloadDirView(BaseMaskDialog, Ui_DownloadDir, QtTaskBase): 16 | CloseLogin = Signal() 17 | 18 | def __init__(self, parent=None): 19 | BaseMaskDialog.__init__(self, parent) 20 | Ui_DownloadDir.__init__(self) 21 | QtTaskBase.__init__(self) 22 | self.widget.adjustSize() 23 | self.setupUi(self.widget) 24 | self.selectDir.clicked.connect(self.SelectSavePath) 25 | self.saveDir.clicked.connect(self.SavePath) 26 | 27 | def SelectSavePath(self): 28 | url = QFileDialog.getExistingDirectory(self, Str.GetStr(Str.SelectFold)) 29 | if url: 30 | self.lineEdit.setText(url) 31 | self.downloadDir.setText(os.path.join(url, config.SavePathDir)) 32 | # self.chatDir.setText(os.path.join(url, config.ChatSavePath)) 33 | self.cacheDir.setText(os.path.join(url, config.CachePathDir)) 34 | self.waifu2xDir.setText(os.path.join(os.path.join(url, config.CachePathDir), config.Waifu2xPath)) 35 | 36 | def SavePath(self): 37 | path = self.lineEdit.text() 38 | if not path: 39 | QtOwner().ShowMsg(Str.GetStr(Str.SetDir)) 40 | return 41 | Setting.SavePath.SetValue(path) 42 | self.close() 43 | -------------------------------------------------------------------------------- /src/view/help/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/help/__init__.py -------------------------------------------------------------------------------- /src/view/help/help_log_widget.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import random 3 | import re 4 | import sys 5 | from io import TextIOWrapper 6 | 7 | from PySide6.QtCore import QUrl, Signal, QTimer 8 | from PySide6.QtGui import QDesktopServices, Qt, QTextCursor, QColor 9 | from PySide6.QtWidgets import QWidget, QMessageBox, QTextEdit, QStyle 10 | from interface.ui_help_log_widget import Ui_HelpLogWidget 11 | from task.qt_task import QtTaskBase 12 | from tools.log import Log 13 | from tools.tool import ToolUtil 14 | 15 | 16 | class HelpLogWidget(QWidget, Ui_HelpLogWidget): 17 | logMsg = Signal(int, str) 18 | 19 | def __init__(self, parent=None): 20 | QWidget.__init__(self, parent) 21 | Ui_HelpLogWidget.__init__(self) 22 | self.setupUi(self) 23 | self.buttonGroup.setId(self.warnButton, logging.WARN) 24 | self.buttonGroup.setId(self.infoButton, logging.INFO) 25 | self.buttonGroup.setId(self.debugButton, logging.DEBUG) 26 | self.textBrowser.setReadOnly(True) 27 | self.textBrowser.setOpenExternalLinks(True) 28 | self.logMsg.connect(self.AddLog) 29 | Log.InstallFilter(self) 30 | self.defaultColor = None 31 | self.runEnable = False 32 | self._SetRunEnable(self.runEnable) 33 | self.toolButton.clicked.connect(self.SwitchRunEnable) 34 | self.runButton.clicked.connect(self.RunCode) 35 | self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation)) 36 | 37 | def write(self, levelNo, info): 38 | self.logMsg.emit(levelNo, info) 39 | 40 | def AddLog(self, logLevel, logData): 41 | if self.isHidden(): 42 | return 43 | if logLevel < self.buttonGroup.checkedId(): 44 | return 45 | if logLevel == logging.ERROR: 46 | self._AddLog(logData, "#ff0000") 47 | else: 48 | self._AddLog(logData) 49 | return 50 | 51 | def _AddLog(self, logData, color=None): 52 | if not self.defaultColor: 53 | self.defaultColor = QColor(255, 64, 129) 54 | self.textBrowser.moveCursor(QTextCursor.Start) 55 | if not color: 56 | color = "#ff4081" 57 | # logData = "{}".format(logData) 58 | self.textBrowser.setTextColor(color) 59 | else: 60 | self.textBrowser.setTextColor(self.defaultColor) 61 | 62 | pattern = re.compile(r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+') 63 | urls = re.findall(pattern, logData) 64 | if urls: 65 | url = urls[0] 66 | data = logData.split(url, 2) 67 | if len(data) == 2: 68 | logData = ToolUtil.Escape(data[0]) + "{}".format(url, url) + ToolUtil.Escape(data[1]) 69 | self.textBrowser.insertHtml("

{}

".format(logData)) 70 | return 71 | self.textBrowser.insertHtml("

{}

".format(color, ToolUtil.Escape(logData))) 72 | # self.textEdit.setTextColor(self.defaultColor) 73 | 74 | def SwitchRunEnable(self): 75 | self.runEnable = not self.runEnable 76 | self._SetRunEnable(self.runEnable) 77 | 78 | def _SetRunEnable(self, isEnable): 79 | if isEnable: 80 | self.toolButton.setArrowType(Qt.UpArrow) 81 | self.textEdit.setVisible(True) 82 | self.runButton.setVisible(True) 83 | else: 84 | self.toolButton.setArrowType(Qt.DownArrow) 85 | self.textEdit.setVisible(False) 86 | self.runButton.setVisible(False) 87 | 88 | def RunCode(self): 89 | text = self.textEdit.toPlainText() 90 | try: 91 | for v in text.split("\n"): 92 | Log.Info("Run code: {}".format(v)) 93 | exec(v) 94 | self.textEdit.setText("") 95 | except Exception as es: 96 | Log.Error(es) -------------------------------------------------------------------------------- /src/view/info/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/info/__init__.py -------------------------------------------------------------------------------- /src/view/main/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/main/__init__.py -------------------------------------------------------------------------------- /src/view/read/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/read/__init__.py -------------------------------------------------------------------------------- /src/view/read/read_pool.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtGui import QPixmap 2 | from PySide6.QtWidgets import QGraphicsProxyWidget, QGraphicsPixmapItem, QLabel 3 | 4 | from tools.singleton import Singleton 5 | from view.read.read_qgraphics_proxy_widget import ReadQGraphicsProxyWidget 6 | 7 | 8 | class QtReadImgPoolManager(Singleton): 9 | def __init__(self): 10 | self.proxyNum = 1000 # QGraphicsProxyWidget 11 | self.pixMapNum = 3 # QGraphicsPixmapItem 12 | 13 | self.proxyItem = [] 14 | self.pixMapItem = [] 15 | 16 | def GetProxyItem(self): 17 | if not self.proxyItem: 18 | a = QGraphicsProxyWidget() 19 | a.setWidget(QLabel()) 20 | return a 21 | return self.proxyItem.pop() 22 | 23 | def AddProxyItem(self, item): 24 | assert isinstance(item, QGraphicsProxyWidget) 25 | # if item.widget(): 26 | # item.widget().setParent(None) 27 | # item.setWidget(None) 28 | item.setPos(0, 0) 29 | self.proxyItem.append(item) 30 | 31 | def GetPixMapItem(self): 32 | if not self.pixMapItem: 33 | return QGraphicsPixmapItem() 34 | return self.pixMapItem.pop() 35 | 36 | def AddPixMapItem(self, item): 37 | assert isinstance(item, QGraphicsPixmapItem) 38 | item.setPixmap(QPixmap()) 39 | 40 | # item.widget().clear() 41 | item.setPos(0, 0) 42 | self.pixMapItem.append(item) 43 | -------------------------------------------------------------------------------- /src/view/read/read_proxy.py: -------------------------------------------------------------------------------- 1 | from qt_owner import QtOwner 2 | from task.qt_task import QtTaskBase 3 | from tools.status import Status 4 | from tools.str import Str 5 | 6 | 7 | class ReadProxy(QtTaskBase): 8 | def __init__(self): 9 | QtTaskBase.__init__(self) 10 | self.bookId = 0 11 | self.epsId = 0 12 | self.maxPic = 0 13 | self.curIndex = 0 14 | self.pageIndex = 0 15 | self.epsName = "" 16 | self.initCallBack = None 17 | return 18 | 19 | def Init(self): 20 | self.AddDownloadBook(self.bookId, 0, statusBack=self.InitBack, backParam=0, isInit=True) 21 | return 22 | 23 | def InitBack(self, raw): 24 | st = raw["st"] 25 | if st == Status.Error: 26 | QtOwner().ShowError(Str.GetStr(st)) 27 | return 28 | maxPic = raw.get("maxPic") 29 | if not maxPic or self.maxPic > 0: 30 | return 31 | self.maxPic = maxPic 32 | title = raw.get("title", "") 33 | self.epsName = title 34 | # info = BookMgr().GetBook(self.bookId) 35 | if 0 < self.pageIndex < self.maxPic: 36 | self.curIndex = self.pageIndex 37 | QtOwner().ShowMsg(Str.GetStr(Str.ContinueRead) + str(self.pageIndex + 1) + Str.GetStr(Str.Page)) 38 | self.AddHistory() 39 | self.initCallBack() 40 | self.scrollArea.InitAllQLabel(self.maxPic, self.curIndex) 41 | self.qtTool.UpdateSlider() 42 | self.CheckLoadPicture() 43 | self.qtTool.InitSlider(self.maxPic) 44 | return 45 | 46 | def AddHistory(self): 47 | bookName = QtOwner().bookInfoView.bookName 48 | url = QtOwner().bookInfoView.url 49 | path = QtOwner().bookInfoView.path 50 | QtOwner().historyView.AddHistory(self.bookId, bookName, self.epsId, self.curIndex, url, path) 51 | return -------------------------------------------------------------------------------- /src/view/search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/search/__init__.py -------------------------------------------------------------------------------- /src/view/search/rank_view.py: -------------------------------------------------------------------------------- 1 | import json 2 | from functools import partial 3 | 4 | from PySide6 import QtWidgets 5 | 6 | from config import config 7 | from interface.ui_rank import Ui_Rank 8 | from qt_owner import QtOwner 9 | from server import req, Log, Status, ToolUtil 10 | from task.qt_task import QtTaskBase 11 | from tools.str import Str 12 | 13 | 14 | class RankView(QtWidgets.QWidget, Ui_Rank, QtTaskBase): 15 | def __init__(self): 16 | super(self.__class__, self).__init__() 17 | Ui_Rank.__init__(self) 18 | QtTaskBase.__init__(self) 19 | 20 | self.isInitKind = False 21 | self.setupUi(self) 22 | 23 | self.isInit = False 24 | self.tabWidget.setCurrentIndex(0) 25 | self.tabWidget.currentChanged.connect(self.SwitchPage) 26 | self.indexDict = { 27 | 0: "15", 28 | 1: "13", 29 | 2: "12", 30 | 3: "11" 31 | } 32 | self.indexToList = { 33 | 0: self.dayBookList, 34 | 1: self.monthBookList, 35 | 2: self.yearBookList, 36 | 3: self.allBookList 37 | } 38 | self.dayBookList.LoadCallBack = partial(self.LoadNextPage, 0) 39 | self.monthBookList.LoadCallBack = partial(self.LoadNextPage, 1) 40 | self.yearBookList.LoadCallBack = partial(self.LoadNextPage, 2) 41 | self.allBookList.LoadCallBack = partial(self.LoadNextPage, 3) 42 | 43 | def SwitchCurrent(self, **kwargs): 44 | refresh = kwargs.get("refresh") 45 | self.isInit = True 46 | if refresh: 47 | self.dayBookList.clear() 48 | self.monthBookList.clear() 49 | self.yearBookList.clear() 50 | self.allBookList.clear() 51 | self.Init() 52 | pass 53 | 54 | def SwitchPage(self, index): 55 | if not self.isInit: 56 | return 57 | bookList = self.indexToList.get(index) 58 | if bookList.count() > 0: 59 | return 60 | QtOwner().ShowLoading() 61 | bookList.UpdatePage(1, 200) 62 | self.AddHttpTask(req.GetRankInfoReq(self.indexDict.get(index)), self.InitBack, backParam=(index, 1)) 63 | return 64 | 65 | def LoadNextPage(self, index): 66 | bookList = self.indexToList.get(index) 67 | if bookList.page >= bookList.pages: 68 | return 69 | QtOwner().ShowLoading() 70 | self.AddHttpTask(req.GetRankInfoReq(self.indexDict.get(index)), self.InitBack, backParam=(index, bookList.page+1)) 71 | return 72 | 73 | def Init(self): 74 | if self.tabWidget.currentIndex() == 0: 75 | self.SwitchPage(0) 76 | else: 77 | self.tabWidget.setCurrentIndex(0) 78 | 79 | def InitBack(self, raw, backParam): 80 | index, page = backParam 81 | QtOwner().CloseLoading() 82 | bookList = self.indexToList.get(index) 83 | bookList.UpdatePage(page, 200) 84 | bookList.UpdateState() 85 | try: 86 | st = raw["st"] 87 | if st == Status.Ok: 88 | data = raw 89 | for info in data.get("bookList"): 90 | _id = info.baseInfo.id 91 | title = info.baseInfo.title 92 | url = info.baseInfo.imgUrl 93 | token = info.baseInfo.token 94 | category = ToolUtil.GetCategoryName(info.baseInfo.category) 95 | path = "{}/{}_{}_cover".format(config.CurSite, _id, info.baseInfo.token) 96 | bookList.AddBookItem(_id, title, Str.GetStr(Str.Classify) + ":" + category, url, path, "", token=token) 97 | else: 98 | QtOwner().ShowError(Str.GetStr(st)) 99 | except Exception as es: 100 | Log.Error(es) 101 | 102 | -------------------------------------------------------------------------------- /src/view/setting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/setting/__init__.py -------------------------------------------------------------------------------- /src/view/tool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/tool/__init__.py -------------------------------------------------------------------------------- /src/view/tool/doh_dns_view.py: -------------------------------------------------------------------------------- 1 | from PySide6.QtWidgets import QTableWidgetItem 2 | 3 | from component.dialog.base_mask_dialog import BaseMaskDialog 4 | from interface.ui_doh_dns import Ui_DohDns 5 | from tools.qt_domain import QtDomainMgr 6 | 7 | 8 | class DohDnsView(BaseMaskDialog, Ui_DohDns): 9 | def __init__(self, param=None): 10 | super(self.__class__, self).__init__(param) 11 | Ui_DohDns.__init__(self) 12 | self.setupUi(self.widget) 13 | self.pushButton.clicked.connect(self.close) 14 | self.tableWidget.setMinimumWidth(600) 15 | self.LoadDns() 16 | 17 | def show(self): 18 | return super(self.__class__, self).show() 19 | 20 | def LoadDns(self): 21 | for row in range(0, self.tableWidget.rowCount()): 22 | row = self.tableWidget.rowCount() 23 | self.tableWidget.removeRow(row-1) 24 | 25 | row = 0 26 | for host, ip in QtDomainMgr().cache_dns.items(): 27 | self.tableWidget.insertRow(row) 28 | self.tableWidget.setItem(row, 0, QTableWidgetItem(host)) 29 | self.tableWidget.setItem(row, 1, QTableWidgetItem(ip)) 30 | row += 1 31 | 32 | for host in QtDomainMgr().fail_dns: 33 | if host in QtDomainMgr().cache_dns: 34 | continue 35 | self.tableWidget.insertRow(row) 36 | self.tableWidget.setItem(row, 0, QTableWidgetItem(host)) 37 | self.tableWidget.setItem(row, 1, QTableWidgetItem("Fail")) 38 | row += 1 39 | return 40 | -------------------------------------------------------------------------------- /src/view/tool/local_read_eps_view.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from this import d 4 | 5 | from PySide6.QtCore import Signal, QUrl 6 | from PySide6.QtGui import QAction, Qt, QDesktopServices 7 | from PySide6.QtWidgets import QWidget, QMenu, QFileDialog 8 | from natsort import natsorted 9 | 10 | from interface.ui_index import Ui_Index 11 | from interface.ui_local import Ui_Local 12 | from interface.ui_local_eps import Ui_LocalEps 13 | from qt_owner import QtOwner 14 | from task.qt_task import QtTaskBase 15 | from task.task_local import LocalData 16 | 17 | 18 | class LocalReadEpsView(QWidget, Ui_LocalEps, QtTaskBase): 19 | ReloadHistory = Signal(int) 20 | 21 | def __init__(self, parent=None): 22 | QWidget.__init__(self, parent) 23 | Ui_Index.__init__(self) 24 | QtTaskBase.__init__(self) 25 | self.setupUi(self) 26 | self.isInit = False 27 | self.bookId = "" 28 | self.cacheBook = None 29 | self.bookIdToIndex = {} 30 | self.bookList.isLocal = True 31 | self.bookList.isLocalEps = True 32 | self.bookList.LoadingPicture = self.LoadingPicture 33 | self.bookList.ReDownloadPicture = self.LoadingPicture 34 | self.showWaifu2x.clicked.connect(self.Init) 35 | 36 | @property 37 | def localReadView(self): 38 | return QtOwner().localReadView 39 | 40 | def Init(self): 41 | book = self.localReadView.allBookInfos.get(self.bookId) 42 | if not book: 43 | return 44 | self.name.setText(book.title) 45 | self.bookIdToIndex.clear() 46 | self.cacheBook = book 47 | assert isinstance(book, LocalData) 48 | self.bookList.clear() 49 | for i, v in enumerate(book.eps): 50 | assert isinstance(v, LocalData) 51 | self.bookIdToIndex[v.id] = i 52 | categroup = [] 53 | if v.isWaifu2x: 54 | categroup.append("waifu2x") 55 | else: 56 | if self.showWaifu2x.isChecked(): 57 | continue 58 | 59 | if v.isZipFile: 60 | categroup.append("zip") 61 | self.bookList.AddBookByLocal(v, "".join(categroup)) 62 | pass 63 | 64 | def SwitchCurrent(self, **kwargs): 65 | bookId = kwargs.get("bookId") 66 | if bookId == self.bookId: 67 | return 68 | self.bookId = bookId 69 | self.Init() 70 | return 71 | 72 | def LoadingPicture(self, index): 73 | if isinstance(index, int): 74 | item = self.bookList.item(index) 75 | widget = self.bookList.itemWidget(item) 76 | else: 77 | widget = self.bookList.indexWidget(index) 78 | 79 | bookId = widget.id 80 | if bookId not in self.bookIdToIndex: 81 | return 82 | i = self.bookIdToIndex.get(bookId) 83 | v = self.cacheBook.eps[i] 84 | self.AddLocalTaskLoadPicture(v, -1, index, self.bookList.LoadingPictureComplete) 85 | 86 | def OpenLocalBook(self, bookId): 87 | if bookId not in self.bookIdToIndex: 88 | return 89 | newV = LocalData() 90 | newV.id = self.cacheBook.id 91 | newV.CopyData(self.cacheBook) 92 | newV.eps = [] 93 | eps = 0 94 | i = 0 95 | for v in self.cacheBook.eps: 96 | assert isinstance(v, LocalData) 97 | if v.id == bookId: 98 | eps = i 99 | if not v.isWaifu2x: 100 | if self.showWaifu2x.isChecked(): 101 | continue 102 | i += 1 103 | newV.eps.append(v) 104 | QtOwner().OpenLocalReadView(newV, eps) 105 | -------------------------------------------------------------------------------- /src/view/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/src/view/user/__init__.py -------------------------------------------------------------------------------- /src/view/user/favorite_info_view.py: -------------------------------------------------------------------------------- 1 | from PySide6 import QtWidgets, QtCore 2 | 3 | from component.dialog.base_mask_dialog import BaseMaskDialog 4 | from interface.ui_favorite_info import Ui_FavoriteInfo 5 | from qt_owner import QtOwner 6 | from server import req, Status 7 | from task.qt_task import QtTaskBase 8 | 9 | from tools.str import Str 10 | 11 | 12 | class FavoriteInfoView(BaseMaskDialog, Ui_FavoriteInfo, QtTaskBase): 13 | def __init__(self, parent=None): 14 | BaseMaskDialog.__init__(self, parent) 15 | Ui_FavoriteInfo.__init__(self) 16 | QtTaskBase.__init__(self) 17 | 18 | self.widget.SaveFavorite = self.SaveFavorite 19 | self.setupUi(self.widget) 20 | # self.closeButton.clicked.connect(self.close) 21 | self.bookId = "" 22 | self.isUpdate = True 23 | self.pushButton_2.clicked.connect(self.close) 24 | 25 | def OpenFavorite(self, bookId, bookName): 26 | QtOwner().ShowLoading() 27 | self.AddHttpTask(req.AddFavoritesReq(bookId), self.GetFavoriteBack, (bookId, bookName)) 28 | 29 | def GetFavoriteBack(self, data, v): 30 | QtOwner().CloseLoading() 31 | bookId, bookName = v 32 | st = data["st"] 33 | if st == Status.Ok: 34 | note = data["note"] 35 | favorites = data["favorites"] 36 | isUpdate = data["update"] 37 | self.lineEdit.setText(note) 38 | self.bookId = bookId 39 | self.nameLabel.setText(bookName) 40 | self.isUpdate = isUpdate 41 | if not self.isUpdate: 42 | self.pushButton.setText(Str.GetStr(Str.Save)) 43 | else: 44 | self.pushButton.setText(Str.GetStr(Str.Change)) 45 | 46 | for k, v in favorites.items(): 47 | if v: 48 | self.comboBox.setCurrentIndex(int(k)) 49 | 50 | pass 51 | else: 52 | QtOwner().ShowError(Str.GetStr(st)) 53 | self.close() 54 | 55 | def SaveFavorite(self): 56 | if not self.bookId: 57 | return 58 | QtOwner().ShowLoading() 59 | self.AddHttpTask(req.AddFavorites2Req(self.bookId, self.comboBox.currentIndex(), self.lineEdit.text(), self.isUpdate), self.SaveFavoriteBack) 60 | 61 | def SaveFavoriteBack(self, data): 62 | QtOwner().CloseLoading() 63 | st = data["st"] 64 | self.close() 65 | if st != Status.Ok: 66 | QtOwner().ShowError(Str.GetStr(st)) 67 | else: 68 | QtOwner().ShowMsg(Str.GetStr(Str.SaveSuc)) 69 | -------------------------------------------------------------------------------- /src/view/user/login_host_widget.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | from copy import deepcopy 3 | 4 | from PySide6 import QtWidgets 5 | from PySide6.QtCore import QUrl 6 | from PySide6.QtGui import QDesktopServices 7 | 8 | from config import config 9 | from config.setting import Setting 10 | from interface.ui_host import Ui_LoginHostWidget 11 | from qt_owner import QtOwner 12 | from server import req, Log 13 | from server.server import Server 14 | from task.qt_task import QtTaskBase 15 | from tools.str import Str 16 | from urllib3.util.ssl_ import is_ipaddress 17 | 18 | 19 | class LoginHostWidget(QtWidgets.QWidget, Ui_LoginHostWidget, QtTaskBase): 20 | def __init__(self): 21 | super(self.__class__, self).__init__() 22 | Ui_LoginHostWidget.__init__(self) 23 | QtTaskBase.__init__(self) 24 | self.setupUi(self) 25 | self.LoadConfig() 26 | 27 | def LoadConfig(self): 28 | config.Hosts = {} 29 | for v in Setting.OwnerHosts.value.split("\n"): 30 | try: 31 | v = v.strip("\r") 32 | if not v: 33 | continue 34 | if ":" in v: 35 | data= v.split(":") 36 | else: 37 | data= v.split(":") 38 | if len(data) < 2: 39 | Log.Warn("pass host error, {}".format(v)) 40 | else: 41 | domain = data[0] 42 | ip = data[1] 43 | if is_ipaddress(ip): 44 | config.Hosts[domain] = ip 45 | else: 46 | Log.Warn("pass host error2, {}".format(v)) 47 | except Exception as es: 48 | Log.Error(es) 49 | Log.Info("load hosts data, {}".format(config.Hosts)) 50 | 51 | def Init(self): 52 | self.LoadConfig() 53 | self.plainTextEdit.setPlainText(Setting.OwnerHosts.value) 54 | pass 55 | 56 | def ClickButton(self): 57 | Setting.OwnerHosts.SetValue(self.plainTextEdit.toPlainText()) 58 | self.LoadConfig() 59 | QtOwner().ShowMsg(Str.GetStr(Str.SaveSuc)) 60 | pass 61 | -------------------------------------------------------------------------------- /src/view/user/login_view.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | from PySide6.QtCore import Signal, QTimer 4 | 5 | from component.dialog.base_mask_dialog import BaseMaskDialog 6 | from config.setting import Setting 7 | from interface.ui_login import Ui_Login 8 | from qt_owner import QtOwner 9 | from task.qt_task import QtTaskBase 10 | from tools.str import Str 11 | from view.user.login_host_widget import LoginHostWidget 12 | 13 | 14 | class LoginView(BaseMaskDialog, Ui_Login, QtTaskBase): 15 | CloseLogin = Signal() 16 | 17 | def __init__(self, parent=None, isAutoLogin=0): 18 | BaseMaskDialog.__init__(self, parent) 19 | Ui_Login.__init__(self) 20 | QtTaskBase.__init__(self) 21 | self.widget.adjustSize() 22 | self.setupUi(self.widget) 23 | self.tabWidget.currentChanged.connect(self._SwichWidget) 24 | self.loginButton.clicked.connect(self._ClickButton) 25 | 26 | self.loginWidget.passHash.setText(Setting.IpbPassHash.value) 27 | self.loginWidget.memberId.setText(Setting.IpbMemberId.value) 28 | self.loginWidget.igneous.setText(Setting.Igneous.value) 29 | 30 | self.closeButton.clicked.connect(self.close) 31 | self.timer = QTimer() 32 | self.timer.setInterval(1000) 33 | self.timer.timeout.connect(self._AutoLogin) 34 | if isAutoLogin: 35 | self.timer.start() 36 | 37 | @property 38 | def loginWidget(self): 39 | return self.tab 40 | 41 | @property 42 | def loginProxyWidget(self): 43 | return self.tab_3 44 | 45 | @property 46 | def loginHostWidget(self): 47 | return self.tab_2 48 | 49 | def closeEvent(self, arg__1) -> None: 50 | self.timer.stop() 51 | return BaseMaskDialog.closeEvent(self, arg__1) 52 | 53 | def _AutoLogin(self): 54 | self.timer.stop() 55 | self.loginWidget.ClickButton() 56 | return 57 | 58 | def _SwichWidget(self, index): 59 | # self.tabWidget.widget(index).adjustSize() 60 | # print(self.tabWidget.widget(index).size()) 61 | # self.tabWidget.resize(self.tabWidget.widget(index).size()) 62 | if self.tabWidget.widget(index) == self.loginWidget: 63 | self.loginButton.setText(Str.GetStr(Str.Login)) 64 | elif self.tabWidget.widget(index) == self.loginProxyWidget: 65 | self.loginButton.setText(Str.GetStr(Str.Save)) 66 | elif self.tabWidget.widget(index) == self.loginHostWidget: 67 | self.loginButton.setText(Str.GetStr(Str.Save)) 68 | self.tabWidget.widget(index).Init() 69 | 70 | def _ClickButton(self): 71 | index = self.tabWidget.currentIndex() 72 | self.tabWidget.widget(index).ClickButton() 73 | if self.tabWidget.widget(index) == self.loginHostWidget: 74 | self.loginProxyWidget.UpdateServer() 75 | 76 | # def event(self, event) -> bool: 77 | # return BaseMaskDialog.event(event) -------------------------------------------------------------------------------- /translate/ui_en.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tonquer/ehentai-qt/3763dbb8da5589ebfeaa3a2aff0004d01614076d/translate/ui_en.qm -------------------------------------------------------------------------------- /ui/component/ui_download_dir.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DownloadDir 4 | 5 | 6 | 7 | 0 8 | 0 9 | 401 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | Qt::Vertical 21 | 22 | 23 | 24 | 20 25 | 40 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 你必须设置下载与缓存的路径才能使用 34 | 35 | 36 | 37 | 38 | 39 | 40 | 目录 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 300 51 | 0 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ... 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 下载: 92 | 93 | 94 | 95 | 96 | 97 | 98 | 缓存: 99 | 100 | 101 | 102 | 103 | 104 | 105 | waifu2x缓存: 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 保存 115 | 116 | 117 | 118 | 119 | 120 | 121 | Qt::Vertical 122 | 123 | 124 | 125 | 20 126 | 40 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /ui/component/ui_help_log_widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | HelpLogWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | 控制台 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Warn 23 | 24 | 25 | buttonGroup 26 | 27 | 28 | 29 | 30 | 31 | 32 | Info 33 | 34 | 35 | true 36 | 37 | 38 | buttonGroup 39 | 40 | 41 | 42 | 43 | 44 | 45 | Debug 46 | 47 | 48 | buttonGroup 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Qt::Horizontal 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Qt::DownArrow 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 运行代码 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ui/component/ui_host.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LoginHostWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 272 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 自定义IP:请使用域名:IP的格式 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 40 34 | 20 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /ui/component/ui_line_edit_help_widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LineEditHelp 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 440 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 联想词 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 120 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ui/component/ui_local_fold.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LocalFold 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 分类 23 | 24 | 25 | 26 | 27 | 28 | 29 | Qt::Horizontal 30 | 31 | 32 | 33 | 40 34 | 20 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 编辑 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 保存 57 | 58 | 59 | 60 | 61 | 62 | 63 | 取消 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /ui/ui_category.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Category 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | 分类 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | CategoryListWidget 26 | QListWidget 27 |
component.list.category_list_widget.h
28 |
29 |
30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /ui/ui_comment.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Comment 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | 评论 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 回复 29 | 30 | 31 | Return 32 | 33 | 34 | 35 | 36 | 37 | 38 | Qt::Horizontal 39 | 40 | 41 | 42 | 40 43 | 20 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | TextLabel 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 跳转 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | UserListWidget 72 | QListWidget 73 |
component.list.user_list_widget.h
74 |
75 |
76 | 77 | 78 |
79 | -------------------------------------------------------------------------------- /ui/ui_doh_dns.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DohDns 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Doh DNS 15 | 16 | 17 | 18 | 19 | 20 | 21 | Host 22 | 23 | 24 | 25 | 26 | IP 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 关闭 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /ui/ui_index.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Index 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | QWidget#Index{ 18 | border: 3px solid rgb(229, 229, 229); 19 | border-radius: 15px; 20 | } 21 | QLabel#titleLabel { 22 | font: 14px 'Microsoft YaHei Light'; 23 | border: 0px; 24 | /* padding: 10px 15px 10px 15px; */ 25 | } 26 | QListWidget { 27 | border: 0px; 28 | } 29 | *{ 30 | background-color: rgb(249,249,249); 31 | } 32 | 33 | 34 | 35 | 0 36 | 37 | 38 | 39 | 40 | 首页 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ComicListWidget 52 | QListWidget 53 |
component.list.comic_list_widget.h
54 |
55 |
56 | 57 | 58 |
59 | -------------------------------------------------------------------------------- /ui/ui_local_eps.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LocalEps 4 | 5 | 6 | 7 | 0 8 | 0 9 | 646 10 | 391 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 | 36 | 37 | 38 | 39 | 40 | 只显示Waifu2x 41 | 42 | 43 | 44 | 45 | 46 | 47 | Qt::Horizontal 48 | 49 | 50 | 51 | 40 52 | 20 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ComicListWidget 64 | QListWidget 65 |
component.list.comic_list_widget.h
66 |
67 |
68 | 69 | 70 | 71 | JumpPage() 72 | RefreshDataFocus() 73 | 74 |
75 | -------------------------------------------------------------------------------- /ui/ui_rank.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Rank 4 | 5 | 6 | 7 | 0 8 | 0 9 | 536 10 | 379 11 | 12 | 13 | 14 | 排行榜 15 | 16 | 17 | 18 | 19 | 20 | 0 21 | 22 | 23 | 24 | 昨日 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 上月 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 去年 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 全部 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ComicListWidget 69 | QListWidget 70 |
component.list.comic_list_widget.h
71 |
72 |
73 | 74 | 75 |
76 | --------------------------------------------------------------------------------