├── .gitignore ├── EHentaiSearch.ini ├── EHentaiSearch.pro ├── EHentaiSearch.pro.user ├── README.md ├── ex.jpg ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── resource.qrc └── ui.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.db-shm 3 | *.db-wal -------------------------------------------------------------------------------- /EHentaiSearch.ini: -------------------------------------------------------------------------------- 1 | [thumb] 2 | enable=true 3 | password=awslawslawslawsl 4 | prefix=http://103.45.103.176:8088/picture/ 5 | suffix= 6 | [database] 7 | path=eh.db 8 | limit=20 9 | -------------------------------------------------------------------------------- /EHentaiSearch.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2019-07-27T22:49:20 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui sql network 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = EHentaiSearch 12 | TEMPLATE = app 13 | 14 | # The following define makes your compiler emit warnings if you use 15 | # any feature of Qt which has been marked as deprecated (the exact warnings 16 | # depend on your compiler). Please consult the documentation of the 17 | # deprecated API in order to know how to port your code away from it. 18 | DEFINES += QT_DEPRECATED_WARNINGS 19 | 20 | # You can also make your code fail to compile if you use deprecated APIs. 21 | # In order to do so, uncomment the following line. 22 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 23 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 24 | 25 | CONFIG += c++11 26 | 27 | SOURCES += \ 28 | main.cpp \ 29 | mainwindow.cpp 30 | 31 | HEADERS += \ 32 | mainwindow.h 33 | 34 | FORMS += \ 35 | mainwindow.ui 36 | 37 | win32{ 38 | QMAKE_CXXFLAGS += /utf-8 39 | } 40 | # Default rules for deployment. 41 | qnx: target.path = /tmp/$${TARGET}/bin 42 | else: unix:!android: target.path = /opt/$${TARGET}/bin 43 | !isEmpty(target.path): INSTALLS += target 44 | 45 | win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../../../../vcpkg/installed/x86-windows/lib/ -lcryptopp-static 46 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../../../vcpkg/installed/x86-windows/debug/lib/ -lcryptopp-static 47 | 48 | INCLUDEPATH += $$PWD/../../../../vcpkg/installed/x86-windows/include 49 | DEPENDPATH += $$PWD/../../../../vcpkg/installed/x86-windows/include 50 | 51 | win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$PWD/../../../../vcpkg/installed/x86-windows/lib/cryptopp-static.lib 52 | else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$PWD/../../../../vcpkg/installed/x86-windows/debug/lib/cryptopp-static.lib 53 | 54 | RESOURCES += \ 55 | resource.qrc 56 | 57 | DISTFILES += \ 58 | EHentaiSearch.ini 59 | -------------------------------------------------------------------------------- /EHentaiSearch.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {59f19bb9-3b4e-4102-b975-bc071ced5f2f} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | -fno-delayed-template-parsing 60 | 61 | false 62 | {bf0843d8-06c8-4e6b-ac88-db71480b9e6d} 63 | 64 | 65 | 66 | ProjectExplorer.Project.Target.0 67 | 68 | Desktop Qt 5.13.0 MSVC2017 32bit 69 | Desktop Qt 5.13.0 MSVC2017 32bit 70 | qt.qt5.5130.win32_msvc2017_kit 71 | 1 72 | 0 73 | 0 74 | 75 | C:/Users/liaoh/Documents/build-EHentaiSearch-Desktop_Qt_5_13_0_MSVC2017_32bit 76 | 77 | 78 | true 79 | qmake 80 | 81 | QtProjectManager.QMakeBuildStep 82 | true 83 | 84 | false 85 | false 86 | false 87 | 88 | 89 | true 90 | Make 91 | 92 | Qt4ProjectManager.MakeStep 93 | 94 | false 95 | 96 | 97 | false 98 | 99 | 2 100 | Build 101 | 102 | ProjectExplorer.BuildSteps.Build 103 | 104 | 105 | 106 | true 107 | Make 108 | 109 | Qt4ProjectManager.MakeStep 110 | 111 | true 112 | clean 113 | 114 | false 115 | 116 | 1 117 | Clean 118 | 119 | ProjectExplorer.BuildSteps.Clean 120 | 121 | 2 122 | false 123 | 124 | Debug 125 | Debug 126 | Qt4ProjectManager.Qt4BuildConfiguration 127 | 2 128 | true 129 | 130 | 131 | C:/Users/liaoh/Documents/build-EHentaiSearch-Desktop_Qt_5_13_0_MSVC2017_32bit 132 | 133 | 134 | true 135 | qmake 136 | 137 | QtProjectManager.QMakeBuildStep 138 | false 139 | 140 | false 141 | false 142 | true 143 | 144 | 145 | true 146 | Make 147 | 148 | Qt4ProjectManager.MakeStep 149 | 150 | false 151 | 152 | 153 | false 154 | 155 | 2 156 | Build 157 | 158 | ProjectExplorer.BuildSteps.Build 159 | 160 | 161 | 162 | true 163 | Make 164 | 165 | Qt4ProjectManager.MakeStep 166 | 167 | true 168 | clean 169 | 170 | false 171 | 172 | 1 173 | Clean 174 | 175 | ProjectExplorer.BuildSteps.Clean 176 | 177 | 2 178 | false 179 | 180 | Release 181 | Release 182 | Qt4ProjectManager.Qt4BuildConfiguration 183 | 0 184 | true 185 | 186 | 2 187 | 188 | 189 | 0 190 | 部署 191 | 192 | ProjectExplorer.BuildSteps.Deploy 193 | 194 | 1 195 | Deploy Configuration 196 | 197 | ProjectExplorer.DefaultDeployConfiguration 198 | 199 | 1 200 | 201 | 202 | dwarf 203 | 204 | cpu-cycles 205 | 206 | 207 | 250 208 | -F 209 | true 210 | 4096 211 | false 212 | false 213 | 1000 214 | 215 | true 216 | 217 | false 218 | false 219 | false 220 | false 221 | true 222 | 0.01 223 | 10 224 | true 225 | kcachegrind 226 | 1 227 | 25 228 | 229 | 1 230 | true 231 | false 232 | true 233 | valgrind 234 | 235 | 0 236 | 1 237 | 2 238 | 3 239 | 4 240 | 5 241 | 6 242 | 7 243 | 8 244 | 9 245 | 10 246 | 11 247 | 12 248 | 13 249 | 14 250 | 251 | 2 252 | 253 | EHentaiSearch 254 | 255 | Qt4ProjectManager.Qt4RunConfiguration:C:/Users/liaoh/Documents/EHentaiSearch/EHentaiSearch.pro 256 | 257 | 3768 258 | false 259 | true 260 | true 261 | false 262 | false 263 | true 264 | true 265 | C:/Users/liaoh/Documents/build-EHentaiSearch-Desktop_Qt_5_13_0_MSVC2017_32bit/debug 266 | C:/Users/liaoh/Documents/build-EHentaiSearch-Desktop_Qt_5_13_0_MSVC2017_32bit 267 | 268 | 1 269 | 270 | 271 | 272 | ProjectExplorer.Project.Target.1 273 | 274 | Desktop Qt 5.13.0 MSVC2017 64bit 275 | Desktop Qt 5.13.0 MSVC2017 64bit 276 | qt.qt5.5130.win64_msvc2017_64_kit 277 | 0 278 | 0 279 | 0 280 | 281 | C:/Users/liaoh/Documents/build-EHentaiSearch-Desktop_Qt_5_13_0_MSVC2017_64bit-Debug 282 | 283 | 284 | true 285 | qmake 286 | 287 | QtProjectManager.QMakeBuildStep 288 | true 289 | 290 | false 291 | false 292 | false 293 | 294 | 295 | true 296 | Make 297 | 298 | Qt4ProjectManager.MakeStep 299 | 300 | false 301 | 302 | 303 | false 304 | 305 | 2 306 | Build 307 | 308 | ProjectExplorer.BuildSteps.Build 309 | 310 | 311 | 312 | true 313 | Make 314 | 315 | Qt4ProjectManager.MakeStep 316 | 317 | true 318 | clean 319 | 320 | false 321 | 322 | 1 323 | Clean 324 | 325 | ProjectExplorer.BuildSteps.Clean 326 | 327 | 2 328 | false 329 | 330 | Debug 331 | Debug 332 | Qt4ProjectManager.Qt4BuildConfiguration 333 | 2 334 | true 335 | 336 | 337 | C:/Users/liaoh/Documents/build-EHentaiSearch-Desktop_Qt_5_13_0_MSVC2017_64bit-Release 338 | 339 | 340 | true 341 | qmake 342 | 343 | QtProjectManager.QMakeBuildStep 344 | false 345 | 346 | false 347 | false 348 | true 349 | 350 | 351 | true 352 | Make 353 | 354 | Qt4ProjectManager.MakeStep 355 | 356 | false 357 | 358 | 359 | false 360 | 361 | 2 362 | Build 363 | 364 | ProjectExplorer.BuildSteps.Build 365 | 366 | 367 | 368 | true 369 | Make 370 | 371 | Qt4ProjectManager.MakeStep 372 | 373 | true 374 | clean 375 | 376 | false 377 | 378 | 1 379 | Clean 380 | 381 | ProjectExplorer.BuildSteps.Clean 382 | 383 | 2 384 | false 385 | 386 | Release 387 | Release 388 | Qt4ProjectManager.Qt4BuildConfiguration 389 | 0 390 | true 391 | 392 | 393 | C:/Users/liaoh/Documents/build-EHentaiSearch-Desktop_Qt_5_13_0_MSVC2017_64bit-Profile 394 | 395 | 396 | true 397 | qmake 398 | 399 | QtProjectManager.QMakeBuildStep 400 | true 401 | 402 | false 403 | true 404 | true 405 | 406 | 407 | true 408 | Make 409 | 410 | Qt4ProjectManager.MakeStep 411 | 412 | false 413 | 414 | 415 | false 416 | 417 | 2 418 | Build 419 | 420 | ProjectExplorer.BuildSteps.Build 421 | 422 | 423 | 424 | true 425 | Make 426 | 427 | Qt4ProjectManager.MakeStep 428 | 429 | true 430 | clean 431 | 432 | false 433 | 434 | 1 435 | Clean 436 | 437 | ProjectExplorer.BuildSteps.Clean 438 | 439 | 2 440 | false 441 | 442 | Profile 443 | Profile 444 | Qt4ProjectManager.Qt4BuildConfiguration 445 | 0 446 | true 447 | 448 | 3 449 | 450 | 451 | 0 452 | 部署 453 | 454 | ProjectExplorer.BuildSteps.Deploy 455 | 456 | 1 457 | Deploy Configuration 458 | 459 | ProjectExplorer.DefaultDeployConfiguration 460 | 461 | 1 462 | 463 | 464 | dwarf 465 | 466 | cpu-cycles 467 | 468 | 469 | 250 470 | -F 471 | true 472 | 4096 473 | false 474 | false 475 | 1000 476 | 477 | true 478 | 479 | false 480 | false 481 | false 482 | false 483 | true 484 | 0.01 485 | 10 486 | true 487 | kcachegrind 488 | 1 489 | 25 490 | 491 | 1 492 | true 493 | false 494 | true 495 | valgrind 496 | 497 | 0 498 | 1 499 | 2 500 | 3 501 | 4 502 | 5 503 | 6 504 | 7 505 | 8 506 | 9 507 | 10 508 | 11 509 | 12 510 | 13 511 | 14 512 | 513 | 2 514 | 515 | EHentaiSearch 516 | 517 | Qt4ProjectManager.Qt4RunConfiguration:C:/Users/liaoh/Documents/EHentaiSearch/EHentaiSearch.pro 518 | 519 | 3768 520 | false 521 | true 522 | true 523 | false 524 | false 525 | true 526 | 527 | 528 | 529 | 1 530 | 531 | 532 | 533 | ProjectExplorer.Project.TargetCount 534 | 2 535 | 536 | 537 | ProjectExplorer.Project.Updater.FileVersion 538 | 21 539 | 540 | 541 | Version 542 | 21 543 | 544 | 545 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://github.com/ex-rip/ehdown/raw/master/ex.jpg) 2 | 3 | # e站搜索工具(本地) 4 | 5 | 使用e站的数据库 6 | 7 | [数据库](https://pan.baidu.com/s/18xSrYlx29NkSYfby3ct01A) 密码 p0q1 8 | 9 | [程序](https://github.com/ex-rip/EHentaiSearch/releases/latest/download/release.7z) 10 | 11 | [QT依赖](https://github.com/ex-rip/EHentaiSearch/releases/download/1.0.3/depend.7z) 如果安装了QT并添加到PATH,就不需要这个包 12 | 13 | ## 使用 14 | 15 | ![](https://github.com/ex-rip/EHentaiSearch/raw/master/ui.png) 16 | 17 | #### 标题 18 | 19 | 按标题搜索,包括英文标题或日文标题 20 | 21 | #### 标签 22 | 23 | 格式:类:值 24 | 25 | * id:符号 数值 26 | * id:>4 27 | * id:<=10 28 | * category:类别 29 | * rating:评分 同id 30 | * artist:艺术家 31 | * group:组织 32 | * parody:原作 33 | * language:语言 34 | * misc:杂项标签 35 | * female:女性 36 | * male:男性 37 | * character:人物 38 | 39 | ## ini配置 40 | 41 | ### thumb 封面 42 | 43 | #### enable 44 | 45 | 是否启用 true/false 46 | 47 | #### prefix suffix 48 | 49 | 封面链接的前缀、后缀 50 | 51 | #### password 52 | 53 | aes加密的密码 54 | 55 | aes加密使用__256bit__ 56 | 57 | ### database 数据库 58 | 59 | #### path 60 | 61 | 数据库路径 62 | 63 | #### limit 64 | 65 | 每一页显示结果数 -------------------------------------------------------------------------------- /ex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ex-rip/EHentaiSearch/422d43dfcb975384a0282895e102b15586095c9d/ex.jpg -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | QApplication a(argc, argv); 6 | MainWindow w; 7 | w.show(); 8 | 9 | return a.exec(); 10 | } 11 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | static QSettings* setting; 18 | 19 | static bool thumb_enable; 20 | static QString thumb_prefix; 21 | static QString thumb_suffix; 22 | static QByteArray thumb_aes_key; 23 | 24 | //const QString dbpath = R"(C:\Users\liaoh\Documents\EHentaiSearch\eh.db)"; 25 | static QString dbpath; 26 | static int result_limit; 27 | 28 | QMap> parseTag(QString tags) { 29 | QMap> result; 30 | QRegularExpression re(R"!!!( *([^: ]+):(?:([^ ]+)|"([^"]+)$") *)!!!"); 31 | int offset = 0; 32 | QRegularExpressionMatch match; 33 | while (true) { 34 | match = re.match(tags, offset); 35 | if (!match.hasMatch()) { 36 | break; 37 | } 38 | result[match.captured(1)].insert(match.captured(2)); 39 | offset = match.capturedEnd(0); 40 | } 41 | QMap> ret; 42 | for (const QString& k: result.keys()) { 43 | ret[k].resize(result[k].size()); 44 | std::copy(result[k].begin(), result[k].end(), ret[k].begin()); 45 | std::sort(ret[k].begin(), ret[k].end()); 46 | } 47 | return ret; 48 | } 49 | 50 | static QString bys[2] = { 51 | "posted", 52 | "rating" 53 | }; 54 | 55 | static QString orders[2] = { 56 | " desc", 57 | "", 58 | }; 59 | 60 | MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), reply(nullptr) { 61 | ui->setupUi(this); 62 | ordergroup = new QButtonGroup(this); 63 | ordergroup->addButton(ui->desc, 0); 64 | ordergroup->addButton(ui->asc, 1); 65 | ui->desc->setChecked(true); 66 | bygroup = new QButtonGroup(this); 67 | bygroup->addButton(ui->byposted, 0); 68 | bygroup->addButton(ui->byrating, 1); 69 | ui->byposted->setChecked(true); 70 | by = bys[0]; 71 | order = orders[0]; 72 | setting = new QSettings("EHentaiSearch.ini", QSettings::IniFormat); 73 | dbpath = setting->value("database/path", "eh.db").toString(); 74 | result_limit = setting->value("database/limit", 20).toInt(); 75 | thumb_enable = setting->value("thumb/enable", false).toBool(); 76 | thumb_prefix = setting->value("thumb/prefix", "").toString(); 77 | thumb_suffix = setting->value("thumb/suffix", "").toString(); 78 | thumb_aes_key = setting->value("thumb/password", QByteArray("awslawslawslawsl")).toByteArray(); 79 | database = QSqlDatabase::addDatabase("QSQLITE"); 80 | database.setDatabaseName(dbpath); 81 | if (!database.open()) { 82 | QMessageBox::critical(this, "EHentaiSearch", "无法打开数据库"); 83 | exit(0); 84 | } 85 | QLabel* update = new QLabel("构建于 " __DATE__ " " __TIME__ " 主页与帮助", ui->status); 86 | update->setOpenExternalLinks(true); 87 | ui->status->addPermanentWidget(update); 88 | query = QSqlQuery(database); 89 | connect(bygroup, QOverload::of(&QButtonGroup::buttonClicked), [&](int id) { 90 | by = bys[id]; 91 | }); 92 | connect(ordergroup, QOverload::of(&QButtonGroup::buttonClicked), [&](int id) { 93 | order = orders[id]; 94 | }); 95 | connect(ui->prev, &QPushButton::clicked, [&]() { 96 | int n = curpage; 97 | --n; 98 | if (n == 1) { 99 | ui->prev->setEnabled(false); 100 | } 101 | if (n < maxpage) { 102 | ui->next->setEnabled(true); 103 | } 104 | ui->page->setValue(n); 105 | movePage(n); 106 | }); 107 | connect(ui->next, &QPushButton::clicked, [&]() { 108 | int n = curpage; 109 | ++n; 110 | if (n == maxpage) { 111 | ui->next->setEnabled(false); 112 | } 113 | if (n > 1) { 114 | ui->prev->setEnabled(true); 115 | } 116 | ui->page->setValue(n); 117 | movePage(n); 118 | }); 119 | connect(ui->search, &QPushButton::clicked, [&]() { 120 | ui->result->disconnect(); 121 | ui->page->disconnect(); 122 | ui->result->clear(); 123 | ui->status->showMessage("搜索中,请稍候。"); 124 | ui->status->repaint(); 125 | if (!query.exec("select count(id) from gallery " + genWhere() + ";")) { 126 | QMessageBox::critical(this, "EX R.I.P.", "搜索失败:" + query.lastError().text()); 127 | exit(0); 128 | } 129 | query.next(); 130 | int results = query.value(0).toInt(); 131 | ui->status->showMessage("搜索出结果" + QString::asprintf("%d", results) + "条。搜索中,请稍候。"); 132 | ui->status->repaint(); 133 | if (results) { 134 | ui->page->setEnabled(results > result_limit); 135 | maxpage = (results - 1) / result_limit + 1; 136 | curpage = 1; 137 | ui->page->setRange(1, (results - 1) / result_limit + 1); 138 | ui->page->setValue(1); 139 | ui->prev->setEnabled(false); 140 | ui->next->setEnabled(maxpage > 1); 141 | connect(ui->page, &QSpinBox::editingFinished, [&]() { 142 | this->movePage(ui->page->value()); 143 | }); 144 | if (!query.exec("select * from gallery " + genWhere() + " order by " + by + order + QString::asprintf(" limit %d;", result_limit))) { 145 | QMessageBox::critical(this, "EX R.I.P.", "搜索失败:" + query.lastError().text()); 146 | exit(0); 147 | } 148 | while (query.next()) { 149 | QString title; 150 | if (query.value(3).toString().length() > 0) { 151 | title = query.value(3).toString(); 152 | } else { 153 | title = query.value(2).toString(); 154 | } 155 | ui->result->addItem(title); 156 | } 157 | connect(ui->result, &QListWidget::currentRowChanged, this, &MainWindow::updateInfo); 158 | } else { 159 | maxpage = 1; 160 | curpage = 1; 161 | ui->page->setRange(1, 1); 162 | ui->page->setValue(1); 163 | ui->page->setEnabled(false); 164 | ui->prev->setEnabled(false); 165 | ui->next->setEnabled(false); 166 | } 167 | updateInfo(-1); 168 | message = QString::asprintf("结果共计%d条,%d页。", results, maxpage); 169 | ui->status->showMessage(message); 170 | }); 171 | } 172 | 173 | MainWindow::~MainWindow() { 174 | delete ui; 175 | } 176 | 177 | QString MainWindow::genWhere() { 178 | auto tags = parseTag(ui->tag->text() 179 | .replace("/", "//") 180 | .replace("'", "''") 181 | .replace("[", "/[") 182 | .replace("]", "/]") 183 | .replace("%", "/%") 184 | .replace("&", "/&") 185 | .replace("_", "/_") 186 | .replace("(", "/(") 187 | .replace(")", "/)")); 188 | QList likes; 189 | for (const QString& k: {"artist", "group", "parody", "language", "character", "female", "male"}) { 190 | if (tags[k].size()) { 191 | QString rst = "%"; 192 | for (const QString& v: tags[k]) { 193 | rst += v + "%"; 194 | } 195 | likes.push_back(k + " like '" + rst + "' escape '/'"); 196 | } 197 | } 198 | if (tags["misc"].size()) { 199 | QString rst = "%"; 200 | for (const QString& v: tags["misc"]) { 201 | rst += v + "%"; 202 | } 203 | likes.push_back("rest like '" + rst + "' escape '/'"); 204 | } 205 | if (tags["category"].size()) { 206 | QList categories; 207 | for (QString category: tags["category"]) { 208 | category = category.toLower(); 209 | category[0] = category[0].toUpper(); 210 | categories.push_back("category = '" + category + "'"); 211 | } 212 | likes.push_back("( " + categories.join(" or ") + " )"); 213 | } 214 | if (tags["rating"].size()) { 215 | QList ratingreq; 216 | for (QString rating: tags["rating"]) { 217 | ratingreq.push_back("rating " + rating); 218 | } 219 | likes.push_back("( " + ratingreq.join(" and ") + " )"); 220 | } 221 | if (tags["id"].size()) { 222 | QList idreq; 223 | for (QString id: tags["id"]) { 224 | idreq.push_back("id " + id); 225 | } 226 | likes.push_back("( " + idreq.join(" and ") + " )"); 227 | } 228 | QString tag = likes.join(" and "); 229 | QString tit = ""; 230 | if (ui->query->text().length()) { 231 | QString s = ui->query->text() 232 | .replace("/", "//") 233 | .replace("'", "''") 234 | .replace("[", "/[") 235 | .replace("]", "/]") 236 | .replace("%", "/%") 237 | .replace("&", "/&") 238 | .replace("_", "/_") 239 | .replace("(", "/(") 240 | .replace(")", "/)"); 241 | tit = "title like '%" + s + "%' escape '/' or title_jpn like '%" + s + "%' escape '/'"; 242 | } 243 | return tag.length() ? (tit.length() ? " where ( " + tit + " ) and ( " + tag + " )" : " where " + tag) : (tit.length() ? " where " + tit : ""); 244 | } 245 | 246 | QByteArray decryption(QByteArray data) { 247 | int len = data.length(); 248 | int blocks = len >> 4; 249 | unsigned char* ptr = reinterpret_cast(data.data()); 250 | CryptoPP::AES::Decryption aesDecryption(reinterpret_cast(thumb_aes_key.data()), 16); 251 | while (blocks--) { 252 | aesDecryption.ProcessBlock(ptr); 253 | ptr += 16; 254 | } 255 | data.chop(*--ptr); 256 | return data; 257 | } 258 | 259 | void MainWindow::updateInfo(int r) { 260 | auto div = [](const QString& raw) -> QList { 261 | if (raw.length() == 0) { 262 | return QList(); 263 | } else { 264 | auto rst = raw.mid(2, raw.length() - 4).split("', '"); 265 | std::sort(rst.begin(), rst.end()); 266 | return std::move(rst); 267 | } 268 | }; 269 | if (reply) { 270 | reply->disconnect(); 271 | reply->abort(); 272 | reply->deleteLater(); 273 | reply = nullptr; 274 | } 275 | if (r == -1) { 276 | ui->id->setText("0"); 277 | ui->title->setText("EX R.I.P."); 278 | ui->titlejp->setText("EX R.I.P."); 279 | ui->category->setText("Misc"); 280 | ui->uploader->setText("nekosu"); 281 | ui->posted->setText("2019/7/26"); 282 | ui->rating->setText("5.00"); 283 | ui->artist->setText("Tenboro"); 284 | ui->group->setText("Sad Panda"); 285 | ui->parody->setText(""); 286 | ui->language->setText("chinese translated"); 287 | ui->misc->setText(""); 288 | ui->female->clear(); 289 | ui->male->clear(); 290 | ui->character->clear(); 291 | ui->urlF->setText("e-hentai"); 292 | ui->urlB->setText("exhentai"); 293 | ui->thumb->setPixmap(QPixmap(":/ex.jpg")); 294 | } else { 295 | query.seek(r); 296 | ui->id->setText(query.value(0).toString()); 297 | ui->title->setText(query.value(2).toString()); 298 | ui->title->setCursorPosition(0); 299 | ui->titlejp->setText(query.value(3).toString()); 300 | ui->titlejp->setCursorPosition(0); 301 | ui->category->setText(query.value(4).toString()); 302 | ui->uploader->setText(query.value(5).toString()); 303 | QDate tm = QDateTime().fromTime_t(query.value(6).toString().toULong()).date(); 304 | ui->posted->setText(QString::asprintf("%d/%d/%d", tm.year(), tm.month(), tm.day())); 305 | ui->rating->setText(query.value(14).toString()); 306 | ui->artist->setText(div(query.value(15).toString()).join(", ")); 307 | ui->group->setText(div(query.value(16).toString()).join(", ")); 308 | ui->parody->setText(div(query.value(17).toString()).join(", ")); 309 | QList lang = div(query.value(21).toString()); 310 | if (lang.length() == 0) { 311 | ui->language->setText("unknown"); 312 | } else if (lang.length() == 1) { 313 | ui->language->setText(lang[0]); 314 | } else { 315 | ui->language->setText(lang[lang[0] == "translated"] + " translated"); 316 | } 317 | ui->misc->setText(div(query.value(23).toString()).join(", ")); 318 | ui->character->clear(); 319 | ui->character->addItems(div(query.value(18).toString())); 320 | ui->female->clear(); 321 | ui->female->addItems(div(query.value(19).toString())); 322 | ui->male->clear(); 323 | ui->male->addItems(div(query.value(20).toString())); 324 | ui->urlF->setText("e-hentai"); 325 | ui->urlB->setText("exhentai"); 326 | if (thumb_enable) { 327 | int id = query.value(0).toString().toInt(); 328 | if (!QDir("cache").exists()) { 329 | QDir().mkdir("cache"); 330 | } 331 | QString cache = QString("cache") + QDir::separator() + QString::asprintf("%d", id); 332 | if (!QFile::exists(cache)) { 333 | ui->thumb->setPixmap(QPixmap(":/ex.jpg")); 334 | QString url = thumb_prefix + QString::asprintf("%d", id) + thumb_suffix; 335 | reply = manager.get(QNetworkRequest(QUrl(url))); 336 | ui->status->showMessage(message + "正在下载封面。"); 337 | ui->status->repaint(); 338 | connect(reply, &QNetworkReply::finished, [&]() { 339 | auto err = reply->error(); 340 | if (err) { 341 | QMessageBox::warning(this, "EHentaiSearch", "无法加载图片 " + reply->errorString()); 342 | } else { 343 | QByteArray data = reply->readAll(); 344 | reply->disconnect(); 345 | reply->deleteLater(); 346 | reply = nullptr; 347 | QString cache = QString("cache") + QDir::separator() + QString::asprintf("%d", query.value(0).toString().toInt()); 348 | QFile file(cache); 349 | file.open(QIODevice::WriteOnly); 350 | file.write(data); 351 | file.close(); 352 | updateThumb(data); 353 | } 354 | ui->status->showMessage(message); 355 | ui->status->repaint(); 356 | }); 357 | } else { 358 | QFile file(cache); 359 | file.open(QIODevice::ReadOnly); 360 | updateThumb(file.readAll()); 361 | file.close(); 362 | } 363 | } 364 | } 365 | } 366 | 367 | void MainWindow::updateThumb(const QByteArray& data) { 368 | QPixmap pixmap; 369 | pixmap.loadFromData(decryption(data)); 370 | int w = pixmap.width(), h = pixmap.height(); 371 | if (w >= h && w > 400) { 372 | pixmap.scaledToWidth(400); 373 | } else if (h > w && h > 400) { 374 | pixmap.scaledToHeight(400); 375 | } 376 | ui->thumb->setPixmap(pixmap); 377 | } 378 | 379 | void MainWindow::movePage(int p) { 380 | curpage = p; 381 | ui->result->disconnect(); 382 | ui->result->clear(); 383 | if (!query.exec("select * from gallery " + genWhere() + " order by " + by + order + QString::asprintf(" limit %d offset ", result_limit) + QString::asprintf("%d", (p - 1) * result_limit) + ";")) { 384 | QMessageBox::critical(this, "EX R.I.P.", "搜索失败:" + query.lastError().text()); 385 | exit(0); 386 | } 387 | while (query.next()) { 388 | QString title; 389 | if (query.value(3).toString().length() > 0) { 390 | title = query.value(3).toString(); 391 | } else { 392 | title = query.value(2).toString(); 393 | } 394 | ui->result->addItem(title); 395 | } 396 | connect(ui->result, &QListWidget::currentRowChanged, this, &MainWindow::updateInfo); 397 | updateInfo(-1); 398 | } 399 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace Ui { 12 | class MainWindow; 13 | } 14 | 15 | class MainWindow : public QMainWindow { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit MainWindow(QWidget *parent = nullptr); 20 | ~MainWindow(); 21 | 22 | void updateInfo(int r); 23 | void movePage(int p); 24 | QString genWhere(); 25 | void updateThumb(const QByteArray& data); 26 | 27 | private: 28 | Ui::MainWindow *ui; 29 | int curpage; 30 | int maxpage; 31 | QSqlDatabase database; 32 | QSqlQuery query; 33 | QNetworkAccessManager manager; 34 | QNetworkReply* reply; 35 | QString message; 36 | QString order; 37 | QString by; 38 | QButtonGroup* ordergroup; 39 | QButtonGroup* bygroup; 40 | }; 41 | 42 | #endif // MAINWINDOW_H 43 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | EHentaiSearch 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 标题 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 标签 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 时间 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | 51 | 评分 52 | 53 | 54 | false 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 高->低 66 | 67 | 68 | false 69 | 70 | 71 | 72 | 73 | 74 | 75 | 低->高 76 | 77 | 78 | false 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 搜索 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | false 99 | 100 | 101 | < 102 | 103 | 104 | 105 | 106 | 107 | 108 | false 109 | 110 | 111 | 112 | 0 113 | 0 114 | 115 | 116 | 117 | 118 | 50 119 | 0 120 | 121 | 122 | 123 | QAbstractSpinBox::NoButtons 124 | 125 | 126 | 1 127 | 128 | 129 | 1 130 | 131 | 132 | 133 | 134 | 135 | 136 | false 137 | 138 | 139 | > 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | Qt::Horizontal 153 | 154 | 155 | 156 | Qt::ScrollBarAlwaysOn 157 | 158 | 159 | QAbstractItemView::NoEditTriggers 160 | 161 | 162 | 163 | 164 | 165 | 5 166 | 167 | 168 | 0 169 | 170 | 171 | 0 172 | 173 | 174 | 175 | 176 | 177 | 178 | 0 179 | 180 | 181 | true 182 | 183 | 184 | 185 | 186 | 187 | 188 | 编号 189 | 190 | 191 | 192 | 193 | 194 | 195 | 英文标题 196 | 197 | 198 | 199 | 200 | 201 | 202 | EX R.I.P. 203 | 204 | 205 | true 206 | 207 | 208 | 209 | 210 | 211 | 212 | 日文标题 213 | 214 | 215 | 216 | 217 | 218 | 219 | EX R.I.P. 220 | 221 | 222 | true 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 0 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 类别 241 | 242 | 243 | 244 | 245 | 246 | 247 | Misc 248 | 249 | 250 | 251 | 252 | 253 | 254 | 上传者 255 | 256 | 257 | 258 | 259 | 260 | 261 | nekosu 262 | 263 | 264 | 265 | 266 | 267 | 268 | 上传时间 269 | 270 | 271 | 272 | 273 | 274 | 275 | 2019/7/26 276 | 277 | 278 | 279 | 280 | 281 | 282 | 评分 283 | 284 | 285 | 286 | 287 | 288 | 289 | 5.00 290 | 291 | 292 | 293 | 294 | 295 | 296 | 艺术家 297 | 298 | 299 | 300 | 301 | 302 | 303 | Tenboro 304 | 305 | 306 | true 307 | 308 | 309 | 310 | 311 | 312 | 313 | 组织 314 | 315 | 316 | 317 | 318 | 319 | 320 | Sad Panda 321 | 322 | 323 | true 324 | 325 | 326 | 327 | 328 | 329 | 330 | 原作 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | true 341 | 342 | 343 | 344 | 345 | 346 | 347 | 语言 348 | 349 | 350 | 351 | 352 | 353 | 354 | chinese translated 355 | 356 | 357 | true 358 | 359 | 360 | 361 | 362 | 363 | 364 | 杂项标签 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | true 375 | 376 | 377 | 378 | 379 | 380 | 381 | 链接 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | <a href="https://e-hentai.org">e-hentai</a> 391 | 392 | 393 | true 394 | 395 | 396 | 397 | 398 | 399 | 400 | <a href="https://exhentai.org">exhentai</a> 401 | 402 | 403 | true 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 女性 417 | 418 | 419 | 420 | 1 421 | 422 | 423 | 1 424 | 425 | 426 | 1 427 | 428 | 429 | 1 430 | 431 | 432 | 433 | 434 | QAbstractItemView::NoEditTriggers 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 男性 445 | 446 | 447 | 448 | 1 449 | 450 | 451 | 1 452 | 453 | 454 | 1 455 | 456 | 457 | 1 458 | 459 | 460 | 461 | 462 | QAbstractItemView::NoEditTriggers 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 角色 473 | 474 | 475 | 476 | 1 477 | 478 | 479 | 1 480 | 481 | 482 | 1 483 | 484 | 485 | 1 486 | 487 | 488 | 489 | 490 | QAbstractItemView::NoEditTriggers 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 0 506 | 0 507 | 508 | 509 | 510 | :/ex.jpg 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | -------------------------------------------------------------------------------- /resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | ex.jpg 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ex-rip/EHentaiSearch/422d43dfcb975384a0282895e102b15586095c9d/ui.png --------------------------------------------------------------------------------