├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build_macos.yml │ └── build_on_dispatch.yml ├── .gitignore ├── Compile_UI.bat ├── MarkdownView.py ├── OKP.ui ├── OKPLogic.py ├── OKPUI.py ├── ProcessWindow.py ├── README.md ├── WarningDialog.py ├── WarningDialog.ui ├── WebHelper.py ├── image ├── Console.jpg ├── Home01.jpg ├── ProfileManager01.jpg ├── ProfileManager02.jpg ├── ProfileManager03.jpg ├── Proxy.jpg ├── acgnx.jpg └── login.jpg ├── main.py ├── make.bat ├── requirements.txt ├── test.py ├── venv └── Lib │ └── site-packages │ ├── html2bbcode-2.3.3-py3.11.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── installed-files.txt │ └── top_level.txt │ ├── html2bbcode │ ├── __init__.py │ ├── data │ │ └── defaults.conf │ └── parser.py │ ├── html2phpbbcode-0.1.4.dist-info │ ├── INSTALLER │ ├── METADATA │ ├── RECORD │ ├── REQUESTED │ ├── WHEEL │ └── top_level.txt │ └── html2phpbbcode │ ├── __init__.py │ ├── data │ └── defaults.conf │ ├── parser.py │ └── validators.py └── versionfile.rc /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment:** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build_macos.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Build MacOS Package 5 | 6 | on: 7 | workflow_dispatch: 8 | #branches: [ "master" ] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | Build_MacOS: 15 | runs-on: macos-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Python 3.11 19 | uses: actions/setup-python@v3 20 | with: 21 | python-version: "3.11" 22 | - name: Install dependencies 23 | run: | 24 | python -m venv venv 25 | source ./venv/bin/activate 26 | python -m pip install --upgrade pip 27 | pip install -r requirements.txt 28 | - name: Build with Pyinstaller 29 | run: | 30 | source ./venv/bin/activate 31 | pyinstaller --onefile --noconsole main.py -p ./venv/Lib/site-packages --collect-all html2phpbbcode --osx-bundle-identifier com.AmusementClub.Publish.OKPGUI -n OKPGUI.app 32 | # - name: Sign binary 33 | # env: 34 | # WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} 35 | # WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} 36 | # run: | 37 | # New-Item -ItemType directory -Path certificate 38 | # Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE 39 | # certutil -decode certificate/tempCert.txt certificate/certificate.pfx 40 | # Remove-Item -path certificate -include tempCert.txt 41 | # & "C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe" sign /v /f certificate/certificate.pfx /p $env:WINDOWS_CERTIFICATE_PASSWORD /t http://timestamp.digicert.com/ /fd SHA256 dist/OKPGUI.exe 42 | - name: Upload artifacts 43 | uses: actions/upload-artifact@v3 44 | with: 45 | name: MacOS-package 46 | path: dist/OKPGUI.app 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/build_on_dispatch.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Build Package 5 | 6 | on: 7 | workflow_dispatch: 8 | #branches: [ "master" ] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: windows-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up Python 3.11 21 | uses: actions/setup-python@v3 22 | with: 23 | python-version: "3.11" 24 | - name: Install dependencies 25 | run: | 26 | python -m venv venv 27 | & ./venv/Scripts/Activate.ps1 28 | python -m pip install --upgrade pip 29 | pip install -r requirements.txt 30 | - name: Build with Pyinstaller 31 | run: | 32 | & ./venv/Scripts/Activate.ps1 33 | pyinstaller --onefile --noconsole main.py --collect-all html2phpbbcode --version-file versionfile.rc -n OKPGUI.exe 34 | - name: Sign binary 35 | env: 36 | WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} 37 | WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} 38 | run: | 39 | New-Item -ItemType directory -Path certificate 40 | Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE 41 | certutil -decode certificate/tempCert.txt certificate/certificate.pfx 42 | Remove-Item -path certificate -include tempCert.txt 43 | & "C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x86/signtool.exe" sign /v /f certificate/certificate.pfx /p $env:WINDOWS_CERTIFICATE_PASSWORD /t http://timestamp.digicert.com/ /fd SHA256 dist/OKPGUI.exe 44 | - name: Upload artifacts 45 | uses: actions/upload-artifact@v3 46 | with: 47 | name: windows-package 48 | path: dist/OKPGUI.exe 49 | 50 | 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | venv/* 204 | !venv/Lib/ 205 | venv/Lib/site-packages/* 206 | !venv/Lib/site-packages/HTMLParser.py 207 | !venv/Lib/site-packages/markupbase.py 208 | !venv/lib/site-packages/html2bbcode/ 209 | !venv/lib/site-packages/html2bbcode-2.3.3-py3.11.egg-info/ 210 | !venv/lib/site-packages/html2phpbbcode/ 211 | !venv/lib/site-packages/html2phpbbcode-0.1.4.dist-info/ 212 | 213 | # Installer logs 214 | pip-log.txt 215 | 216 | # Unit test / coverage reports 217 | .coverage 218 | .tox 219 | 220 | #Translations 221 | *.mo 222 | 223 | #Mr Developer 224 | .mr.developer.cfg 225 | 226 | #OKP.core 227 | config/ 228 | OKP.Core.exe 229 | OKP.Core.pdb 230 | 231 | template.toml 232 | cookies.txt 233 | *.spec 234 | log*.txt 235 | 236 | okpgui_config.yml 237 | okpgui_profile.yml 238 | 239 | Traceback_* 240 | *.crt -------------------------------------------------------------------------------- /Compile_UI.bat: -------------------------------------------------------------------------------- 1 | pyuic6 -x OKP.ui -o OKPUI.py 2 | pyuic6 -x WarningDialog.ui -o WarningDialog.py -------------------------------------------------------------------------------- /MarkdownView.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt6.QtCore import QUrl, QByteArray, QSize 4 | from PyQt6.QtWebEngineWidgets import QWebEngineView 5 | from PyQt6.QtWebEngineCore import QWebEngineProfile 6 | from PyQt6.QtWidgets import QApplication, QTextEdit, QPushButton, QToolBar, QMainWindow, QDialog, QWidget, QVBoxLayout 7 | from PyQt6.QtNetwork import QNetworkCookie, QNetworkCookieJar 8 | from PyQt6.QtGui import QAction 9 | from urllib import parse 10 | 11 | class MarkdownViewWindow(QWidget): 12 | def __init__(self, html, parentWindow, *args, **kwargs): 13 | super(QWidget, self).__init__(*args, **kwargs) 14 | 15 | self.resize(1200, 1080) 16 | self.parentWindow = parentWindow 17 | self.setWindowTitle("Preview") 18 | 19 | self.browser = QWebEngineView() 20 | self.browser.setHtml(html) 21 | 22 | vbox = QVBoxLayout(self) 23 | vbox.addWidget(self.browser) 24 | self.setLayout(vbox) 25 | -------------------------------------------------------------------------------- /OKP.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 609 10 | 950 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 600 22 | 840 23 | 24 | 25 | 26 | 27 | Microsoft YaHei UI 28 | 12 29 | 30 | 31 | 32 | OKPGUI by AmusementClub 33 | 34 | 35 | 36 | 37 | 0 38 | 0 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 0 48 | 49 | 50 | 51 | 52 | 594 53 | 800 54 | 55 | 56 | 57 | 58 | 12 59 | PreferDefault 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | 68 | 69 | 70 | 71 | 0 72 | 0 73 | 74 | 75 | 76 | 77 | 12 78 | 79 | 80 | 81 | 主页 82 | 83 | 84 | 85 | 86 | 87 | 88 | 0 89 | 0 90 | 91 | 92 | 93 | false 94 | 95 | 96 | color: rgb(255, 0, 30); 97 | font: 12pt "Microsoft YaHei UI"; 98 | 99 | 100 | 删除模板 101 | 102 | 103 | false 104 | 105 | 106 | 107 | 108 | 109 | 110 | Qt::Horizontal 111 | 112 | 113 | 114 | 40 115 | 20 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 0 125 | 0 126 | 127 | 128 | 129 | 130 | 9 131 | 132 | 133 | 134 | color:rgb(75, 75, 75) 135 | 136 | 137 | 138 | 请使用 Markdown 编写 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 0 147 | 0 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | QFormLayout::ExpandingFieldsGrow 156 | 157 | 158 | 159 | 160 | 161 | 0 162 | 0 163 | 164 | 165 | 166 | 集数匹配 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 0 175 | 0 176 | 177 | 178 | 179 | 180 | 0 181 | 30 182 | 183 | 184 | 185 | 186 | 10 187 | 188 | 189 | 190 | <html><head/><body><p>测试</p></body></html> 191 | 192 | 193 | <html><head/><body><p>测试</p></body></html> 194 | 195 | 196 | 在文件名集数部分使用 <ep> 替换,分辨率用 <res> 替换 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 0 205 | 0 206 | 207 | 208 | 209 | 标题匹配 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 0 218 | 0 219 | 220 | 221 | 222 | 223 | 0 224 | 30 225 | 226 | 227 | 228 | 229 | 10 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 0 239 | 0 240 | 241 | 242 | 243 | 标题 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 0 252 | 0 253 | 254 | 255 | 256 | 257 | 0 258 | 30 259 | 260 | 261 | 262 | 263 | 10 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 0 273 | 0 274 | 275 | 276 | 277 | 海报链接 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 0 286 | 0 287 | 288 | 289 | 290 | 291 | 0 292 | 30 293 | 294 | 295 | 296 | 297 | 10 298 | 299 | 300 | 301 | For dmhy.org 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 0 310 | 0 311 | 312 | 313 | 314 | 关于 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 0 323 | 0 324 | 325 | 326 | 327 | 328 | 0 329 | 30 330 | 331 | 332 | 333 | 334 | 10 335 | 336 | 337 | 338 | For nyaa.si 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 0 347 | 0 348 | 349 | 350 | 351 | 352 | 0 353 | 30 354 | 355 | 356 | 357 | 358 | 10 359 | 360 | 361 | 362 | 输入标签,以英文逗号分隔,可用标签可参考 “如何使用 tags?” 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 0 374 | 0 375 | 376 | 377 | 378 | Tags 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 0 391 | 0 392 | 393 | 394 | 395 | 模板名称 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 0 404 | 0 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 0 414 | 0 415 | 416 | 417 | 418 | 选择模板 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 0 427 | 0 428 | 429 | 430 | 431 | <html><head/><body><p>第一次使用请选择「新模板」</p></body></html> 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 0 445 | 0 446 | 447 | 448 | 449 | 种子文件 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 0 458 | 0 459 | 460 | 461 | 462 | 463 | 0 464 | 30 465 | 466 | 467 | 468 | 469 | 10 470 | 471 | 472 | 473 | true 474 | 475 | 476 | 可直接 .torrent 文件拖放到此处 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 0 487 | 0 488 | 489 | 490 | 491 | 保存模板 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 0 500 | 0 501 | 502 | 503 | 504 | 内容 505 | 506 | 507 | 508 | 509 | 510 | 511 | Qt::Horizontal 512 | 513 | 514 | 515 | 40 516 | 20 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 0 526 | 0 527 | 528 | 529 | 530 | 浏览 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 0 539 | 150 540 | 541 | 542 | 543 | true 544 | 545 | 546 | true 547 | 548 | 549 | 550 | Files 551 | 552 | 553 | 554 | 555 | Size 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 0 565 | 0 566 | 567 | 568 | 569 | 570 | 20 571 | 572 | 573 | 574 | One Key Publish! 575 | 576 | 577 | 578 | 579 | 580 | 581 | QFormLayout::ExpandingFieldsGrow 582 | 583 | 584 | 585 | 586 | 587 | 0 588 | 0 589 | 590 | 591 | 592 | 选择身份 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 0 601 | 0 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 0 611 | 0 612 | 613 | 614 | 615 | dmhy 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 0 624 | 0 625 | 626 | 627 | 628 | bangumi 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 0 637 | 0 638 | 639 | 640 | 641 | nyaa 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 0 650 | 0 651 | 652 | 653 | 654 | acg.rip 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 0 663 | 0 664 | 665 | 666 | 667 | acgnx_asia 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 0 676 | 0 677 | 678 | 679 | 680 | acgnx_global 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 0 691 | 0 692 | 693 | 694 | 695 | 预览 696 | 697 | 698 | 699 | 700 | 701 | 702 | <html><head/><body><p><a href="https://github.com/AmusementClub/OKP/wiki/TagsConvert"><span style=" text-decoration: underline; color:#0000ff;">如何使用 tags?</span></a></p></body></html> 703 | 704 | 705 | Qt::RichText 706 | 707 | 708 | 709 | 710 | 711 | 712 | Qt::Horizontal 713 | 714 | 715 | 716 | 40 717 | 20 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 身份管理器 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 登录发布网站 735 | 736 | 737 | 738 | 739 | 740 | 741 | 发布组名称 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 0 750 | 0 751 | 752 | 753 | 754 | dmhy 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | nyaa 765 | 766 | 767 | 768 | 769 | 770 | 771 | false 772 | 773 | 774 | false 775 | 776 | 777 | 778 | 779 | 780 | 781 | acg.rip 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | bangumi 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | acgnx_asia UID 802 | 803 | 804 | 805 | 806 | 807 | 808 | 填写 acgnx UID 809 | 810 | 811 | 812 | 813 | 814 | 815 | 填写 acgnx asia API Token 816 | 817 | 818 | 819 | 820 | 821 | 822 | acgnx_global UID 823 | 824 | 825 | 826 | 827 | 828 | 829 | 填写 acgnx UID 830 | 831 | 832 | 833 | 834 | 835 | 836 | 填写 acgnx global API Token 837 | 838 | 839 | 840 | 841 | 842 | 843 | Token 844 | 845 | 846 | 847 | 848 | 849 | 850 | Token 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | IBeamCursor 860 | 861 | 862 | 863 | 864 | 865 | QTextEdit::NoWrap 866 | 867 | 868 | 869 | 870 | 871 | 872 | Cookies 文件: 873 | 874 | 875 | 876 | 877 | 878 | 879 | 本页中的内容需要保存身份后才会生效。 880 | 881 | 882 | 883 | 884 | 885 | 886 | color: rgb(255, 0, 30); 887 | font: 12pt "Microsoft YaHei UI"; 888 | 889 | 890 | 删除身份 891 | 892 | 893 | 894 | 895 | 896 | 897 | Qt::Horizontal 898 | 899 | 900 | 901 | 40 902 | 20 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 保存身份 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 选择身份 920 | 921 | 922 | 923 | 924 | 925 | 926 | <html><head/><body><p>第一次使用请选择「新模板」</p></body></html> 927 | 928 | 929 | 930 | 新身份 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 身份名称 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 杂项 952 | 953 | 954 | 955 | 956 | 957 | 代理类型 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 不使用代理 966 | 967 | 968 | 969 | 970 | HTTP 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | Host 979 | 980 | 981 | 982 | 983 | 984 | 985 | http://127.0.0.1:7890 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 应用 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | true 1003 | 1004 | 1005 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 1006 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 1007 | p, li { white-space: pre-wrap; } 1008 | </style></head><body style=" font-family:'Microsoft YaHei UI'; font-size:12pt; font-weight:400; font-style:normal;"> 1009 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">此软件为 <a href="https://github.com/AmusementClub/OKP"><span style=" text-decoration: underline; color:#0000ff;">OKP</span></a> 的 GUI,由<a href="https://github.com/AmusementClub"><span style=" text-decoration: underline; color:#0000ff;">娱乐部</span></a>制作,用于快速在多个 BT 资源站发布种子。</p> 1010 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">使用方法参见 GitHub 上的 <a href="https://github.com/AmusementClub/OKPGUI"><span style=" text-decoration: underline; color:#0000ff;">README</span></a>。</p> 1011 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 1012 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Version: 0.0.1 Alpha 内部测试版。</p> 1013 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 1014 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 1015 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 1016 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 1017 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 1018 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">作者:<a href="https://github.com/tastysugar"><span style=" text-decoration: underline; color:#0000ff;">tastySugar</span></a></p> 1019 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> 1020 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> 1021 | 1022 | 1023 | Qt::RichText 1024 | 1025 | 1026 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 1027 | 1028 | 1029 | true 1030 | 1031 | 1032 | Qt::TextBrowserInteraction 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 代理设置 1040 | 1041 | 1042 | Qt::AlignCenter 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | true 1050 | 1051 | 1052 | 关于 1053 | 1054 | 1055 | Qt::AlignCenter 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | Qt::Horizontal 1063 | 1064 | 1065 | 1066 | 40 1067 | 20 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | Qt::Horizontal 1076 | 1077 | 1078 | 1079 | 40 1080 | 20 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | -------------------------------------------------------------------------------- /OKPLogic.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import ( 2 | QUrl, 3 | QProcess, 4 | Qt, 5 | QFileInfo, 6 | ) 7 | from PyQt6.QtWidgets import ( 8 | QApplication, 9 | QWidget, 10 | QMainWindow, 11 | QFileDialog, 12 | QDialog, 13 | QTreeWidgetItem, 14 | QFileIconProvider, 15 | QStyle, 16 | ) 17 | import sys 18 | from OKPUI import Ui_MainWindow 19 | from WarningDialog import Ui_Dialog 20 | import yaml 21 | from pathlib import Path 22 | from WebHelper import WebEngineView 23 | import re 24 | import markdown 25 | from MarkdownView import MarkdownViewWindow 26 | import toml 27 | from html2phpbbcode.parser import HTML2PHPBBCode 28 | from collections import defaultdict 29 | import torrent_parser as tp 30 | from ProcessWindow import MyConsole 31 | import platform 32 | 33 | VERSION = "v0.1.7 Beta" 34 | 35 | CATEGORY = { 36 | 'Anime': ['Default', 'MV', 'TV', 'Movie', 'Collection', 'Raw', 'English'], 37 | 'Music': ['Default', 'Lossless', 'Lossy', 'ACG', 'Doujin', 'Pop'], 38 | 'Comic': ['Default', 'HongKong', 'Taiwan', 'Japanese', 'English'], 39 | 'Novel': ['Default', 'HongKong', 'Taiwan', 'Japanese', 'English'], 40 | 'Action': ['Default', 'Idol', 'TV', 'Movie', 'Tokusatsu', 'Show', 'Raw', 'English'], 41 | 'Picture': ['Default', 'Graphics', 'Photo'], 42 | 'Software': ['Default', 'App', 'Game'] 43 | } 44 | 45 | TEMPLATE_CONFIG = Path("okpgui_config.yml") 46 | PROFILE_CONFIG = Path("okpgui_profile.yml") 47 | 48 | class OKPerror(Exception): 49 | pass 50 | 51 | class OKPMainWIndow(QMainWindow, Ui_MainWindow): 52 | def __init__(self, *args, **kwargs): 53 | QMainWindow.__init__(self, *args, **kwargs) 54 | self.setupUi(self) 55 | self.setupUi2() 56 | if not Path("OKP.Core.exe").exists(): 57 | self.warning("找不到 OKP.Core.exe,请将本程序复制到 OKP.Core.exe 同目录下。") 58 | sys.exit(1) 59 | 60 | def setupUi2(self): 61 | # set title 62 | self.setWindowTitle("OKPGUI by AmusementClub " + VERSION) 63 | 64 | self.textAboutProgram.setText(f""" 65 | 66 | 69 |

此软件为 OKP 的 GUI,由娱乐部制作,用于快速在多个 BT 资源站发布种子。

70 |

使用方法参见 GitHub 上的 README

71 |


72 |

Version: {VERSION}

73 |


74 |

作者:tastySugar

75 |


76 | """) 77 | 78 | # Select torrent 79 | self.buttonBrowse.clicked.connect(self.selectTorrentFile) 80 | 81 | self.HomeTab.setAcceptDrops(True) 82 | self.tab.currentChanged.connect(self.changeTabHandler) 83 | # self.textTorrentPath.setAcceptDrops(True) 84 | self.HomeTab.dragEnterEvent = self.onDragEnterEvent 85 | self.HomeTab.dropEvent = self.onDropEvent 86 | self.HomeTab.dragLeaveEvent = self.onDragLeaveEvent 87 | self.textTorrentPath.textChanged.connect(self.loadTorrent) 88 | 89 | 90 | # Select template 91 | self.reloadProfile() 92 | 93 | self.reloadTemplate() 94 | self.updateTemplate() 95 | self.selectCookiesChangeHandler(self.menuSelectCookies.currentText()) 96 | 97 | self.loadProxy() 98 | 99 | # Save / Delete template 100 | self.buttonSaveTemplate.clicked.connect(self.saveTemplate) 101 | self.buttonDeleteTemplate.clicked.connect(self.deleteTemplate) 102 | 103 | 104 | # preview markdown 105 | self.buttonPreviewMarkdown.clicked.connect(self.previewMarkdown) 106 | #self.textDescription.setMarkdown(self.textDescription.toPlainText()) 107 | 108 | self.textEpPattern.textEdited.connect(self.setTitleText) 109 | self.textTitlePattern.textEdited.connect(self.setTitleText) 110 | 111 | self.menuSelectCookies.currentTextChanged.connect(self.selectCookiesChangeHandler) 112 | 113 | self.fileTree.setColumnWidth(0,450) 114 | 115 | # tab 2 login 116 | self.buttonDmhyLogin.clicked.connect(self.loginWebsite("https://share.dmhy.org/user/login")) 117 | self.buttonNyaaLogin.clicked.connect(self.loginWebsite("https://nyaa.si/login")) 118 | self.buttonAcgripLogin.clicked.connect(self.loginWebsite("https://acg.rip/users/sign_in")) 119 | self.buttonBangumiLogin.clicked.connect(self.loginWebsite("https://bangumi.moe/")) 120 | 121 | self.textNyaaName.setDisabled(True) 122 | 123 | 124 | self.buttonSaveProfile.clicked.connect(self.saveProfile) 125 | self.buttonDeleteProfile.clicked.connect(self.deleteProfile) 126 | 127 | self.menuProxyType.currentTextChanged.connect(self.onProxySelection) 128 | self.onProxySelection() 129 | 130 | self.buttonSaveProxy.clicked.connect(self.saveProxy) 131 | 132 | self.textAcgnxasiaToken.textEdited.connect(self.applyAcgnxasiaAPIToken) 133 | self.textAcgnxglobalToken.textEdited.connect(self.applyAcgnxglobalAPIToken) 134 | self.textCookies.textChanged.connect(self.onCookiesChange) 135 | 136 | # publish button 137 | self.buttonOKP.clicked.connect(self.publishRun) 138 | 139 | def changeTabHandler(self, event): 140 | if event == 1: 141 | self.reloadProfile() 142 | if event == 2: 143 | self.loadProxy() 144 | 145 | def onDragEnterEvent(self, event): 146 | if event.mimeData().hasUrls(): 147 | event.accept() 148 | self.textTorrentPath.setPlaceholderText("请在此释放鼠标") 149 | else: 150 | event.ignore() 151 | 152 | def onDropEvent(self, event): 153 | self.textTorrentPath.setPlaceholderText("可直接 .torrent 文件拖放到此处") 154 | files = [u.toLocalFile() for u in event.mimeData().urls()] 155 | self.textTorrentPath.setText(files[0]) 156 | 157 | def onDragLeaveEvent(self, evet): 158 | self.textTorrentPath.setPlaceholderText("可直接 .torrent 文件拖放到此处") 159 | 160 | def loadTorrent(self): 161 | 162 | def sizeof_fmt(num, suffix="B"): 163 | if num == -1: 164 | return "" 165 | 166 | for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: 167 | if abs(num) < 1024.0: 168 | return f"{num:3.1f} {unit}{suffix}" 169 | num /= 1024.0 170 | return f"{num:.1f} Yi{suffix}" 171 | 172 | self.fileTree.clear() 173 | self.setTitleText() 174 | 175 | torrentPath = Path(self.textTorrentPath.text()) 176 | try: 177 | data = tp.parse_torrent_file(torrentPath) 178 | except: 179 | return 180 | 181 | 182 | if 'files' not in data['info']: 183 | # One file torrent 184 | root = QTreeWidgetItem(self.fileTree) 185 | root.setText(0, Path(data['info']['name']).stem) 186 | root.setText(1, sizeof_fmt(data['info']['length'])) 187 | file_info = QFileInfo(str(data['info']['name'])) 188 | file_icon_provider = QFileIconProvider() 189 | root.setIcon(0, file_icon_provider.icon(file_info)) 190 | 191 | else: 192 | # Multi file torrent 193 | data = data['info']['files'] 194 | folder_icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DirIcon) 195 | 196 | root = QTreeWidgetItem(self.fileTree) 197 | root.setText(0, torrentPath.stem) 198 | root.setIcon(0, folder_icon) 199 | root.setExpanded(True) 200 | self.fileTree.insertTopLevelItem(0, root) 201 | 202 | d = {str(x['path']):x['length'] for x in data} 203 | d["[]"] = root 204 | 205 | longetstPath = 0 206 | for file in data: 207 | if len(file['path']) > longetstPath: 208 | longetstPath = len(file['path']) 209 | 210 | nodes = dict() # path: length, if length = -1 then it is a dir 211 | 212 | for x in range(longetstPath + 1): 213 | for path, length in d.items(): 214 | path = eval(path) 215 | if len(path) > x: 216 | nodes[str(path[:x])] = -1 217 | else: 218 | nodes[str(path)] = length 219 | 220 | 221 | sorted_nodes = sorted(nodes, key=lambda x: len(eval(x))) 222 | 223 | for n in sorted_nodes: 224 | if n == "[]": 225 | continue 226 | item = QTreeWidgetItem(nodes[f'{eval(n)[:-1]}']) 227 | item.setText(0, eval(n)[-1]) 228 | item.setText(1, sizeof_fmt(nodes[n])) 229 | if nodes[n] == -1: 230 | item.setIcon(0, folder_icon) 231 | else: 232 | file_info = QFileInfo(eval(n)[-1]) 233 | file_icon_provider = QFileIconProvider() 234 | item.setIcon(0, file_icon_provider.icon(file_info)) 235 | nodes[n] = item 236 | 237 | self.fileTree.sortByColumn(1, Qt.SortOrder.AscendingOrder) 238 | self.fileTree.setAlternatingRowColors(True) 239 | 240 | 241 | 242 | 243 | 244 | def selectTorrentFile(self): 245 | fname = QFileDialog.getOpenFileName(self, 'Open file', 'c:\\',"Torrent file v1 (*.torrent)")[0] 246 | self.textTorrentPath.setText(fname) 247 | 248 | 249 | 250 | 251 | 252 | def loadConfig(self): 253 | path = Path(TEMPLATE_CONFIG) 254 | if not path.exists(): 255 | with open(TEMPLATE_CONFIG, "w", encoding='utf-8') as f: 256 | f.write(''' 257 | lastUsed: 新模板 258 | proxy: http://127.0.0.1:7890 259 | proxyType: 不使用代理 260 | template: 261 | 新模板: 262 | about: 263 | checkAcgnxasia: false 264 | checkAcgnxglobal: false 265 | checkAcgrip: false 266 | checkDmhy: false 267 | checkNyaa: false 268 | description: "" 269 | epPattern: "" 270 | poster: "" 271 | profile: "" 272 | tags: "Anime" 273 | titlePattern: "" 274 | title: "" 275 | ''') 276 | 277 | with open(path, "r", encoding="utf-8") as f: 278 | self.conf = yaml.safe_load(f) 279 | 280 | def loadProxy(self): 281 | conf = defaultdict(str, self.conf) 282 | self.menuProxyType.setCurrentText(conf['proxyType']) 283 | self.textProxyHost.setText(conf['proxy']) 284 | 285 | def saveProxy(self): 286 | self.conf['proxyType'] = self.menuProxyType.currentText() 287 | self.conf['proxy'] = self.textProxyHost.text() 288 | with open(TEMPLATE_CONFIG, "w", encoding='utf-8') as file: 289 | yaml.safe_dump(self.conf, file, encoding='utf-8',allow_unicode=True) 290 | 291 | 292 | def loginWebsite(self, url): 293 | def login(): 294 | self.webview = WebEngineView(url=QUrl(url),parentWindow=self) 295 | self.webview.show() 296 | 297 | return login 298 | 299 | def getCookies(self): 300 | return self.textCookies.toPlainText() 301 | 302 | def setCookies(self, cookies:str): 303 | self.textCookies.setText(cookies) 304 | 305 | def addCookies(self, cookies:str): 306 | c = self.textCookies.toPlainText() 307 | cookies = re.sub(r"https://\.", "https://", cookies) 308 | if c == "": 309 | if len(cookies) > 0 and cookies[-1] != "\n": 310 | cookies += "\n" 311 | self.textCookies.setText(cookies) 312 | else: 313 | c += cookies 314 | c = re.sub(r"\n\n", "", c) 315 | if c[-1] != "\n": 316 | c += "\n" 317 | self.textCookies.setText(c) 318 | 319 | def setUserAgent(self, ua:str): 320 | if not re.search(r"^user-agent:", self.textCookies.toPlainText()): 321 | self.textCookies.setText(f"user-agent:\t{ua}\n" + self.textCookies.toPlainText()) 322 | else: 323 | self.textCookies.setText( 324 | re.sub(r"^user-agent:.*\n", f"user-agent:\t{ua}\n", self.textCookies.toPlainText()) 325 | ) 326 | 327 | def updateTemplate(self): 328 | selected = self.menuTemplateSelection.currentText() 329 | if selected == "创建新模板": 330 | self.textTemplateName.setText("新模板") 331 | self.textEpPattern.clear() 332 | self.textTitlePattern.clear() 333 | self.textTitle.clear() 334 | self.textPoster.clear() 335 | self.textAbout.clear() 336 | self.textTags.setText("Anime") 337 | self.textDescription.clear() 338 | self.menuSelectCookies.setCurrentIndex(0) 339 | 340 | 341 | elif selected not in self.conf['template']: 342 | return 343 | else: 344 | 345 | conf = defaultdict(str, self.conf['template'][selected]) 346 | self.textTemplateName.setText(selected) 347 | self.textEpPattern.setText(conf['epPattern']) 348 | self.textTitlePattern.setText(conf['titlePattern']) 349 | self.setTitleText() 350 | self.textPoster.setText(conf['poster']) 351 | self.textAbout.setText(conf['about']) 352 | self.textDescription.setText(conf['description']) 353 | self.reloadMenuSelectCookies() 354 | self.textTags.setText(conf['tags']) 355 | self.textTitle.setText(conf['title']) 356 | self.setTitleText() 357 | self.conf['template'][selected] = dict(conf) 358 | 359 | conf = defaultdict(bool, self.conf['template'][selected]) 360 | self.checkboxDmhyPublish.setChecked(conf['checkDmhy']) 361 | self.checkboxNyaaPublish.setChecked(conf['checkNyaa']) 362 | self.checkboxBangumiPublish.setChecked(conf['checkBangumi']) 363 | self.checkboxAcgripPublish.setChecked(conf['checkAcgrip']) 364 | self.checkboxAcgnxasiaPublish.setChecked(conf['checkAcgnxasia']) 365 | self.checkboxAcgnxglobalPublish.setChecked(conf['checkAcgnxglobal']) 366 | self.conf['template'][selected] = dict(conf) 367 | 368 | def setTitleText(self): 369 | # set title based on patterns, set to "" when no pattern set 370 | filename = Path(self.textTorrentPath.text()).name 371 | epPattern = self.textEpPattern.text() 372 | titlePattern = self.textTitlePattern.text() 373 | 374 | if epPattern == "" or titlePattern == "": 375 | return 376 | 377 | replaces = re.findall(r"<\w+>", epPattern) 378 | epPattern = re.escape(epPattern) 379 | epPattern = re.sub(r"<", r"(?P<", epPattern) 380 | epPattern = re.sub(r">", r">.+)", epPattern) 381 | 382 | try: 383 | m = re.search(epPattern, filename) 384 | except re.error: 385 | return 386 | 387 | if not m: 388 | return 389 | 390 | title = titlePattern 391 | for i in replaces: 392 | title = re.sub(i, m[f'{re.sub("<|>", "", i)}'], title) 393 | 394 | self.textTitle.setText(title) 395 | 396 | def selectCookiesChangeHandler(self, event): 397 | if event == "": 398 | return 399 | 400 | cookies = self.profile['profiles'][event]['cookies'] 401 | 402 | 403 | if cookies is None or not re.search(r"https:\/\/share\.dmhy\.org", cookies): 404 | self.checkboxDmhyPublish.setChecked(False) 405 | self.checkboxDmhyPublish.setCheckable(False) 406 | else: 407 | self.checkboxDmhyPublish.setCheckable(True) 408 | 409 | if cookies is None or not re.search(r"https:\/\/nyaa\.si", cookies): 410 | self.checkboxNyaaPublish.setChecked(False) 411 | self.checkboxNyaaPublish.setCheckable(False) 412 | else: 413 | self.checkboxNyaaPublish.setCheckable(True) 414 | 415 | if cookies is None or not re.search(r"https:\/\/acg\.rip", cookies): 416 | self.checkboxAcgripPublish.setChecked(False) 417 | self.checkboxAcgripPublish.setCheckable(False) 418 | else: 419 | self.checkboxAcgripPublish.setCheckable(True) 420 | 421 | if cookies is None or not re.search(r"https:\/\/bangumi\.moe", cookies): 422 | self.checkboxBangumiPublish.setChecked(False) 423 | self.checkboxBangumiPublish.setCheckable(False) 424 | else: 425 | self.checkboxBangumiPublish.setCheckable(True) 426 | 427 | if cookies is None or not re.search(r"https:\/\/share\.acgnx\.se", cookies): 428 | self.checkboxAcgnxasiaPublish.setChecked(False) 429 | self.checkboxAcgnxasiaPublish.setCheckable(False) 430 | else: 431 | self.checkboxAcgnxasiaPublish.setCheckable(True) 432 | 433 | if cookies is None or not re.search(r"https:\/\/www\.acgnx\.se", cookies): 434 | self.checkboxAcgnxglobalPublish.setChecked(False) 435 | self.checkboxAcgnxglobalPublish.setCheckable(False) 436 | else: 437 | self.checkboxAcgnxglobalPublish.setCheckable(True) 438 | 439 | 440 | def reloadTemplate(self): 441 | self.loadConfig() 442 | templateList = list(self.conf['template'].keys()) 443 | self.menuTemplateSelection.clear() 444 | self.menuTemplateSelection.addItems(templateList) 445 | self.menuTemplateSelection.addItem("创建新模板") 446 | self.menuTemplateSelection.currentTextChanged.connect(self.updateTemplate) 447 | try: 448 | self.menuTemplateSelection.setCurrentText(self.conf['lastUsed']) 449 | self.updateTemplate() 450 | except: 451 | pass 452 | 453 | 454 | def saveTemplate(self): 455 | templateName = self.textTemplateName.text() 456 | 457 | if templateName in ["", "创建新模板"]: 458 | self.warning(f"非法模板名\"{templateName}\",请换个名字。") 459 | return 460 | 461 | if templateName in self.conf['template']: 462 | if not self.warning(f"即将覆盖模板\"{templateName}\",是否确认?"): 463 | return 464 | 465 | self.conf['lastUsed'] = templateName 466 | self.conf['template'][templateName] = { 467 | 'epPattern': self.textEpPattern.text(), 468 | 'titlePattern': self.textTitlePattern.text(), 469 | 'poster': self.textPoster.text(), 470 | 'about': self.textAbout.text(), 471 | 'tags': self.textTags.text(), 472 | 'description': self.textDescription.toPlainText(), 473 | 'profile': self.menuSelectCookies.currentText(), 474 | 'checkDmhy': self.checkboxDmhyPublish.isChecked(), 475 | 'checkNyaa': self.checkboxNyaaPublish.isChecked(), 476 | 'checkBangumi': self.checkboxBangumiPublish.isChecked(), 477 | 'checkAcgrip': self.checkboxAcgripPublish.isChecked(), 478 | 'checkAcgnxasia': self.checkboxAcgnxasiaPublish.isChecked(), 479 | 'checkAcgnxglobal': self.checkboxAcgnxglobalPublish.isChecked(), 480 | 'title': self.textTitle.text() 481 | } 482 | 483 | with open(TEMPLATE_CONFIG, "w", encoding='utf-8') as file: 484 | yaml.safe_dump(self.conf, file, encoding='utf-8',allow_unicode=True) 485 | 486 | self.reloadTemplate() 487 | 488 | 489 | def deleteTemplate(self): 490 | # todo: ask for confirmation 491 | if self.warning(f'正在删除"{self.menuTemplateSelection.currentText()}"模板,删除后将无法恢复,是否继续?'): 492 | self.conf['template'].pop(self.menuTemplateSelection.currentText()) 493 | with open(TEMPLATE_CONFIG, "w", encoding='utf-8') as file: 494 | yaml.safe_dump(self.conf, file, encoding='utf-8',allow_unicode=True) 495 | 496 | self.reloadTemplate() 497 | 498 | def loadProfile(self): 499 | path = Path(PROFILE_CONFIG) 500 | if not path.exists(): 501 | with open(path, "w", encoding="utf-8") as f: 502 | f.write( 503 | ''' 504 | lastUsed: 新身份 505 | profiles: 506 | 新身份: 507 | cookies: 508 | dmhyName: 509 | nyaaName: 510 | acgripName: 511 | bangumiName: 512 | acgnxasiaName: 513 | acgnxglobalName: 514 | ''' 515 | ) 516 | with open(path, "r", encoding="utf-8") as f: 517 | self.profile = yaml.safe_load(f) 518 | 519 | def reloadProfile(self): 520 | self.loadProfile() 521 | profileList = list(self.profile["profiles"].keys()) 522 | self.menuProfileSelection.clear() 523 | self.menuProfileSelection.addItems(profileList) 524 | self.menuProfileSelection.addItem("创建新身份") 525 | self.updateProfile() 526 | self.menuProfileSelection.currentTextChanged.connect(self.updateProfile) 527 | try: 528 | self.menuProfileSelection.setCurrentText(self.profile["lastUsed"]) 529 | self.updateProfile() 530 | except: 531 | pass 532 | 533 | def updateProfile(self): 534 | 535 | selected = self.menuProfileSelection.currentText() 536 | 537 | if selected == "创建新身份": 538 | # todo: warning 539 | self.textProfileName.setText("新身份") 540 | self.textDmhyName.clear() 541 | self.textNyaaName.clear() 542 | self.textAcgripName.clear() 543 | self.textBangumiName.clear() 544 | self.textAcgnxasiaName.clear() 545 | self.textAcgnxasiaToken.clear() 546 | self.textAcgnxglobalName.clear() 547 | self.textAcgnxglobalToken.clear() 548 | self.textCookies.clear() 549 | # self.menuProxyType.setCurrentIndex(0) 550 | # self.textProxyHost.setText("http://127.0.0.1:7890") 551 | 552 | elif selected not in self.profile["profiles"]: 553 | return 554 | else: 555 | # if 'proxyType' in self.profile: self.menuProxyType.setCurrentText(self.profile['proxyType']) 556 | # if 'proxy' in self.profile: self.textProxyHost.setText(self.profile['proxy']) 557 | 558 | prof = defaultdict(str, self.profile["profiles"][selected]) 559 | 560 | self.textProfileName.setText(selected) 561 | self.textDmhyName.setText(prof['dmhyName']) 562 | self.textNyaaName.setText(prof['nyaaName']) 563 | self.textAcgripName.setText(prof['acgripName']) 564 | self.textBangumiName.setText(prof['bangumiName']) 565 | self.textAcgnxasiaName.setText(prof['acgnxasiaName']) 566 | self.textAcgnxglobalName.setText(prof['acgnxglobalName']) 567 | self.textCookies.setText(prof['cookies']) 568 | 569 | 570 | res = re.search(r'https:\/\/share.acgnx.se\ttoken=(?P.*)(\n|$)', str(prof['cookies'])) 571 | if res: 572 | self.textAcgnxasiaToken.setText(res['token']) 573 | else: 574 | self.textAcgnxasiaToken.clear() 575 | res = re.search(r'https:\/\/www.acgnx.se\ttoken=(?P.*)(\n|$)', str(prof['cookies'])) 576 | if res: 577 | self.textAcgnxglobalToken.setText(res['token']) 578 | else: 579 | self.textAcgnxglobalToken.clear() 580 | 581 | self.profile["profiles"][selected] = dict(prof) 582 | 583 | 584 | def saveProfile(self): 585 | profileName = self.textProfileName.text() 586 | 587 | if profileName in ["", "创建新身份"]: 588 | self.warning(f"非法身份名\"{profileName}\",请换个名字。") 589 | return 590 | 591 | if profileName in self.profile["profiles"]: 592 | if not self.warning(f"即将覆盖身份\"{profileName}\", 是否确认?"): 593 | return 594 | 595 | self.profile["lastUsed"] = self.textProfileName.text() 596 | self.profile["profiles"][self.textProfileName.text()] = { 597 | 'cookies': self.textCookies.toPlainText(), 598 | 'dmhyName': self.textDmhyName.text(), 599 | 'nyaaName': self.textNyaaName.text(), 600 | 'acgripName': self.textAcgripName.text(), 601 | 'bangumiName': self.textBangumiName.text(), 602 | 'acgnxasiaName': self.textAcgnxasiaName.text(), 603 | 'acgnxglobalName': self.textAcgnxglobalName.text(), 604 | } 605 | 606 | with open(PROFILE_CONFIG, "w", encoding='utf-8') as file: 607 | yaml.safe_dump(self.profile, file, encoding='utf-8',allow_unicode=True) 608 | 609 | self.reloadProfile() 610 | self.reloadMenuSelectCookies() 611 | 612 | 613 | def deleteProfile(self): 614 | if self.warning(f'正在删除"{self.menuProfileSelection.currentText()}"身份,删除后将无法恢复,是否继续?'): 615 | self.profile['profiles'].pop(self.menuProfileSelection.currentText()) 616 | with open(PROFILE_CONFIG, "w", encoding='utf-8') as file: 617 | yaml.safe_dump(self.profile, file, encoding='utf-8',allow_unicode=True) 618 | 619 | self.reloadMenuSelectCookies() 620 | self.reloadProfile() 621 | 622 | 623 | def previewMarkdown(self): 624 | md = markdown.markdown(self.textDescription.toPlainText()) 625 | #self.textDescription.setPlainText(md) 626 | self.markdownWindow = MarkdownViewWindow(html=md,parentWindow=self) 627 | self.markdownWindow.show() 628 | 629 | def warning(self, message): 630 | warning = WarningDialog() 631 | warning.label.setText(message) 632 | warning.show() 633 | return warning.exec() 634 | 635 | def reloadMenuSelectCookies(self): 636 | self.menuSelectCookies.clear() 637 | self.menuSelectCookies.addItems(self.profile['profiles'].keys()) 638 | try: self.menuSelectCookies.setCurrentText(self.conf['template'][self.menuTemplateSelection.currentText()]['profile']) 639 | except: pass 640 | 641 | def onProxySelection(self): 642 | selected = self.menuProxyType.currentText() 643 | if selected == "不使用代理": 644 | self.textProxyHost.setDisabled(True) 645 | return 646 | if selected == "HTTP": 647 | self.textProxyHost.setEnabled(True) 648 | return 649 | 650 | def applyAcgnxasiaAPIToken(self): 651 | cookies = self.textCookies.toPlainText() 652 | new_string, n = re.subn( 653 | r"https:\/\/share.acgnx.se\ttoken=.*(\n|$)", 654 | f"https://share.acgnx.se\ttoken={self.textAcgnxasiaToken.text()}\n", 655 | cookies) 656 | if n != 0: 657 | self.textCookies.setText(new_string) 658 | else: 659 | if cookies and cookies[-1] != "\n": cookies += "\n" 660 | self.textCookies.setText( 661 | cookies + f"https://share.acgnx.se\ttoken={self.textAcgnxasiaToken.text()}\n" 662 | ) 663 | 664 | def applyAcgnxglobalAPIToken(self): 665 | cookies = self.textCookies.toPlainText() 666 | new_string, n = re.subn( 667 | r"https:\/\/www.acgnx.se\ttoken=.*(\n|$)", 668 | f"https://www.acgnx.se\ttoken={self.textAcgnxglobalToken.text()}\n", 669 | cookies) 670 | if n != 0: 671 | self.textCookies.setText(new_string) 672 | else: 673 | if cookies and cookies[-1] != "\n": cookies += "\n" 674 | self.textCookies.setText( 675 | cookies + f"https://www.acgnx.se\ttoken={self.textAcgnxglobalToken.text()}\n" 676 | ) 677 | 678 | def onCookiesChange(self): 679 | cookies = self.textCookies.toPlainText() 680 | m = re.search(r"https:\/\/share.acgnx.se\ttoken=(?P.*)(\n|$)", cookies) 681 | if m: 682 | self.textAcgnxasiaToken.setText(m['token']) 683 | 684 | m = re.search(r"https:\/\/www.acgnx.se\ttoken=(?P.*)(\n|$)", cookies) 685 | if m: 686 | self.textAcgnxglobalToken.setText(m['token']) 687 | 688 | 689 | 690 | def publishRun(self): 691 | # Sanity check 692 | path = self.textTorrentPath.text() 693 | if path == "": 694 | self.warning("种子文件不能为空。") 695 | return 696 | 697 | if not Path(path).exists(): 698 | self.warning(f"无法找到种子文件'{path}'。") 699 | return 700 | 701 | if Path(path).suffix != ".torrent": 702 | self.warning(f"'{path}' 不是一个 .torrent 文件") 703 | return 704 | 705 | if self.textTitle.text() == "": 706 | self.warning("标题不能为空。") 707 | return 708 | 709 | if self.textDescription.toPlainText() == "": 710 | self.warning("内容不能为空。") 711 | 712 | # Generate template.toml 713 | tags = map(lambda x: x.strip() , self.textTags.text().split(",")) 714 | intro_templates = [] 715 | 716 | md = self.textDescription.toPlainText() 717 | html = markdown.markdown(md) 718 | parser = HTML2PHPBBCode() 719 | bbcode = parser.feed(html) 720 | proxy = self.conf["proxy"] 721 | 722 | cookies = self.profile['profiles'][self.menuSelectCookies.currentText()]['cookies'] 723 | profile = self.profile['profiles'][self.menuSelectCookies.currentText()] 724 | 725 | if self.checkboxDmhyPublish.isChecked() and self.checkboxDmhyPublish.isCheckable(): 726 | intro_templates.append( 727 | { 728 | 'site': 'dmhy', 729 | 'name': profile['dmhyName'], 730 | 'content': html, 731 | } 732 | ) 733 | 734 | if self.checkboxNyaaPublish.isChecked() and self.checkboxNyaaPublish.isCheckable(): 735 | intro_templates.append( 736 | { 737 | 'site': 'nyaa', 738 | 'name': profile['nyaaName'], 739 | 'content': md, 740 | } 741 | ) 742 | 743 | if self.checkboxAcgripPublish.isChecked() and self.checkboxAcgripPublish.isCheckable(): 744 | intro_templates.append( 745 | { 746 | 'site': 'acgrip', 747 | 'name': profile['acgripName'], 748 | 'content': bbcode, 749 | } 750 | ) 751 | 752 | if self.checkboxBangumiPublish.isChecked() and self.checkboxBangumiPublish.isCheckable(): 753 | intro_templates.append( 754 | { 755 | 'site': 'bangumi', 756 | 'name': profile['bangumiName'], 757 | 'content': html, 758 | } 759 | ) 760 | 761 | if self.checkboxAcgnxasiaPublish.isChecked() and self.checkboxAcgnxasiaPublish.isCheckable(): 762 | intro_templates.append( 763 | { 764 | 'site': 'acgnx_asia', 765 | 'name': profile['acgnxasiaName'], 766 | 'content': html, 767 | } 768 | ) 769 | 770 | if self.checkboxAcgnxglobalPublish.isChecked() and self.checkboxAcgnxglobalPublish.isCheckable(): 771 | intro_templates.append( 772 | { 773 | 'site': 'acgnx_global', 774 | 'name': profile['acgnxglobalName'], 775 | 'content': html, 776 | } 777 | ) 778 | 779 | if self.conf['proxyType'] == "HTTP": 780 | for d in intro_templates: 781 | d['proxy'] = proxy 782 | 783 | template_conf = { 784 | 'display_name': self.textTitle.text(), 785 | 'poster': self.textPoster.text(), 786 | 'about': self.textAbout.text(), 787 | 'filename_regex': '', 788 | 'resolution_regex': '', 789 | 'tags': list(tags), 790 | 'intro_template': intro_templates 791 | } 792 | 793 | with open("template.toml", "w", encoding='utf-8') as f: 794 | toml.dump(template_conf, f) 795 | 796 | # Generate cookies.txt 797 | with open("cookies.txt", "w", encoding='utf-8') as f: 798 | f.write(self.profile['profiles'][self.menuSelectCookies.currentText()]['cookies']) 799 | 800 | self.console = MyConsole(self) 801 | self.console.onFinished(self.updateCookies) 802 | self.console.start("OKP.Core.exe", [ 803 | self.textTorrentPath.text(), 804 | "-s", str(Path.cwd().joinpath("template.toml")), 805 | '--cookies', str(Path.cwd().joinpath("cookies.txt")) 806 | ]) 807 | self.console.show() 808 | 809 | 810 | 811 | 812 | def updateCookies(self, int, exitStatus): 813 | if exitStatus == QProcess.ExitStatus.NormalExit: 814 | try: 815 | with open("cookies.txt", "r", encoding="utf-8") as f: 816 | newCookies = f.read() 817 | 818 | self.profile["profiles"][self.menuSelectCookies.currentText()]["cookies"] = newCookies 819 | 820 | with open(PROFILE_CONFIG, "w", encoding="utf-8") as file: 821 | yaml.safe_dump(self.profile, file, encoding='utf-8',allow_unicode=True) 822 | 823 | self.reloadProfile() 824 | 825 | except: 826 | return 827 | 828 | 829 | 830 | class WarningDialog(QDialog, Ui_Dialog): 831 | def __init__(self, *args, **kwargs): 832 | QDialog.__init__(self, *args, **kwargs) 833 | self.setupUi(self) 834 | 835 | 836 | if __name__ == '__main__': 837 | app = QApplication(sys.argv) 838 | if platform.system() != "Windows": 839 | app.setStyle('Fusion') 840 | 841 | window = OKPMainWIndow() 842 | window.show() 843 | sys.exit(app.exec()) -------------------------------------------------------------------------------- /OKPUI.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'OKP.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.4.2 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_MainWindow(object): 13 | def setupUi(self, MainWindow): 14 | MainWindow.setObjectName("MainWindow") 15 | MainWindow.resize(609, 950) 16 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 17 | sizePolicy.setHorizontalStretch(0) 18 | sizePolicy.setVerticalStretch(0) 19 | sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth()) 20 | MainWindow.setSizePolicy(sizePolicy) 21 | MainWindow.setMinimumSize(QtCore.QSize(600, 840)) 22 | font = QtGui.QFont() 23 | font.setFamily("Microsoft YaHei UI") 24 | font.setPointSize(12) 25 | MainWindow.setFont(font) 26 | self.centralwidget = QtWidgets.QWidget(parent=MainWindow) 27 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 28 | sizePolicy.setHorizontalStretch(0) 29 | sizePolicy.setVerticalStretch(0) 30 | sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) 31 | self.centralwidget.setSizePolicy(sizePolicy) 32 | self.centralwidget.setObjectName("centralwidget") 33 | self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) 34 | self.gridLayout_2.setObjectName("gridLayout_2") 35 | self.tab = QtWidgets.QTabWidget(parent=self.centralwidget) 36 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 37 | sizePolicy.setHorizontalStretch(0) 38 | sizePolicy.setVerticalStretch(0) 39 | sizePolicy.setHeightForWidth(self.tab.sizePolicy().hasHeightForWidth()) 40 | self.tab.setSizePolicy(sizePolicy) 41 | self.tab.setMinimumSize(QtCore.QSize(594, 800)) 42 | font = QtGui.QFont() 43 | font.setPointSize(12) 44 | font.setStyleStrategy(QtGui.QFont.StyleStrategy.PreferDefault) 45 | self.tab.setFont(font) 46 | self.tab.setStyleSheet("") 47 | self.tab.setObjectName("tab") 48 | self.HomeTab = QtWidgets.QWidget() 49 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 50 | sizePolicy.setHorizontalStretch(0) 51 | sizePolicy.setVerticalStretch(0) 52 | sizePolicy.setHeightForWidth(self.HomeTab.sizePolicy().hasHeightForWidth()) 53 | self.HomeTab.setSizePolicy(sizePolicy) 54 | font = QtGui.QFont() 55 | font.setPointSize(12) 56 | self.HomeTab.setFont(font) 57 | self.HomeTab.setObjectName("HomeTab") 58 | self.gridLayout = QtWidgets.QGridLayout(self.HomeTab) 59 | self.gridLayout.setObjectName("gridLayout") 60 | self.buttonDeleteTemplate = QtWidgets.QPushButton(parent=self.HomeTab) 61 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 62 | sizePolicy.setHorizontalStretch(0) 63 | sizePolicy.setVerticalStretch(0) 64 | sizePolicy.setHeightForWidth(self.buttonDeleteTemplate.sizePolicy().hasHeightForWidth()) 65 | self.buttonDeleteTemplate.setSizePolicy(sizePolicy) 66 | self.buttonDeleteTemplate.setAutoFillBackground(False) 67 | self.buttonDeleteTemplate.setStyleSheet("color: rgb(255, 0, 30);\n" 68 | "font: 12pt \"Microsoft YaHei UI\";") 69 | self.buttonDeleteTemplate.setFlat(False) 70 | self.buttonDeleteTemplate.setObjectName("buttonDeleteTemplate") 71 | self.gridLayout.addWidget(self.buttonDeleteTemplate, 1, 8, 1, 1) 72 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) 73 | self.gridLayout.addItem(spacerItem, 5, 4, 1, 1) 74 | self.label_10 = QtWidgets.QLabel(parent=self.HomeTab) 75 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 76 | sizePolicy.setHorizontalStretch(0) 77 | sizePolicy.setVerticalStretch(0) 78 | sizePolicy.setHeightForWidth(self.label_10.sizePolicy().hasHeightForWidth()) 79 | self.label_10.setSizePolicy(sizePolicy) 80 | font = QtGui.QFont() 81 | font.setPointSize(9) 82 | self.label_10.setFont(font) 83 | self.label_10.setStyleSheet("color:rgb(75, 75, 75)\n" 84 | "") 85 | self.label_10.setObjectName("label_10") 86 | self.gridLayout.addWidget(self.label_10, 6, 0, 1, 1) 87 | self.textDescription = QtWidgets.QTextEdit(parent=self.HomeTab) 88 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 89 | sizePolicy.setHorizontalStretch(0) 90 | sizePolicy.setVerticalStretch(0) 91 | sizePolicy.setHeightForWidth(self.textDescription.sizePolicy().hasHeightForWidth()) 92 | self.textDescription.setSizePolicy(sizePolicy) 93 | self.textDescription.setObjectName("textDescription") 94 | self.gridLayout.addWidget(self.textDescription, 7, 0, 1, 9) 95 | self.formLayout = QtWidgets.QFormLayout() 96 | self.formLayout.setFieldGrowthPolicy(QtWidgets.QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow) 97 | self.formLayout.setObjectName("formLayout") 98 | self.label_4 = QtWidgets.QLabel(parent=self.HomeTab) 99 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 100 | sizePolicy.setHorizontalStretch(0) 101 | sizePolicy.setVerticalStretch(0) 102 | sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) 103 | self.label_4.setSizePolicy(sizePolicy) 104 | self.label_4.setObjectName("label_4") 105 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_4) 106 | self.textEpPattern = QtWidgets.QLineEdit(parent=self.HomeTab) 107 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 108 | sizePolicy.setHorizontalStretch(0) 109 | sizePolicy.setVerticalStretch(0) 110 | sizePolicy.setHeightForWidth(self.textEpPattern.sizePolicy().hasHeightForWidth()) 111 | self.textEpPattern.setSizePolicy(sizePolicy) 112 | self.textEpPattern.setMinimumSize(QtCore.QSize(0, 30)) 113 | font = QtGui.QFont() 114 | font.setPointSize(10) 115 | self.textEpPattern.setFont(font) 116 | self.textEpPattern.setObjectName("textEpPattern") 117 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textEpPattern) 118 | self.label_5 = QtWidgets.QLabel(parent=self.HomeTab) 119 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 120 | sizePolicy.setHorizontalStretch(0) 121 | sizePolicy.setVerticalStretch(0) 122 | sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) 123 | self.label_5.setSizePolicy(sizePolicy) 124 | self.label_5.setObjectName("label_5") 125 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_5) 126 | self.textTitlePattern = QtWidgets.QLineEdit(parent=self.HomeTab) 127 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 128 | sizePolicy.setHorizontalStretch(0) 129 | sizePolicy.setVerticalStretch(0) 130 | sizePolicy.setHeightForWidth(self.textTitlePattern.sizePolicy().hasHeightForWidth()) 131 | self.textTitlePattern.setSizePolicy(sizePolicy) 132 | self.textTitlePattern.setMinimumSize(QtCore.QSize(0, 30)) 133 | font = QtGui.QFont() 134 | font.setPointSize(10) 135 | self.textTitlePattern.setFont(font) 136 | self.textTitlePattern.setObjectName("textTitlePattern") 137 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textTitlePattern) 138 | self.Lable = QtWidgets.QLabel(parent=self.HomeTab) 139 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 140 | sizePolicy.setHorizontalStretch(0) 141 | sizePolicy.setVerticalStretch(0) 142 | sizePolicy.setHeightForWidth(self.Lable.sizePolicy().hasHeightForWidth()) 143 | self.Lable.setSizePolicy(sizePolicy) 144 | self.Lable.setObjectName("Lable") 145 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.Lable) 146 | self.textTitle = QtWidgets.QLineEdit(parent=self.HomeTab) 147 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 148 | sizePolicy.setHorizontalStretch(0) 149 | sizePolicy.setVerticalStretch(0) 150 | sizePolicy.setHeightForWidth(self.textTitle.sizePolicy().hasHeightForWidth()) 151 | self.textTitle.setSizePolicy(sizePolicy) 152 | self.textTitle.setMinimumSize(QtCore.QSize(0, 30)) 153 | font = QtGui.QFont() 154 | font.setPointSize(10) 155 | self.textTitle.setFont(font) 156 | self.textTitle.setObjectName("textTitle") 157 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textTitle) 158 | self.label_7 = QtWidgets.QLabel(parent=self.HomeTab) 159 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 160 | sizePolicy.setHorizontalStretch(0) 161 | sizePolicy.setVerticalStretch(0) 162 | sizePolicy.setHeightForWidth(self.label_7.sizePolicy().hasHeightForWidth()) 163 | self.label_7.setSizePolicy(sizePolicy) 164 | self.label_7.setObjectName("label_7") 165 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_7) 166 | self.textPoster = QtWidgets.QLineEdit(parent=self.HomeTab) 167 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 168 | sizePolicy.setHorizontalStretch(0) 169 | sizePolicy.setVerticalStretch(0) 170 | sizePolicy.setHeightForWidth(self.textPoster.sizePolicy().hasHeightForWidth()) 171 | self.textPoster.setSizePolicy(sizePolicy) 172 | self.textPoster.setMinimumSize(QtCore.QSize(0, 30)) 173 | font = QtGui.QFont() 174 | font.setPointSize(10) 175 | self.textPoster.setFont(font) 176 | self.textPoster.setObjectName("textPoster") 177 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textPoster) 178 | self.label_8 = QtWidgets.QLabel(parent=self.HomeTab) 179 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 180 | sizePolicy.setHorizontalStretch(0) 181 | sizePolicy.setVerticalStretch(0) 182 | sizePolicy.setHeightForWidth(self.label_8.sizePolicy().hasHeightForWidth()) 183 | self.label_8.setSizePolicy(sizePolicy) 184 | self.label_8.setObjectName("label_8") 185 | self.formLayout.setWidget(5, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_8) 186 | self.textAbout = QtWidgets.QLineEdit(parent=self.HomeTab) 187 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 188 | sizePolicy.setHorizontalStretch(0) 189 | sizePolicy.setVerticalStretch(0) 190 | sizePolicy.setHeightForWidth(self.textAbout.sizePolicy().hasHeightForWidth()) 191 | self.textAbout.setSizePolicy(sizePolicy) 192 | self.textAbout.setMinimumSize(QtCore.QSize(0, 30)) 193 | font = QtGui.QFont() 194 | font.setPointSize(10) 195 | self.textAbout.setFont(font) 196 | self.textAbout.setObjectName("textAbout") 197 | self.formLayout.setWidget(5, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textAbout) 198 | self.textTags = QtWidgets.QLineEdit(parent=self.HomeTab) 199 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 200 | sizePolicy.setHorizontalStretch(0) 201 | sizePolicy.setVerticalStretch(0) 202 | sizePolicy.setHeightForWidth(self.textTags.sizePolicy().hasHeightForWidth()) 203 | self.textTags.setSizePolicy(sizePolicy) 204 | self.textTags.setMinimumSize(QtCore.QSize(0, 30)) 205 | font = QtGui.QFont() 206 | font.setPointSize(10) 207 | self.textTags.setFont(font) 208 | self.textTags.setPlaceholderText("") 209 | self.textTags.setObjectName("textTags") 210 | self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textTags) 211 | self.label_18 = QtWidgets.QLabel(parent=self.HomeTab) 212 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 213 | sizePolicy.setHorizontalStretch(0) 214 | sizePolicy.setVerticalStretch(0) 215 | sizePolicy.setHeightForWidth(self.label_18.sizePolicy().hasHeightForWidth()) 216 | self.label_18.setSizePolicy(sizePolicy) 217 | self.label_18.setObjectName("label_18") 218 | self.formLayout.setWidget(6, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_18) 219 | self.gridLayout.addLayout(self.formLayout, 4, 0, 1, 9) 220 | self.formLayout_6 = QtWidgets.QFormLayout() 221 | self.formLayout_6.setObjectName("formLayout_6") 222 | self.label_2 = QtWidgets.QLabel(parent=self.HomeTab) 223 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 224 | sizePolicy.setHorizontalStretch(0) 225 | sizePolicy.setVerticalStretch(0) 226 | sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) 227 | self.label_2.setSizePolicy(sizePolicy) 228 | self.label_2.setObjectName("label_2") 229 | self.formLayout_6.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_2) 230 | self.textTemplateName = QtWidgets.QLineEdit(parent=self.HomeTab) 231 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 232 | sizePolicy.setHorizontalStretch(0) 233 | sizePolicy.setVerticalStretch(0) 234 | sizePolicy.setHeightForWidth(self.textTemplateName.sizePolicy().hasHeightForWidth()) 235 | self.textTemplateName.setSizePolicy(sizePolicy) 236 | self.textTemplateName.setObjectName("textTemplateName") 237 | self.formLayout_6.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textTemplateName) 238 | self.label = QtWidgets.QLabel(parent=self.HomeTab) 239 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 240 | sizePolicy.setHorizontalStretch(0) 241 | sizePolicy.setVerticalStretch(0) 242 | sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) 243 | self.label.setSizePolicy(sizePolicy) 244 | self.label.setObjectName("label") 245 | self.formLayout_6.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label) 246 | self.menuTemplateSelection = QtWidgets.QComboBox(parent=self.HomeTab) 247 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 248 | sizePolicy.setHorizontalStretch(0) 249 | sizePolicy.setVerticalStretch(0) 250 | sizePolicy.setHeightForWidth(self.menuTemplateSelection.sizePolicy().hasHeightForWidth()) 251 | self.menuTemplateSelection.setSizePolicy(sizePolicy) 252 | self.menuTemplateSelection.setObjectName("menuTemplateSelection") 253 | self.menuTemplateSelection.addItem("") 254 | self.menuTemplateSelection.setItemText(0, "") 255 | self.formLayout_6.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.menuTemplateSelection) 256 | self.label_3 = QtWidgets.QLabel(parent=self.HomeTab) 257 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 258 | sizePolicy.setHorizontalStretch(0) 259 | sizePolicy.setVerticalStretch(0) 260 | sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) 261 | self.label_3.setSizePolicy(sizePolicy) 262 | self.label_3.setObjectName("label_3") 263 | self.formLayout_6.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_3) 264 | self.textTorrentPath = QtWidgets.QLineEdit(parent=self.HomeTab) 265 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 266 | sizePolicy.setHorizontalStretch(0) 267 | sizePolicy.setVerticalStretch(0) 268 | sizePolicy.setHeightForWidth(self.textTorrentPath.sizePolicy().hasHeightForWidth()) 269 | self.textTorrentPath.setSizePolicy(sizePolicy) 270 | self.textTorrentPath.setMinimumSize(QtCore.QSize(0, 30)) 271 | font = QtGui.QFont() 272 | font.setPointSize(10) 273 | self.textTorrentPath.setFont(font) 274 | self.textTorrentPath.setReadOnly(True) 275 | self.textTorrentPath.setObjectName("textTorrentPath") 276 | self.formLayout_6.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textTorrentPath) 277 | self.gridLayout.addLayout(self.formLayout_6, 0, 0, 4, 8) 278 | self.buttonSaveTemplate = QtWidgets.QPushButton(parent=self.HomeTab) 279 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 280 | sizePolicy.setHorizontalStretch(0) 281 | sizePolicy.setVerticalStretch(0) 282 | sizePolicy.setHeightForWidth(self.buttonSaveTemplate.sizePolicy().hasHeightForWidth()) 283 | self.buttonSaveTemplate.setSizePolicy(sizePolicy) 284 | self.buttonSaveTemplate.setObjectName("buttonSaveTemplate") 285 | self.gridLayout.addWidget(self.buttonSaveTemplate, 2, 8, 1, 1) 286 | self.label_9 = QtWidgets.QLabel(parent=self.HomeTab) 287 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 288 | sizePolicy.setHorizontalStretch(0) 289 | sizePolicy.setVerticalStretch(0) 290 | sizePolicy.setHeightForWidth(self.label_9.sizePolicy().hasHeightForWidth()) 291 | self.label_9.setSizePolicy(sizePolicy) 292 | self.label_9.setObjectName("label_9") 293 | self.gridLayout.addWidget(self.label_9, 5, 0, 1, 1) 294 | spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) 295 | self.gridLayout.addItem(spacerItem1, 5, 3, 1, 1) 296 | self.label_22 = QtWidgets.QLabel(parent=self.HomeTab) 297 | self.label_22.setTextFormat(QtCore.Qt.TextFormat.RichText) 298 | self.label_22.setObjectName("label_22") 299 | self.gridLayout.addWidget(self.label_22, 5, 5, 1, 1) 300 | self.buttonPreviewMarkdown = QtWidgets.QPushButton(parent=self.HomeTab) 301 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 302 | sizePolicy.setHorizontalStretch(0) 303 | sizePolicy.setVerticalStretch(0) 304 | sizePolicy.setHeightForWidth(self.buttonPreviewMarkdown.sizePolicy().hasHeightForWidth()) 305 | self.buttonPreviewMarkdown.setSizePolicy(sizePolicy) 306 | self.buttonPreviewMarkdown.setObjectName("buttonPreviewMarkdown") 307 | self.gridLayout.addWidget(self.buttonPreviewMarkdown, 5, 6, 1, 3) 308 | self.buttonBrowse = QtWidgets.QPushButton(parent=self.HomeTab) 309 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 310 | sizePolicy.setHorizontalStretch(0) 311 | sizePolicy.setVerticalStretch(0) 312 | sizePolicy.setHeightForWidth(self.buttonBrowse.sizePolicy().hasHeightForWidth()) 313 | self.buttonBrowse.setSizePolicy(sizePolicy) 314 | self.buttonBrowse.setObjectName("buttonBrowse") 315 | self.gridLayout.addWidget(self.buttonBrowse, 0, 8, 1, 1) 316 | self.fileTree = QtWidgets.QTreeWidget(parent=self.HomeTab) 317 | self.fileTree.setMinimumSize(QtCore.QSize(0, 150)) 318 | self.fileTree.setObjectName("fileTree") 319 | self.fileTree.header().setSortIndicatorShown(True) 320 | self.gridLayout.addWidget(self.fileTree, 9, 0, 1, 9) 321 | self.buttonOKP = QtWidgets.QPushButton(parent=self.HomeTab) 322 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 323 | sizePolicy.setHorizontalStretch(0) 324 | sizePolicy.setVerticalStretch(0) 325 | sizePolicy.setHeightForWidth(self.buttonOKP.sizePolicy().hasHeightForWidth()) 326 | self.buttonOKP.setSizePolicy(sizePolicy) 327 | font = QtGui.QFont() 328 | font.setPointSize(20) 329 | self.buttonOKP.setFont(font) 330 | self.buttonOKP.setObjectName("buttonOKP") 331 | self.gridLayout.addWidget(self.buttonOKP, 10, 0, 1, 9) 332 | self.formLayout_2 = QtWidgets.QFormLayout() 333 | self.formLayout_2.setFieldGrowthPolicy(QtWidgets.QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow) 334 | self.formLayout_2.setObjectName("formLayout_2") 335 | self.label_12 = QtWidgets.QLabel(parent=self.HomeTab) 336 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 337 | sizePolicy.setHorizontalStretch(0) 338 | sizePolicy.setVerticalStretch(0) 339 | sizePolicy.setHeightForWidth(self.label_12.sizePolicy().hasHeightForWidth()) 340 | self.label_12.setSizePolicy(sizePolicy) 341 | self.label_12.setObjectName("label_12") 342 | self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_12) 343 | self.menuSelectCookies = QtWidgets.QComboBox(parent=self.HomeTab) 344 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 345 | sizePolicy.setHorizontalStretch(0) 346 | sizePolicy.setVerticalStretch(0) 347 | sizePolicy.setHeightForWidth(self.menuSelectCookies.sizePolicy().hasHeightForWidth()) 348 | self.menuSelectCookies.setSizePolicy(sizePolicy) 349 | self.menuSelectCookies.setObjectName("menuSelectCookies") 350 | self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.menuSelectCookies) 351 | self.checkboxDmhyPublish = QtWidgets.QCheckBox(parent=self.HomeTab) 352 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 353 | sizePolicy.setHorizontalStretch(0) 354 | sizePolicy.setVerticalStretch(0) 355 | sizePolicy.setHeightForWidth(self.checkboxDmhyPublish.sizePolicy().hasHeightForWidth()) 356 | self.checkboxDmhyPublish.setSizePolicy(sizePolicy) 357 | self.checkboxDmhyPublish.setObjectName("checkboxDmhyPublish") 358 | self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.checkboxDmhyPublish) 359 | self.checkboxBangumiPublish = QtWidgets.QCheckBox(parent=self.HomeTab) 360 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 361 | sizePolicy.setHorizontalStretch(0) 362 | sizePolicy.setVerticalStretch(0) 363 | sizePolicy.setHeightForWidth(self.checkboxBangumiPublish.sizePolicy().hasHeightForWidth()) 364 | self.checkboxBangumiPublish.setSizePolicy(sizePolicy) 365 | self.checkboxBangumiPublish.setObjectName("checkboxBangumiPublish") 366 | self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.checkboxBangumiPublish) 367 | self.checkboxNyaaPublish = QtWidgets.QCheckBox(parent=self.HomeTab) 368 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 369 | sizePolicy.setHorizontalStretch(0) 370 | sizePolicy.setVerticalStretch(0) 371 | sizePolicy.setHeightForWidth(self.checkboxNyaaPublish.sizePolicy().hasHeightForWidth()) 372 | self.checkboxNyaaPublish.setSizePolicy(sizePolicy) 373 | self.checkboxNyaaPublish.setObjectName("checkboxNyaaPublish") 374 | self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.checkboxNyaaPublish) 375 | self.checkboxAcgripPublish = QtWidgets.QCheckBox(parent=self.HomeTab) 376 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 377 | sizePolicy.setHorizontalStretch(0) 378 | sizePolicy.setVerticalStretch(0) 379 | sizePolicy.setHeightForWidth(self.checkboxAcgripPublish.sizePolicy().hasHeightForWidth()) 380 | self.checkboxAcgripPublish.setSizePolicy(sizePolicy) 381 | self.checkboxAcgripPublish.setObjectName("checkboxAcgripPublish") 382 | self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.checkboxAcgripPublish) 383 | self.checkboxAcgnxasiaPublish = QtWidgets.QCheckBox(parent=self.HomeTab) 384 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 385 | sizePolicy.setHorizontalStretch(0) 386 | sizePolicy.setVerticalStretch(0) 387 | sizePolicy.setHeightForWidth(self.checkboxAcgnxasiaPublish.sizePolicy().hasHeightForWidth()) 388 | self.checkboxAcgnxasiaPublish.setSizePolicy(sizePolicy) 389 | self.checkboxAcgnxasiaPublish.setObjectName("checkboxAcgnxasiaPublish") 390 | self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.checkboxAcgnxasiaPublish) 391 | self.checkboxAcgnxglobalPublish = QtWidgets.QCheckBox(parent=self.HomeTab) 392 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 393 | sizePolicy.setHorizontalStretch(0) 394 | sizePolicy.setVerticalStretch(0) 395 | sizePolicy.setHeightForWidth(self.checkboxAcgnxglobalPublish.sizePolicy().hasHeightForWidth()) 396 | self.checkboxAcgnxglobalPublish.setSizePolicy(sizePolicy) 397 | self.checkboxAcgnxglobalPublish.setObjectName("checkboxAcgnxglobalPublish") 398 | self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.checkboxAcgnxglobalPublish) 399 | self.gridLayout.addLayout(self.formLayout_2, 8, 0, 1, 9) 400 | self.tab.addTab(self.HomeTab, "") 401 | self.CookiesManagerTab = QtWidgets.QWidget() 402 | self.CookiesManagerTab.setObjectName("CookiesManagerTab") 403 | self.gridLayout_3 = QtWidgets.QGridLayout(self.CookiesManagerTab) 404 | self.gridLayout_3.setObjectName("gridLayout_3") 405 | self.formLayout_3 = QtWidgets.QFormLayout() 406 | self.formLayout_3.setObjectName("formLayout_3") 407 | self.label_16 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 408 | self.label_16.setObjectName("label_16") 409 | self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_16) 410 | self.label_17 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 411 | self.label_17.setObjectName("label_17") 412 | self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.label_17) 413 | self.buttonDmhyLogin = QtWidgets.QPushButton(parent=self.CookiesManagerTab) 414 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) 415 | sizePolicy.setHorizontalStretch(0) 416 | sizePolicy.setVerticalStretch(0) 417 | sizePolicy.setHeightForWidth(self.buttonDmhyLogin.sizePolicy().hasHeightForWidth()) 418 | self.buttonDmhyLogin.setSizePolicy(sizePolicy) 419 | self.buttonDmhyLogin.setObjectName("buttonDmhyLogin") 420 | self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.buttonDmhyLogin) 421 | self.textDmhyName = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 422 | self.textDmhyName.setObjectName("textDmhyName") 423 | self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textDmhyName) 424 | self.buttonNyaaLogin = QtWidgets.QPushButton(parent=self.CookiesManagerTab) 425 | self.buttonNyaaLogin.setObjectName("buttonNyaaLogin") 426 | self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.buttonNyaaLogin) 427 | self.textNyaaName = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 428 | self.textNyaaName.setReadOnly(False) 429 | self.textNyaaName.setClearButtonEnabled(False) 430 | self.textNyaaName.setObjectName("textNyaaName") 431 | self.formLayout_3.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textNyaaName) 432 | self.buttonAcgripLogin = QtWidgets.QPushButton(parent=self.CookiesManagerTab) 433 | self.buttonAcgripLogin.setObjectName("buttonAcgripLogin") 434 | self.formLayout_3.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.buttonAcgripLogin) 435 | self.textAcgripName = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 436 | self.textAcgripName.setObjectName("textAcgripName") 437 | self.formLayout_3.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textAcgripName) 438 | self.buttonBangumiLogin = QtWidgets.QPushButton(parent=self.CookiesManagerTab) 439 | self.buttonBangumiLogin.setObjectName("buttonBangumiLogin") 440 | self.formLayout_3.setWidget(4, QtWidgets.QFormLayout.ItemRole.LabelRole, self.buttonBangumiLogin) 441 | self.textBangumiName = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 442 | self.textBangumiName.setObjectName("textBangumiName") 443 | self.formLayout_3.setWidget(4, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textBangumiName) 444 | self.acgnx_asiaLabel = QtWidgets.QLabel(parent=self.CookiesManagerTab) 445 | self.acgnx_asiaLabel.setObjectName("acgnx_asiaLabel") 446 | self.formLayout_3.setWidget(5, QtWidgets.QFormLayout.ItemRole.LabelRole, self.acgnx_asiaLabel) 447 | self.textAcgnxasiaName = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 448 | self.textAcgnxasiaName.setObjectName("textAcgnxasiaName") 449 | self.formLayout_3.setWidget(5, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textAcgnxasiaName) 450 | self.textAcgnxasiaToken = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 451 | self.textAcgnxasiaToken.setObjectName("textAcgnxasiaToken") 452 | self.formLayout_3.setWidget(6, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textAcgnxasiaToken) 453 | self.acgnx_globalLabel = QtWidgets.QLabel(parent=self.CookiesManagerTab) 454 | self.acgnx_globalLabel.setObjectName("acgnx_globalLabel") 455 | self.formLayout_3.setWidget(8, QtWidgets.QFormLayout.ItemRole.LabelRole, self.acgnx_globalLabel) 456 | self.textAcgnxglobalName = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 457 | self.textAcgnxglobalName.setObjectName("textAcgnxglobalName") 458 | self.formLayout_3.setWidget(8, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textAcgnxglobalName) 459 | self.textAcgnxglobalToken = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 460 | self.textAcgnxglobalToken.setObjectName("textAcgnxglobalToken") 461 | self.formLayout_3.setWidget(9, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textAcgnxglobalToken) 462 | self.label_11 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 463 | self.label_11.setObjectName("label_11") 464 | self.formLayout_3.setWidget(6, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_11) 465 | self.label_19 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 466 | self.label_19.setObjectName("label_19") 467 | self.formLayout_3.setWidget(9, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_19) 468 | self.gridLayout_3.addLayout(self.formLayout_3, 8, 0, 1, 4) 469 | self.textCookies = QtWidgets.QTextEdit(parent=self.CookiesManagerTab) 470 | self.textCookies.viewport().setProperty("cursor", QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor)) 471 | self.textCookies.setDocumentTitle("") 472 | self.textCookies.setLineWrapMode(QtWidgets.QTextEdit.LineWrapMode.NoWrap) 473 | self.textCookies.setObjectName("textCookies") 474 | self.gridLayout_3.addWidget(self.textCookies, 11, 0, 1, 4) 475 | self.label_14 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 476 | self.label_14.setObjectName("label_14") 477 | self.gridLayout_3.addWidget(self.label_14, 10, 0, 1, 1) 478 | self.label_6 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 479 | self.label_6.setObjectName("label_6") 480 | self.gridLayout_3.addWidget(self.label_6, 10, 1, 1, 3) 481 | self.buttonDeleteProfile = QtWidgets.QPushButton(parent=self.CookiesManagerTab) 482 | self.buttonDeleteProfile.setStyleSheet("color: rgb(255, 0, 30);\n" 483 | "font: 12pt \"Microsoft YaHei UI\";") 484 | self.buttonDeleteProfile.setObjectName("buttonDeleteProfile") 485 | self.gridLayout_3.addWidget(self.buttonDeleteProfile, 0, 3, 1, 1) 486 | spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) 487 | self.gridLayout_3.addItem(spacerItem2, 9, 1, 1, 1) 488 | self.buttonSaveProfile = QtWidgets.QPushButton(parent=self.CookiesManagerTab) 489 | self.buttonSaveProfile.setObjectName("buttonSaveProfile") 490 | self.gridLayout_3.addWidget(self.buttonSaveProfile, 1, 3, 1, 1) 491 | self.formLayout_7 = QtWidgets.QFormLayout() 492 | self.formLayout_7.setObjectName("formLayout_7") 493 | self.label_13 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 494 | self.label_13.setObjectName("label_13") 495 | self.formLayout_7.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_13) 496 | self.menuProfileSelection = QtWidgets.QComboBox(parent=self.CookiesManagerTab) 497 | self.menuProfileSelection.setObjectName("menuProfileSelection") 498 | self.menuProfileSelection.addItem("") 499 | self.formLayout_7.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.menuProfileSelection) 500 | self.label_15 = QtWidgets.QLabel(parent=self.CookiesManagerTab) 501 | self.label_15.setObjectName("label_15") 502 | self.formLayout_7.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_15) 503 | self.textProfileName = QtWidgets.QLineEdit(parent=self.CookiesManagerTab) 504 | self.textProfileName.setObjectName("textProfileName") 505 | self.formLayout_7.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textProfileName) 506 | self.gridLayout_3.addLayout(self.formLayout_7, 0, 0, 2, 2) 507 | self.tab.addTab(self.CookiesManagerTab, "") 508 | self.ProxyTab = QtWidgets.QWidget() 509 | self.ProxyTab.setObjectName("ProxyTab") 510 | self.formLayout_4 = QtWidgets.QFormLayout(self.ProxyTab) 511 | self.formLayout_4.setObjectName("formLayout_4") 512 | self.Label = QtWidgets.QLabel(parent=self.ProxyTab) 513 | self.Label.setObjectName("Label") 514 | self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.Label) 515 | self.menuProxyType = QtWidgets.QComboBox(parent=self.ProxyTab) 516 | self.menuProxyType.setObjectName("menuProxyType") 517 | self.menuProxyType.addItem("") 518 | self.menuProxyType.addItem("") 519 | self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.menuProxyType) 520 | self.hostLabel = QtWidgets.QLabel(parent=self.ProxyTab) 521 | self.hostLabel.setObjectName("hostLabel") 522 | self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.hostLabel) 523 | self.textProxyHost = QtWidgets.QLineEdit(parent=self.ProxyTab) 524 | self.textProxyHost.setPlaceholderText("") 525 | self.textProxyHost.setObjectName("textProxyHost") 526 | self.formLayout_4.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.textProxyHost) 527 | self.buttonSaveProxy = QtWidgets.QPushButton(parent=self.ProxyTab) 528 | self.buttonSaveProxy.setObjectName("buttonSaveProxy") 529 | self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.buttonSaveProxy) 530 | self.textAboutProgram = QtWidgets.QLabel(parent=self.ProxyTab) 531 | self.textAboutProgram.setAutoFillBackground(True) 532 | self.textAboutProgram.setTextFormat(QtCore.Qt.TextFormat.RichText) 533 | self.textAboutProgram.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) 534 | self.textAboutProgram.setOpenExternalLinks(True) 535 | self.textAboutProgram.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextBrowserInteraction) 536 | self.textAboutProgram.setObjectName("textAboutProgram") 537 | self.formLayout_4.setWidget(7, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.textAboutProgram) 538 | self.label_20 = QtWidgets.QLabel(parent=self.ProxyTab) 539 | self.label_20.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) 540 | self.label_20.setObjectName("label_20") 541 | self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.label_20) 542 | self.label_21 = QtWidgets.QLabel(parent=self.ProxyTab) 543 | self.label_21.setAutoFillBackground(True) 544 | self.label_21.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) 545 | self.label_21.setObjectName("label_21") 546 | self.formLayout_4.setWidget(6, QtWidgets.QFormLayout.ItemRole.SpanningRole, self.label_21) 547 | spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) 548 | self.formLayout_4.setItem(5, QtWidgets.QFormLayout.ItemRole.SpanningRole, spacerItem3) 549 | spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) 550 | self.formLayout_4.setItem(4, QtWidgets.QFormLayout.ItemRole.SpanningRole, spacerItem4) 551 | self.tab.addTab(self.ProxyTab, "") 552 | self.gridLayout_2.addWidget(self.tab, 0, 0, 1, 1) 553 | MainWindow.setCentralWidget(self.centralwidget) 554 | 555 | self.retranslateUi(MainWindow) 556 | self.tab.setCurrentIndex(0) 557 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 558 | 559 | def retranslateUi(self, MainWindow): 560 | _translate = QtCore.QCoreApplication.translate 561 | MainWindow.setWindowTitle(_translate("MainWindow", "OKPGUI by AmusementClub")) 562 | self.buttonDeleteTemplate.setText(_translate("MainWindow", "删除模板")) 563 | self.label_10.setText(_translate("MainWindow", "请使用 Markdown 编写")) 564 | self.label_4.setText(_translate("MainWindow", "集数匹配")) 565 | self.textEpPattern.setToolTip(_translate("MainWindow", "

测试

")) 566 | self.textEpPattern.setWhatsThis(_translate("MainWindow", "

测试

")) 567 | self.textEpPattern.setPlaceholderText(_translate("MainWindow", "在文件名集数部分使用 替换,分辨率用 替换")) 568 | self.label_5.setText(_translate("MainWindow", "标题匹配")) 569 | self.Lable.setText(_translate("MainWindow", "标题")) 570 | self.label_7.setText(_translate("MainWindow", "海报链接")) 571 | self.textPoster.setPlaceholderText(_translate("MainWindow", "For dmhy.org")) 572 | self.label_8.setText(_translate("MainWindow", "关于")) 573 | self.textAbout.setPlaceholderText(_translate("MainWindow", "For nyaa.si")) 574 | self.textTags.setToolTip(_translate("MainWindow", "输入标签,以英文逗号分隔,可用标签可参考 “如何使用 tags?”")) 575 | self.label_18.setText(_translate("MainWindow", "Tags")) 576 | self.label_2.setText(_translate("MainWindow", "模板名称")) 577 | self.label.setText(_translate("MainWindow", "选择模板")) 578 | self.menuTemplateSelection.setWhatsThis(_translate("MainWindow", "

第一次使用请选择「新模板」

")) 579 | self.label_3.setText(_translate("MainWindow", "种子文件")) 580 | self.textTorrentPath.setPlaceholderText(_translate("MainWindow", "可直接 .torrent 文件拖放到此处")) 581 | self.buttonSaveTemplate.setText(_translate("MainWindow", "保存模板")) 582 | self.label_9.setText(_translate("MainWindow", "内容")) 583 | self.label_22.setText(_translate("MainWindow", "

如何使用 tags?

")) 584 | self.buttonPreviewMarkdown.setText(_translate("MainWindow", "预览")) 585 | self.buttonBrowse.setText(_translate("MainWindow", "浏览")) 586 | self.fileTree.setSortingEnabled(True) 587 | self.fileTree.headerItem().setText(0, _translate("MainWindow", "Files")) 588 | self.fileTree.headerItem().setText(1, _translate("MainWindow", "Size")) 589 | self.buttonOKP.setText(_translate("MainWindow", "One Key Publish!")) 590 | self.label_12.setText(_translate("MainWindow", "选择身份")) 591 | self.checkboxDmhyPublish.setText(_translate("MainWindow", "dmhy")) 592 | self.checkboxBangumiPublish.setText(_translate("MainWindow", "bangumi")) 593 | self.checkboxNyaaPublish.setText(_translate("MainWindow", "nyaa")) 594 | self.checkboxAcgripPublish.setText(_translate("MainWindow", "acg.rip")) 595 | self.checkboxAcgnxasiaPublish.setText(_translate("MainWindow", "acgnx_asia")) 596 | self.checkboxAcgnxglobalPublish.setText(_translate("MainWindow", "acgnx_global")) 597 | self.tab.setTabText(self.tab.indexOf(self.HomeTab), _translate("MainWindow", "主页")) 598 | self.label_16.setText(_translate("MainWindow", "登录发布网站")) 599 | self.label_17.setText(_translate("MainWindow", "发布组名称")) 600 | self.buttonDmhyLogin.setText(_translate("MainWindow", "dmhy")) 601 | self.buttonNyaaLogin.setText(_translate("MainWindow", "nyaa")) 602 | self.buttonAcgripLogin.setText(_translate("MainWindow", "acg.rip")) 603 | self.buttonBangumiLogin.setText(_translate("MainWindow", "bangumi")) 604 | self.acgnx_asiaLabel.setText(_translate("MainWindow", "acgnx_asia UID")) 605 | self.textAcgnxasiaName.setPlaceholderText(_translate("MainWindow", "填写 acgnx UID")) 606 | self.textAcgnxasiaToken.setPlaceholderText(_translate("MainWindow", "填写 acgnx asia API Token")) 607 | self.acgnx_globalLabel.setText(_translate("MainWindow", "acgnx_global UID")) 608 | self.textAcgnxglobalName.setPlaceholderText(_translate("MainWindow", "填写 acgnx UID")) 609 | self.textAcgnxglobalToken.setPlaceholderText(_translate("MainWindow", "填写 acgnx global API Token")) 610 | self.label_11.setText(_translate("MainWindow", "Token")) 611 | self.label_19.setText(_translate("MainWindow", "Token")) 612 | self.label_14.setText(_translate("MainWindow", "Cookies 文件:")) 613 | self.label_6.setText(_translate("MainWindow", "本页中的内容需要保存身份后才会生效。")) 614 | self.buttonDeleteProfile.setText(_translate("MainWindow", "删除身份")) 615 | self.buttonSaveProfile.setText(_translate("MainWindow", "保存身份")) 616 | self.label_13.setText(_translate("MainWindow", "选择身份")) 617 | self.menuProfileSelection.setWhatsThis(_translate("MainWindow", "

第一次使用请选择「新模板」

")) 618 | self.menuProfileSelection.setItemText(0, _translate("MainWindow", "新身份")) 619 | self.label_15.setText(_translate("MainWindow", "身份名称")) 620 | self.tab.setTabText(self.tab.indexOf(self.CookiesManagerTab), _translate("MainWindow", "身份管理器")) 621 | self.Label.setText(_translate("MainWindow", "代理类型")) 622 | self.menuProxyType.setItemText(0, _translate("MainWindow", "不使用代理")) 623 | self.menuProxyType.setItemText(1, _translate("MainWindow", "HTTP")) 624 | self.hostLabel.setText(_translate("MainWindow", "Host")) 625 | self.textProxyHost.setText(_translate("MainWindow", "http://127.0.0.1:7890")) 626 | self.buttonSaveProxy.setText(_translate("MainWindow", "应用")) 627 | self.textAboutProgram.setText(_translate("MainWindow", "\n" 628 | "\n" 631 | "

此软件为 OKP 的 GUI,由娱乐部制作,用于快速在多个 BT 资源站发布种子。

\n" 632 | "

使用方法参见 GitHub 上的 README

\n" 633 | "


\n" 634 | "

Version: 0.0.1 Alpha 内部测试版。

\n" 635 | "


\n" 636 | "


\n" 637 | "


\n" 638 | "


\n" 639 | "


\n" 640 | "

作者:tastySugar

\n" 641 | "


\n" 642 | "


")) 643 | self.label_20.setText(_translate("MainWindow", "代理设置")) 644 | self.label_21.setText(_translate("MainWindow", "关于")) 645 | self.tab.setTabText(self.tab.indexOf(self.ProxyTab), _translate("MainWindow", "杂项")) 646 | 647 | 648 | if __name__ == "__main__": 649 | import sys 650 | app = QtWidgets.QApplication(sys.argv) 651 | MainWindow = QtWidgets.QMainWindow() 652 | ui = Ui_MainWindow() 653 | ui.setupUi(MainWindow) 654 | MainWindow.show() 655 | sys.exit(app.exec()) 656 | -------------------------------------------------------------------------------- /ProcessWindow.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt6.QtCore import pyqtSignal, pyqtSlot, QProcess 4 | from PyQt6.QtGui import QTextCursor, QFont 5 | from PyQt6.QtWidgets import QApplication, QPlainTextEdit, QWidget, QVBoxLayout, QPushButton 6 | import locale 7 | 8 | 9 | class ProcessOutputReader(QProcess): 10 | produce_output = pyqtSignal(str) 11 | 12 | def __init__(self, parent=None): 13 | super().__init__(parent=parent) 14 | 15 | # merge stderr channel into stdout channel 16 | self.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels) 17 | 18 | 19 | # only necessary when stderr channel isn't merged into stdout: 20 | # self._decoder_stderr = codec.makeDecoder() 21 | 22 | self.readyReadStandardOutput.connect(self._ready_read_standard_output) 23 | # only necessary when stderr channel isn't merged into stdout: 24 | # self.readyReadStandardError.connect(self._ready_read_standard_error) 25 | 26 | @pyqtSlot() 27 | def _ready_read_standard_output(self): 28 | raw_bytes = self.readAllStandardOutput() 29 | text = raw_bytes.data().decode(locale.getencoding()) 30 | self.produce_output.emit(text) 31 | 32 | 33 | 34 | class MyConsoleWidget(QPlainTextEdit): 35 | 36 | def __init__(self, parent=None): 37 | super().__init__(parent=parent) 38 | 39 | self.setReadOnly(True) 40 | self.setMaximumBlockCount(10000) # limit console to 10000 lines 41 | 42 | 43 | self._cursor_output = self.textCursor() 44 | 45 | @pyqtSlot(str) 46 | def append_output(self, text): 47 | self._cursor_output.insertText(text) 48 | self.scroll_to_last_line() 49 | 50 | def scroll_to_last_line(self): 51 | cursor = self.textCursor() 52 | cursor.movePosition(QTextCursor.MoveOperation.End) 53 | cursor.movePosition(QTextCursor.MoveOperation.Up if cursor.atBlockStart() else 54 | QTextCursor.MoveOperation.StartOfLine) 55 | self.setTextCursor(cursor) 56 | 57 | class MyConsole(QWidget): 58 | def __init__(self, parentWindow, *args, **kwargs): 59 | super(QWidget, self).__init__(*args, **kwargs) 60 | self.resize(800, 600) 61 | self.vbox = QVBoxLayout(self) 62 | 63 | self.publishButton = QPushButton(self) 64 | self.publishButton.setText("确定") 65 | font = QFont() 66 | font.setPointSize(20) 67 | self.publishButton.setFont(font) 68 | 69 | self.publishButton.clicked.connect(self.onPublishButton) 70 | self.reader = ProcessOutputReader() 71 | self.consoleWidget = MyConsoleWidget() 72 | 73 | self.reader.produce_output.connect(self.consoleWidget.append_output) 74 | 75 | self.vbox.addWidget(self.consoleWidget) 76 | self.vbox.addWidget(self.publishButton) 77 | 78 | self.setWindowTitle("OKP 运行中…") 79 | 80 | #self.reader.start('python', ['test.py']) # start the process 81 | 82 | def onPublishButton(self): 83 | self.consoleWidget.append_output("\n") 84 | self.reader.write(b"\n") 85 | 86 | def start(self, *args, **kargs): 87 | self.reader.start(*args, **kargs) 88 | 89 | def onFinished(self, func): 90 | self.reader.finished.connect(func) 91 | 92 | def closeEvent(self, event): 93 | self.reader.terminate() 94 | 95 | 96 | 97 | 98 | if __name__ == "__main__": 99 | # create the application instance 100 | app = QApplication(sys.argv) 101 | # create a console and connect the process output reader to it 102 | console = MyConsole(None) 103 | console.start('python', ['test.py']) 104 | 105 | console.show() 106 | app.exec() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OKPGUI 2 | 3 | 此软件为 [OKP](https://github.com/AmusementClub/OKP/) 的 GUI,由娱乐部制作,用于快速在多个 BT 资源站发布种子。 4 | 5 | ### 支持站点 6 | 7 | _以下排名无先后_ 8 | 9 | | 站点 | 代号 | 10 | | --------------------------------------------- | ------------ | 11 | | [Nyaa](https://nyaa.si/) | nyaa | 12 | | [動漫花園](https://share.dmhy.org/) | dmhy | 13 | | [ACG.RIP](https://acg.rip/) | acgrip | 14 | | [末日動漫資源庫](https://share.acgnx.se/) | acgnx_asia | 15 | | [AcgnX Torrent Global](https://www.acgnx.se/) | acgnx_global | 16 | | [萌番组](https://bangumi.moe/) | bangumi | 17 | 18 | 注: 19 | 20 | 1. acgrip cookie 失效后会刷新,退出登录疑似会直接失效,ua 不同也会登录失败。 21 | 2. acgnx 站点登录可能会被 Cloudflare 风控,鉴于其站点会同步 nyaa、dmhy、acgrip 的种子,可以选择不使用其上传。 22 | 3. 萌番组暂不支持自定义 TAG,目前仅支持 _Team ID_ 和 setting 中 tags 映射的分类两个 TAG。 23 | 24 | 25 | 26 | ## 使用方法 27 | 28 | **请将 Cookies 视为你的账户密码并妥善保护,任何获取到 Cookies 文件(以及`okpgui_profile.yml`)的人都可以轻易登录你的账户。** 29 | 30 | ### 快速开始 31 | 32 | 1. 将程序`OKPGUI.exe`复制到`OKP.Core.exe`的同一个文件夹下,并打开程序。 33 | [点此下载](https://github.com/AmusementClub/OKP/releases)最新版 OKP,[点此下载](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) OKP 的依赖 .NET 6 Runtime。 34 | > 若你使用的是非 Windows 操作系统,请将 `OKP.Core` 重命名为 `OKP.Core.exe`。 35 | 36 | 2. 第一次使用时,请先使用`身份管理器`创建一个新的`身份`,一个`身份`中记载了你的登录发布站的 Cookies 和 API Token。 37 | 点击`身份管理器`进入身份管理器页面。 38 | 39 | ![](/image/ProfileManager01.jpg) 40 | 41 | 3. 在`身份名称`一栏中给你的身份输入一个名字,例如说 `LoliHouse`。 42 | 43 | 4. 在下方的登录发布网站中,点击左边的网站按钮(例如 dmhy)可以打开网站的登录页面,打开后,输入用户名密码。在登录网站后点击`保存 Cookies`或者直接点击关闭。 44 | ![](image/login.jpg) 45 | 46 | 5. 此时,你可以看到`Cookies 文件`框中多出了一些 cookies 内容。如果你不理解你在做什么,请不要手动改动它。 47 | 48 | 6. 一些网站有多个发布身份,你需要在网站右边的发布组名称框中填入你想选用的发布身份,例如说 `LoliHouse`。 49 | 50 | ![](image/ProfileManager02.jpg) 51 | 52 | 7. 对于 [acgnx_asia](https://share.acgnx.se/) 和 [acgnx_global](https://www.acgnx.se/),请前往「发布资源 - 账户 API 设置」中,将 UID 和 API Token 填入对应的文本框中。注意,在填写后必须点击`保存身份`才会生效。 53 | 54 | ![](image/acgnx.jpg) 55 | 56 | ![](image/ProfileManager03.jpg) 57 | 58 | 8. 在登录完所有需要的网站之后,点击`保存身份`,使身份信息得到保存。 59 | 60 | 9. 然后,我们可以点击`主页`回到主页,并创建一个新的模板。 61 | 62 | 10. 在一开始,我们还是在`模板名称`中给你的模板起个名字,例如说`Onimai`。 63 | 64 | 11. 点击`浏览`打开一个种子文件,或者也可以直接把种子文件拖放到窗口中。 65 | 66 | 12. 在`标题`一栏中填入发布标题,例如说`[SweetSub&LoliHouse] 不当哥哥了!/ Oniichan ha Oshimai! - 01 [WebRip 1080p HEVC-10bit AAC][简繁日内封字幕]` 67 | 68 | 13. 如果要发布在动漫花园,在`海报链接`一栏填入海报的 url,如果要发布在 nyaa.si,可以在`关于`里面填入 about 信息。 69 | 70 | 14. `Tags` 决定了资源在各个网站的分类,所有支持的标签和详细规则请参考 [OKP 的 wiki](https://github.com/AmusementClub/OKP/wiki/TagsConvert)。 71 | > 在填写时以逗号分隔开,例如在发布合集时填写 `Anime, Collection`(逗号后是否空格不会影响程序判断)。平时发布单集时填写 `Anime` 即可。 72 | > 注意: **这不是 bangumi.moe 的 tag 系统。** 73 | 74 | 15. `集数匹配`和`标题匹配`是选填项,其使用方法请[参考此处](#标题匹配)。 75 | 76 | 16. 在`内容`栏中使用 **markdown** 格式填写发布贴的详细内容,填写完毕后点击`预览`按钮可以预览其显示效果。 77 | 78 | > 暂不支持用户使用 html 或 bbcode 79 | 80 | 17. 在`选择身份`的选单中选择发布时使用的身份,在此例子中,我们选择刚才在身份管理器中创建的`LoliHouse`身份,如果需要其他的身份,请移步至身份管理器中创建。 81 | 82 | 18. 在下方需要发布的站点的复选框中打钩。 83 | > 如果发现某个网站不能打钩,说明选择的身份中并没有添加这个网站的信息(Cookies/API Token),请移步身份管理器中编辑身份。 84 | 85 | ![](image/Home01.jpg) 86 | 87 | 19. 发布时不需要保存模板,程序会根据目前文本框中的内容和选项来发布,也就是说,即使有模板,但每次发布前也可以手动微调发布标题或者是内容等。如果想要下次重复利用此发布模板,请别忘记了保存模板。 88 | 89 | 20. 点击`One Key Publish!`呼出 OKP 一键发布,在打开的控制台中,确认发布的标题和种子文件准确无误后,点击确定按钮即可发布。 90 | 91 | ![](image/Console.jpg) 92 | 93 | 94 | --- 95 | 96 | ### 其他功能 97 | 98 | #### 1. 标题匹配 99 | 100 | 此功能是为了可以方便地根据种子文件名中的信息自动生成不同的发布标题。 101 | 102 | 103 | 例如说我们有一个种子文件,其名称为 104 | 105 | ``` 106 | [SweetSub] Oniichan ha Oshimai! - 01 [WebRip][1080P][AVC 8bit][CHS].mp4.torrent 107 | ``` 108 | 109 | 然后我们还有其不同分辨率,不同集数的版本,例如说: 110 | 111 | ``` 112 | [SweetSub] Oniichan ha Oshimai! - 01 [WebRip][720P][AVC 8bit][CHS].mp4.torrent 113 | [SweetSub] Oniichan ha Oshimai! - 02 [WebRip][1080P][AVC 8bit][CHS].mp4.torrent 114 | ``` 115 | 116 | 我们不想重复地手动更改发布标题,这样非常麻烦。标题匹配功能可以在种子文件中寻找需要的信息并填写到标题中。 117 | 118 | ##### 使用方法: 119 | 120 | 在集数匹配一栏中,把文件名中的重要信息用 `<>` 标签替换,并在其中填入字符来命名,例如说 121 | 122 | **集数匹配**: 123 | ``` 124 | [SweetSub] Oniichan ha Oshimai! - [WebRip][P][AVC 8bit] 125 | ``` 126 | 127 | 我们再在标题匹配中以 `<>` 标签替换相应的值 128 | 129 | **标题匹配**: 130 | ``` 131 | [SweetSub][不当哥哥了!][Oniichan ha Oshimai!][][WebRip][P][AVC 8bit][简日双语][无修版] 132 | ``` 133 | 134 | 如果我们此时添加一个种子文件 135 | 136 | 例如: 137 | ``` 138 | [SweetSub] Oniichan ha Oshimai! - 01 [WebRip][720P][AVC 8bit][CHS].mp4.torrent 139 | ``` 140 | 141 | 程序会根据种子文件的`文件名`和`集数匹配`中的字符串来确定 `` 的值为 `01`,`` 的值为 `720` 142 | 143 | 这些值会被自动代入到`标题匹配`的模板中,程序会自动生成发布标题: 144 | 145 | ``` 146 | [SweetSub][不当哥哥了!][Oniichan ha Oshimai!][01][WebRip][720P][AVC 8bit][简日双语][无修版] 147 | ``` 148 | 149 | 注意: 150 | 1. 虽然例子中只用了``和``,但你可以使用任意数量的`<>`标签,里面可以填写任意英文单词。 151 | 2. 因为没有设置转义,所以如果发布标题中使用了`<>`,则不可以使用标题匹配的功能。 152 | 153 | 154 | #### 2. 代理 155 | 156 | 为了方便对抗 GFW,程序内置了 HTTP 代理功能。 157 | 158 | 在`杂项`选项卡中选择代理类型 HTTP,并且在 Host 一栏中填入 HTTP 代理的地址,例如说 `http://127.0.0.1:7890`,然后点击`保存身份`,即可在访问网站时使用代理,同时,该代理也会直接传给 OKP.Core 所使用。 159 | 160 | 注意,代理的改动必须点击`应用`后才会生效。 161 | 162 | ![](image/Proxy.jpg) 163 | 164 | --- 165 | 166 | ## 开发、编译此程序 167 | 168 | 169 | 170 | 请先下载代码或者 clone 此 repository 到本地,然后 cd 到根目录下,使用 python 创建一个虚拟环境,并安装依赖。 171 | 172 | ``` 173 | python -m venv venv 174 | venv\Scripts\activate.bat 175 | pip3 install install setuptools==57.5.0 --upgrade 176 | pip3 install -r requirements.txt 177 | ``` 178 | 179 | 如果使用非 cmd,请将第二步换成对应的脚本。 180 | 181 | 第三步是因为安装 html2phpbbcode 需要 use_2to3 支持。 182 | 183 | 运行 `main.py` 或者 `OKPLogic.py` 即可开始 debug。 184 | 185 | 如果需要改变 UI,请使用 [qt designer](https://build-system.fman.io/qt-designer-download) 打开 OKP.ui 或者其他 .ui 文件,在完成编辑后请调用 `Compile_UI.bat` 来将 .ui 文件编译为 .py 文件。 186 | 187 | 注意:绝对不要手动编辑编译后的 .py 文件,因为重新编译之后所有改动将会丢失。 188 | 189 | 在完成修改后可以调用 `make.bat` 将程序编译为 exe 文件。 190 | 191 | ---- 192 | 193 | ## 常见问题 194 | 195 | 1. 为什么无法勾选某发布网站? 196 | 197 | 如果发现某个网站不能勾选,则说明选择的身份中并没有添加这个网站的信息(Cookies/API Token),请移步身份管理器中编辑身份。 198 | 199 | 2. 在设置好了 cookies 之后无法访问某个发布站。 200 | 201 | a. 请检查是否下载最新版的 OKP.Core。 202 | b. 尝试删除身份管理器中发布站对应的 cookies 信息,点击保存身份,然后重新登录。 203 | 204 | 3. 能否让用户自行提交发布站所需的 html, bbcode 发布内容? 205 | 206 | 作者认为 Less is more,简单的 Markdown 足以满足一般的排版需求,所以暂时不会加入提交 html, bbcode 的功能。 207 | 如有此功能的需求,可以使用 OKP.Core 的 CLI。 208 | 209 | 4. 有 Bug 210 | 211 | 去[此页面](https://github.com/AmusementClub/OKPGUI/issues/new/choose)创建一个 issue 并选择 Bug report,请尽量准确地描述要如何重现此 bug。 212 | 213 | 5. 想要新功能 214 | 215 | 去[此页面](https://github.com/AmusementClub/OKPGUI/issues/new/choose)创建一个 issue 并选择 Feature request。 -------------------------------------------------------------------------------- /WarningDialog.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'WarningDialog.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.4.2 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_Dialog(object): 13 | def setupUi(self, Dialog): 14 | Dialog.setObjectName("Dialog") 15 | Dialog.resize(300, 106) 16 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Expanding) 17 | sizePolicy.setHorizontalStretch(0) 18 | sizePolicy.setVerticalStretch(0) 19 | sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth()) 20 | Dialog.setSizePolicy(sizePolicy) 21 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog) 22 | self.verticalLayout_2.setObjectName("verticalLayout_2") 23 | self.label = QtWidgets.QLabel(parent=Dialog) 24 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) 25 | sizePolicy.setHorizontalStretch(0) 26 | sizePolicy.setVerticalStretch(0) 27 | sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) 28 | self.label.setSizePolicy(sizePolicy) 29 | font = QtGui.QFont() 30 | font.setFamily("Microsoft YaHei UI") 31 | font.setPointSize(15) 32 | font.setBold(True) 33 | font.setWeight(75) 34 | self.label.setFont(font) 35 | self.label.setTextFormat(QtCore.Qt.TextFormat.PlainText) 36 | self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) 37 | self.label.setWordWrap(True) 38 | self.label.setObjectName("label") 39 | self.verticalLayout_2.addWidget(self.label) 40 | self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog) 41 | self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) 42 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok) 43 | self.buttonBox.setObjectName("buttonBox") 44 | self.verticalLayout_2.addWidget(self.buttonBox) 45 | 46 | self.retranslateUi(Dialog) 47 | self.buttonBox.accepted.connect(Dialog.accept) # type: ignore 48 | self.buttonBox.rejected.connect(Dialog.reject) # type: ignore 49 | QtCore.QMetaObject.connectSlotsByName(Dialog) 50 | 51 | def retranslateUi(self, Dialog): 52 | _translate = QtCore.QCoreApplication.translate 53 | Dialog.setWindowTitle(_translate("Dialog", "Dialog")) 54 | self.label.setText(_translate("Dialog", "TextLabel")) 55 | 56 | 57 | if __name__ == "__main__": 58 | import sys 59 | app = QtWidgets.QApplication(sys.argv) 60 | Dialog = QtWidgets.QDialog() 61 | ui = Ui_Dialog() 62 | ui.setupUi(Dialog) 63 | Dialog.show() 64 | sys.exit(app.exec()) 65 | -------------------------------------------------------------------------------- /WarningDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 300 10 | 106 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Dialog 21 | 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 30 | 31 | 32 | 33 | Microsoft YaHei UI 34 | 15 35 | 75 36 | true 37 | 38 | 39 | 40 | TextLabel 41 | 42 | 43 | Qt::PlainText 44 | 45 | 46 | Qt::AlignCenter 47 | 48 | 49 | true 50 | 51 | 52 | 53 | 54 | 55 | 56 | Qt::Horizontal 57 | 58 | 59 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | buttonBox 71 | accepted() 72 | Dialog 73 | accept() 74 | 75 | 76 | 248 77 | 254 78 | 79 | 80 | 157 81 | 274 82 | 83 | 84 | 85 | 86 | buttonBox 87 | rejected() 88 | Dialog 89 | reject() 90 | 91 | 92 | 316 93 | 260 94 | 95 | 96 | 286 97 | 274 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /WebHelper.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt6.QtCore import QUrl, QByteArray, QSize, Qt 4 | from PyQt6.QtWebEngineWidgets import QWebEngineView 5 | from PyQt6.QtWebEngineCore import QWebEngineProfile 6 | from PyQt6.QtWidgets import QApplication, QTextEdit, QPushButton, QToolBar, QMainWindow, QDialog, QWidget, QVBoxLayout 7 | from PyQt6.QtNetwork import QNetworkCookie, QNetworkProxy 8 | from PyQt6.QtGui import QAction 9 | from urllib import parse 10 | import traceback 11 | import datetime 12 | 13 | 14 | def bytestostr(data): 15 | if isinstance(data, str): 16 | return data 17 | if isinstance(data, QByteArray): 18 | data = data.data() 19 | if isinstance(data, bytes): 20 | data = data.decode(errors='ignore') 21 | else: 22 | data = str(data) 23 | return data 24 | 25 | def cookiesToStr(cookie: QNetworkCookie): 26 | domain = cookie.domain() 27 | path = cookie.path() 28 | name = bytestostr(cookie.name().data()) 29 | value = bytestostr(cookie.value().data()) 30 | 31 | flags = [] 32 | flags.append(f"{name}={value}") 33 | flags.append(f"domain={domain}") 34 | flags.append(f"path={path}") 35 | 36 | if cookie.isSecure(): flags.append("Secure") 37 | if cookie.isHttpOnly(): flags.append("HttpOnly") 38 | match cookie.sameSitePolicy(): 39 | case QNetworkCookie.SameSite.Default: 40 | pass 41 | case QNetworkCookie.SameSite.Lax: 42 | flags.append("SameSite=Lax") 43 | case QNetworkCookie.SameSite.Strict: 44 | flags.append("SameSite=Strict") 45 | case QNetworkCookie.SameSite.None_: 46 | flags.append("SameSite=None") 47 | 48 | #now2 = datetime.datetime.fromisoformat(now.toString(Qt.DateFormat.ISODate)) 49 | #strftime("%a, %d %b %Y %H:%M:%S %Z") 50 | if not cookie.isSessionCookie(): 51 | time = cookie.expirationDate().toString(Qt.DateFormat.ISODate) 52 | time = datetime.datetime.fromisoformat(time).strftime("%a, %d %b %Y %H:%M:%S GMT") 53 | flags.append(f"expires={time}") 54 | 55 | flags = "; ".join(flags) 56 | 57 | return f"https://{domain}\t{flags}" 58 | 59 | def filterCookies(cookie: QNetworkCookie) -> bool: 60 | if cookie.domain() == "share.dmhy.org": 61 | if bytestostr(cookie.name().data()) in {"pass", "rsspass", "tid", "uname", "uid"}: 62 | return True 63 | if cookie.domain() == "nyaa.si": 64 | if bytestostr(cookie.name().data()) == "session": 65 | return True 66 | if cookie.domain() == "acg.rip": 67 | if bytestostr(cookie.name().data()) == "_kanako_session": 68 | return True 69 | if cookie.domain() == "bangumi.moe": 70 | if bytestostr(cookie.name().data()) in {"locale", "koa:sess", "koa:sess.sig"}: 71 | return True 72 | return False 73 | 74 | 75 | class WebEngineView(QWidget): 76 | 77 | def __init__(self, url, parentWindow, *args, **kwargs): 78 | super(QWidget, self).__init__(*args, **kwargs) 79 | QWebEngineProfile.defaultProfile().cookieStore().cookieAdded.connect(self.onCookieAdd) 80 | self.resize(1920, 600) 81 | self.parentWindow = parentWindow 82 | self.browser = QWebEngineView() 83 | self.browser.loadFinished.connect(self.onLoadFinished) 84 | 85 | 86 | vbox = QVBoxLayout(self) 87 | 88 | 89 | toolbar = QToolBar('toolbar') 90 | self.saveButton = QAction("保存 cookies", parent=self) 91 | backButton = QAction("后退", parent=self) 92 | refreshButton = QAction("刷新", parent=self) 93 | 94 | backButton.triggered.connect(self.browser.back) 95 | refreshButton.triggered.connect(self.browser.reload) 96 | self.saveButton.triggered.connect(self.saveCookies) 97 | 98 | toolbar.addAction(backButton) 99 | toolbar.addAction(refreshButton) 100 | toolbar.addAction(self.saveButton) 101 | 102 | self.cookies = [] 103 | vbox.addWidget(toolbar) 104 | vbox.addWidget(self.browser) 105 | self.setLayout(vbox) 106 | 107 | if parentWindow.menuProxyType.currentText == "HTTP": 108 | parsed = parse.urlparse(self.parentWindow.profile['proxy']) 109 | self.proxy = QNetworkProxy(QNetworkProxy.ProxyType.HttpProxy, hostName=parsed.hostname, port=parsed.port) 110 | QNetworkProxy.setApplicationProxy(self.proxy) 111 | 112 | 113 | self.browser.load(url) 114 | 115 | 116 | def closeEvent(self, event): 117 | self.parentWindow.addCookies("\n".join(self.cookies)) 118 | self.parentWindow.setUserAgent(self.browser.page().profile().httpUserAgent()) 119 | self.parentWindow.saveProfile() 120 | super(WebEngineView, self).closeEvent(event) 121 | 122 | def onLoadFinished(self): 123 | pass 124 | 125 | def onCookieAdd(self, cookie:QNetworkCookie): 126 | if filterCookies(cookie): 127 | self.cookies.append(cookiesToStr(cookie)) 128 | 129 | 130 | def saveCookies(self): 131 | self.close() 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /image/Console.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/Console.jpg -------------------------------------------------------------------------------- /image/Home01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/Home01.jpg -------------------------------------------------------------------------------- /image/ProfileManager01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/ProfileManager01.jpg -------------------------------------------------------------------------------- /image/ProfileManager02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/ProfileManager02.jpg -------------------------------------------------------------------------------- /image/ProfileManager03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/ProfileManager03.jpg -------------------------------------------------------------------------------- /image/Proxy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/Proxy.jpg -------------------------------------------------------------------------------- /image/acgnx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/acgnx.jpg -------------------------------------------------------------------------------- /image/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/image/login.jpg -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt6.QtWidgets import QApplication 4 | from OKPLogic import OKPMainWIndow 5 | import platform 6 | 7 | 8 | if __name__ == '__main__': 9 | app = QApplication(sys.argv) 10 | if platform.system() != "Windows": 11 | app.setStyle('Fusion') 12 | 13 | window = OKPMainWIndow() 14 | window.show() 15 | sys.exit(app.exec()) 16 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | pyinstaller --onefile --noconsole main.py --collect-all html2phpbbcode --version-file versionfile.rc -n OKPGUI.exe -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/requirements.txt -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | input("do you want to write?") 2 | 3 | print("Nice") -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode-2.3.3-py3.11.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: html2bbcode 3 | Version: 2.3.3 4 | Summary: HTML to BBCode converter 5 | Home-page: https://bitbucket.org/amigo/html2bbcode 6 | Author: Vladimir Korsun 7 | Author-email: korsun.vladimir@gmail.com 8 | License: BSD 9 | Platform: UNKNOWN 10 | Classifier: Topic :: Utilities 11 | Classifier: Topic :: Text Processing :: Markup :: HTML 12 | Classifier: License :: OSI Approved :: BSD License 13 | Classifier: Programming Language :: Python :: 2.7 14 | Classifier: Programming Language :: Python :: 3 15 | 16 | UNKNOWN 17 | 18 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode-2.3.3-py3.11.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.rst 2 | setup.cfg 3 | setup.py 4 | html2bbcode/__init__.py 5 | html2bbcode/parser.py 6 | html2bbcode.egg-info/PKG-INFO 7 | html2bbcode.egg-info/SOURCES.txt 8 | html2bbcode.egg-info/dependency_links.txt 9 | html2bbcode.egg-info/top_level.txt 10 | html2bbcode/data/defaults.conf 11 | scripts/html2bbcode -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode-2.3.3-py3.11.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode-2.3.3-py3.11.egg-info/installed-files.txt: -------------------------------------------------------------------------------- 1 | ..\..\..\Scripts\html2bbcode 2 | ..\html2bbcode\__init__.py 3 | ..\html2bbcode\__pycache__\__init__.cpython-311.pyc 4 | ..\html2bbcode\__pycache__\parser.cpython-311.pyc 5 | ..\html2bbcode\data\defaults.conf 6 | ..\html2bbcode\parser.py 7 | PKG-INFO 8 | SOURCES.txt 9 | dependency_links.txt 10 | top_level.txt 11 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode-2.3.3-py3.11.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | html2bbcode 2 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/venv/Lib/site-packages/html2bbcode/__init__.py -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode/data/defaults.conf: -------------------------------------------------------------------------------- 1 | [a] 2 | start: [url=%(href)s] 3 | end: [/url] 4 | 5 | [img] 6 | start: [img]%(src)s[/img] 7 | end: 8 | 9 | [em] 10 | start: [i] 11 | end: [/i] 12 | 13 | [strong] 14 | start: [b] 15 | end: [/b] 16 | 17 | [del] 18 | start: [s] 19 | end: [/s] 20 | 21 | [ins] 22 | start: [u] 23 | end: [/u] 24 | 25 | [ul] 26 | start: [list] 27 | end: [/list] 28 | 29 | [li] 30 | start: [li] 31 | end: [/li] 32 | 33 | [blockquote] 34 | start: [quote] 35 | end: [/quote] 36 | 37 | [code] 38 | start: [code] 39 | end: [/code] 40 | 41 | [font] 42 | expand: color, face, size 43 | start: 44 | end: 45 | 46 | [color] 47 | start: [color=%(color)s] 48 | end: [/color] 49 | 50 | [size] 51 | start: [size=%(size)s] 52 | end: [/size] 53 | 54 | [face] 55 | start: [font=%(face)s] 56 | end: [/font] 57 | 58 | [br] 59 | start: \n 60 | end: 61 | 62 | [p] 63 | start: \n 64 | end: \n 65 | 66 | [h1] 67 | start: [h1] 68 | end: [/h1] 69 | 70 | [h2] 71 | start: [h2] 72 | end: [/h2] 73 | 74 | [h3] 75 | start: [h3] 76 | end: [/h3] 77 | 78 | [h4] 79 | start: [h4] 80 | end: [/h4] 81 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2bbcode/parser.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from configparser import RawConfigParser 4 | from html.parser import HTMLParser 5 | from collections import defaultdict 6 | from os.path import join, dirname 7 | 8 | 9 | class Attributes(dict): 10 | def __getitem__(self, name): 11 | try: 12 | return super(Attributes, self).__getitem__(name) 13 | except KeyError: 14 | return '' 15 | 16 | 17 | class ConfigParser(RawConfigParser, object): 18 | def get(self, section, option): 19 | value = super(ConfigParser, self).get(section, option) 20 | return value.replace('\\n', '\n') 21 | 22 | 23 | class HTML2BBCode(HTMLParser): 24 | """ 25 | HTML to BBCode converter 26 | 27 | >>> parser = HTML2BBCode() 28 | >>> str(parser.feed('
  • one
  • two
')) 29 | '[list][li]one[/li][li]two[/li][/list]' 30 | 31 | >>> str(parser.feed('Google')) 32 | '[url=http://google.com/]Google[/url]' 33 | 34 | >>> str(parser.feed('')) 35 | '[img]http://www.google.com/images/logo.png[/img]' 36 | 37 | >>> str(parser.feed('EM test')) 38 | '[i]EM test[/i]' 39 | 40 | >>> str(parser.feed('Strong text')) 41 | '[b]Strong text[/b]' 42 | 43 | >>> str(parser.feed('a = 10;')) 44 | '[code]a = 10;[/code]' 45 | 46 | >>> str(parser.feed('
Beautiful is better than ugly.
')) 47 | '[quote]Beautiful is better than ugly.[/quote]' 48 | 49 | >>> str(parser.feed('Text decorations')) 50 | '[font=Arial]Text decorations[/font]' 51 | 52 | >>> str(parser.feed('Text decorations')) 53 | '[size=2]Text decorations[/size]' 54 | 55 | >>> str(parser.feed('Text decorations')) 56 | '[color=red]Text decorations[/color]' 57 | 58 | >>> str(parser.feed('Text decorations')) 59 | '[color=green][font=Arial][size=2]Text decorations[/size][/font][/color]' 60 | 61 | >>> str(parser.feed('Text
break')) 62 | 'Text\\nbreak' 63 | 64 | >>> str(parser.feed(' ')) 65 | ' ' 66 | """ 67 | 68 | def __init__(self, config=None): 69 | HTMLParser.__init__(self) 70 | self.config = ConfigParser(allow_no_value=True) 71 | self.config.read(join(dirname(__file__), 'data/defaults.conf')) 72 | if config: 73 | self.config.read(config) 74 | 75 | def handle_starttag(self, tag, attrs): 76 | if self.config.has_section(tag): 77 | self.attrs[tag].append(dict(attrs)) 78 | self.data.append( 79 | self.config.get(tag, 'start') % Attributes(attrs or {})) 80 | if self.config.has_option(tag, 'expand'): 81 | self.expand_starttags(tag) 82 | 83 | def handle_endtag(self, tag): 84 | if self.config.has_section(tag): 85 | self.data.append(self.config.get(tag, 'end')) 86 | if self.config.has_option(tag, 'expand'): 87 | self.expand_endtags(tag) 88 | self.attrs[tag].pop() 89 | 90 | def handle_data(self, data): 91 | self.data.append(data) 92 | 93 | def feed(self, data): 94 | self.data = [] 95 | self.attrs = defaultdict(list) 96 | HTMLParser.feed(self, data) 97 | return ''.join(self.data) 98 | 99 | def expand_starttags(self, tag): 100 | for expand in self.get_expands(tag): 101 | if expand in self.attrs[tag][-1]: 102 | self.data.append( 103 | self.config.get(expand, 'start') % self.attrs[tag][-1]) 104 | 105 | def expand_endtags(self, tag): 106 | for expand in reversed(self.get_expands(tag)): 107 | if expand in self.attrs[tag][-1]: 108 | self.data.append( 109 | self.config.get(expand, 'end') % self.attrs[tag][-1]) 110 | 111 | def get_expands(self, tag): 112 | expands = self.config.get(tag, 'expand').split(',') 113 | return [x.strip() for x in expands] 114 | 115 | def handle_entityref(self, name): 116 | self.data.append('&{};'.format(name)) 117 | 118 | def handle_charref(self, name): 119 | self.data.append('&#{};'.format(name)) 120 | 121 | 122 | if __name__ == '__main__': 123 | import doctest 124 | 125 | doctest.testmod() 126 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode-0.1.4.dist-info/INSTALLER: -------------------------------------------------------------------------------- 1 | pip 2 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode-0.1.4.dist-info/METADATA: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: html2phpbbcode 3 | Version: 0.1.4 4 | Summary: HTML to phpBB-compatible BBCode converter 5 | Home-page: https://github.com/tdiam/html2phpbbcode 6 | Author: Theodoros Diamantidis 7 | Author-email: diamaltho@gmail.com 8 | License: BSD 9 | Platform: UNKNOWN 10 | Classifier: Topic :: Utilities 11 | Classifier: Topic :: Text Processing :: Markup :: HTML 12 | Classifier: Topic :: Software Development :: Libraries :: Python Modules 13 | Classifier: Development Status :: 2 - Pre-Alpha 14 | Classifier: Intended Audience :: Developers 15 | Classifier: Operating System :: OS Independent 16 | Classifier: License :: OSI Approved :: BSD License 17 | Classifier: Programming Language :: Python :: 3.5 18 | Classifier: Programming Language :: Python :: 3.6 19 | Classifier: Programming Language :: Python :: 3.7 20 | Description-Content-Type: text/markdown 21 | Requires-Dist: html2bbcode 22 | Requires-Dist: regex 23 | 24 | [![Build Status](https://travis-ci.org/tdiam/html2phpbbcode.svg?branch=master)](https://travis-ci.org/tdiam/html2phpbbcode) 25 | [![PyPI version](https://badge.fury.io/py/html2phpbbcode.svg)](https://badge.fury.io/py/html2phpbbcode) 26 | [![License](https://img.shields.io/badge/License-BSD%203--Clause-orange.svg)](https://opensource.org/licenses/BSD-3-Clause) 27 | 28 | # HTML2PHPBBCode 29 | 30 | HTML2PHPBBCode is a Python 3 package that can be used to parse HTML code and convert it to phpBB-compatible BBCode. 31 | 32 | ### Usage 33 | 34 | ```python 35 | >>> from html2phpbbcode.parser import HTML2PHPBBCode 36 | >>> parser = HTML2PHPBBCode() 37 | >>> parser.feed('
  • Hello
  • World
') 38 | '[list][*]Hello[*]World[/list]' 39 | >>> parser.feed('
  1. one
  2. two
') 40 | '[list=1][*]one[*]two[/list]' 41 | >>> parser.feed('Water.org') 42 | '[url=https://water.org]Water.org[/url]' 43 | >>> parser.feed('Mail Water.org') 44 | '[email=info@water.org]Mail Water.org[/email]' 45 | >>> parser.feed('Hello World. It's a wonderful world') 46 | "[b]Hello [i]World[/i]. It's a wonderful world[/b]" 47 | ``` 48 | 49 | ### Acknowledgements 50 | 51 | HTML2PHPBBCode is based on the [html2bbcode](https://bitbucket.org/amigo/html2bbcode) package of [Vladimir Korsun](mailto:korsun.vladimir@gmail.com) which is available under the BSD License. 52 | 53 | The [regex](https://pypi.org/project/regex/) package by [Matthew Barnett](mailto:regex@mrabarnett.plus.com) is also used, available under the Python Software Foundation License. 54 | 55 | The code includes some regular expressions from the [phpBB](https://github.com/phpbb/area51-phpbb3) bulletin board software as well. Minor changes have been made for Python compatibility. phpBB code is available under [GNU GPL v2.0](https://opensource.org/licenses/gpl-2.0.php). 56 | 57 | ### Differences from html2bbcode 58 | 59 | This package differs from html2bbcode in the following: 60 | * The generated BBCode follows the syntax described in phpBB's [BBCode guide](https://www.phpbb.com/community/help/bbcode). 61 | * ``, ``, ``, ``, `
    ` HTML tags are also supported. 62 | * ``'s `size` attribute handling has been changed so that it maps to reasonable BBCode size values. 63 | * If the `href` attribute of an `` link uses the `mailto:` protocol, then the `[email]` BBCode tag is used. 64 | * If the `href` attribute of an `` link is neither an email nor a valid http/https URL, the link is converted to plain-text in BBCode. 65 | * The parser removes excessive whitespace such as newlines between tags: `

    Hello

    \n

    World

    ` *(TODO: Use the [W3C spec](https://www.w3.org/TR/css-text-3/) rules)* 66 | 67 | ### Installing 68 | 69 | The package is available at [PyPI](https://pypi.org/project/html2phpbbcode/) and can be installed with the following command: 70 | 71 | ```bash 72 | pip install html2phpbbcode 73 | ``` 74 | 75 | Installing from source is also an option: 76 | 77 | ```bash 78 | python3 setup.py install 79 | ``` 80 | 81 | ### Testing 82 | 83 | [pytest](https://pytest.org) is used for testing. Just run `pytest` in the project directory to execute the tests. 84 | 85 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode-0.1.4.dist-info/RECORD: -------------------------------------------------------------------------------- 1 | html2phpbbcode-0.1.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 2 | html2phpbbcode-0.1.4.dist-info/METADATA,sha256=Bt-rnf0v3oH2TZ5FNARoHtz2RxRbE2TW8m0DPfq_hEg,3831 3 | html2phpbbcode-0.1.4.dist-info/RECORD,, 4 | html2phpbbcode-0.1.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 5 | html2phpbbcode-0.1.4.dist-info/WHEEL,sha256=NzFAKnL7g-U64xnS1s5e3mJnxKpOTeOtlXdFwS9yNXI,92 6 | html2phpbbcode-0.1.4.dist-info/top_level.txt,sha256=FaTLl0QBrhqKYJ2McC3bSMu-rorv6Xbns5RNDk7T6ZA,15 7 | html2phpbbcode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 8 | html2phpbbcode/__pycache__/__init__.cpython-311.pyc,, 9 | html2phpbbcode/__pycache__/parser.cpython-311.pyc,, 10 | html2phpbbcode/__pycache__/validators.cpython-311.pyc,, 11 | html2phpbbcode/data/defaults.conf,sha256=6YTd0ZbRZU1ZiMR8A5LIE2q9q8pWHy5aca_fJFvxJWg,834 12 | html2phpbbcode/parser.py,sha256=uN_oq8T5yrxsqzN9uvBnBu0knJkhR5b8cvUPIRe9_YU,3462 13 | html2phpbbcode/validators.py,sha256=_UrEvAlE60guvvzmjyJQ2fqs8jyM09HVOmnj0D3BMes,4251 14 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode-0.1.4.dist-info/REQUESTED: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/venv/Lib/site-packages/html2phpbbcode-0.1.4.dist-info/REQUESTED -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode-0.1.4.dist-info/WHEEL: -------------------------------------------------------------------------------- 1 | Wheel-Version: 1.0 2 | Generator: bdist_wheel (0.31.1) 3 | Root-Is-Purelib: true 4 | Tag: py3-none-any 5 | 6 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode-0.1.4.dist-info/top_level.txt: -------------------------------------------------------------------------------- 1 | html2phpbbcode 2 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmusementClub/OKPGUI/368409a4f93294db1db3f30aa88056ccab0b3060/venv/Lib/site-packages/html2phpbbcode/__init__.py -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode/data/defaults.conf: -------------------------------------------------------------------------------- 1 | [a] 2 | start: [url=%(href)s] 3 | end: [/url] 4 | 5 | [email] 6 | start: [email=%(email)s] 7 | end: [/email] 8 | 9 | [img] 10 | start: [img]%(src)s[/img] 11 | end: 12 | 13 | [em] 14 | start: [i] 15 | end: [/i] 16 | 17 | [strong] 18 | start: [b] 19 | end: [/b] 20 | 21 | [i] 22 | start: [i] 23 | end: [/i] 24 | 25 | [b] 26 | start: [b] 27 | end: [/b] 28 | 29 | [del] 30 | start: [s] 31 | end: [/s] 32 | 33 | [s] 34 | start: [s] 35 | end: [/s] 36 | 37 | [ins] 38 | start: [u] 39 | end: [/u] 40 | 41 | [u] 42 | start: [u] 43 | end: [/u] 44 | 45 | [ul] 46 | start: [list] 47 | end: [/list] 48 | 49 | [ol] 50 | start: [list=1] 51 | end: [/list] 52 | 53 | [li] 54 | start: [*] 55 | end: 56 | 57 | [blockquote] 58 | start: [quote] 59 | end: [/quote] 60 | 61 | [code] 62 | start: [code] 63 | end: [/code] 64 | 65 | [font] 66 | expand: color, size 67 | start: 68 | end: 69 | 70 | [color] 71 | start: [color=%(color)s] 72 | end: [/color] 73 | 74 | [size] 75 | start: [size=%(size)s] 76 | end: [/size] 77 | 78 | [br] 79 | start: \n 80 | end: 81 | 82 | [p] 83 | start: \n 84 | end: \n 85 | 86 | [h1] 87 | start: [size=200] 88 | end: [/size] 89 | 90 | [h2] 91 | start: [size=167] 92 | end: [/size] 93 | 94 | [h3] 95 | start: [size=139] 96 | end: [/size] 97 | 98 | [h4] 99 | start: [size=116] 100 | end: [/size] -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode/parser.py: -------------------------------------------------------------------------------- 1 | from os.path import join, dirname 2 | 3 | from html2bbcode.parser import HTML2BBCode, Attributes 4 | from .validators import is_valid_url, is_valid_mail 5 | 6 | def is_mailto_url(url): 7 | return url.startswith("mailto:") and is_valid_mail(url[7:]) 8 | 9 | class HTML2PHPBBCode(HTML2BBCode): 10 | def __init__(self, config=None): 11 | if config is None: 12 | config = join(dirname(__file__), "data/defaults.conf") 13 | super().__init__(config=config) 14 | 15 | def handle_starttag(self, tag, attrs): 16 | if self.config.has_section(tag): 17 | dct = dict(attrs) 18 | 19 | skip = False 20 | if tag == "font" and "size" in dct: 21 | sz = dct["size"] 22 | if sz.isdigit() and int(sz) >= 1 and int(sz) <= 7: 23 | """Size attribute of tag is a number between 1-7""" 24 | sz = int(100 * 1.15 ** (int(sz) - 3)) 25 | else: 26 | sz = 100 27 | dct["size"] = str(sz) 28 | if tag == "a": 29 | if "href" not in dct: 30 | """Mark link as invalid so that handle_endtag knows to ignore it""" 31 | dct["!invalid"] = True 32 | skip = True 33 | else: 34 | url = dct["href"] 35 | if is_mailto_url(url): 36 | """Remove mailto: from URL""" 37 | attrs = {"email": url[7:]} 38 | """Mark link as mail so that handle_endtag knows how to close it""" 39 | dct["!mail"] = True 40 | self.data.append( 41 | self.config.get("email", "start") % Attributes(attrs or {}) 42 | ) 43 | skip = True 44 | elif not is_valid_url(url): 45 | dct["!invalid"] = True 46 | skip = True 47 | 48 | self.attrs[tag].append(dct) 49 | if not skip: 50 | self.data.append( 51 | self.config.get(tag, "start") % Attributes(attrs or {}) 52 | ) 53 | if self.config.has_option(tag, "expand"): 54 | self.expand_starttags(tag) 55 | 56 | def handle_endtag(self, tag): 57 | if self.config.has_section(tag): 58 | attrs = self.attrs[tag][-1] 59 | 60 | skip = False 61 | if tag == "a": 62 | if "!invalid" in attrs: 63 | skip = True 64 | if "!mail" in attrs: 65 | self.data.append(self.config.get("email", "end")) 66 | skip = True 67 | 68 | if not skip: 69 | self.data.append(self.config.get(tag, "end")) 70 | if self.config.has_option(tag, "expand"): 71 | self.expand_endtags(tag) 72 | self.attrs[tag].pop() 73 | 74 | def handle_data(self, data): 75 | """Remove excessive whitespace from text nodes 76 | 77 | TODO: Use the CSS Text Module Level 3 Working Draft rules for 78 | inline formatting contexts to process whitespace. 79 | Link: https://www.w3.org/TR/css-text-3/#white-space-processing""" 80 | 81 | """If there is leading whitespace, replace it with a single space""" 82 | left = data.lstrip() 83 | if left != data: 84 | data = " " + left 85 | """Same for trailing whitespace""" 86 | right = data.rstrip() 87 | if right != data: 88 | data = right + " " 89 | """Replace new lines with spaces""" 90 | data = data.replace("\n", " ") 91 | self.data.append(data) 92 | -------------------------------------------------------------------------------- /venv/Lib/site-packages/html2phpbbcode/validators.py: -------------------------------------------------------------------------------- 1 | import regex 2 | 3 | """The following validators have been based on phpBB's URL and e-mail validators, which are available under GNU GPL v2.0 license. 4 | 5 | Link: https://github.com/phpbb/area51-phpbb3/blob/master/phpBB/includes/functions.php 6 | 7 | Copyright notice as found in phpBB: 8 | 9 | /** 10 | * 11 | * phpBB © Copyright phpBB Limited 2003-2016 12 | * http://www.phpbb.com 13 | * 14 | * phpBB is free software. You can redistribute it and/or modify it 15 | * under the terms of the GNU General Public License, version 2 (GPL-2.0) 16 | * as published by the Free Software Foundation. 17 | * 18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | * 23 | * A copy of the license can be viewed in the docs/LICENSE.txt file. 24 | * The same can be viewed at 25 | * 26 | */ 27 | 28 | """ 29 | 30 | url = [ 31 | regex.compile( 32 | r"[a-z][a-z\d+\-.]*(?