├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── enhancement.md ├── WIKI │ ├── ACGMX │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ └── 4.png │ └── SAUCENAO │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png ├── jetbrains-variant-3.png └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── mirai-console-2.17.0-all.jar ├── mirai-console-terminal-2.17.0-all.jar └── mirai-core-all-2.17.0-all.jar ├── settings.gradle.kts └── src └── main ├── java └── com │ └── hcyacg │ └── lowpoly │ ├── Configuration.java │ ├── Delaunay.java │ ├── LowPoly.java │ ├── Sobel.java │ └── utils │ └── CheckFileTypeUtil.java ├── kotlin └── com │ └── hcyacg │ ├── AutoUpdate.kt │ ├── Helper.kt │ ├── Nsfw.kt │ ├── Pixiv.kt │ ├── Vip.kt │ ├── anno │ └── NoArgOpenDataClass.kt │ ├── details │ ├── PicDetails.kt │ └── UserDetails.kt │ ├── entity │ ├── Anilist.kt │ ├── GithubRelease.kt │ ├── Lolicon.kt │ ├── PixivImageDetail.kt │ ├── SaucenaoItem.kt │ ├── Trace.kt │ ├── YandexImage.kt │ └── YandexSearchResult.kt │ ├── initial │ ├── Command.kt │ ├── Config.kt │ ├── Github.kt │ ├── Setting.kt │ └── entity │ │ ├── Cache.kt │ │ ├── Enable.kt │ │ ├── ForWard.kt │ │ └── GoogleConfig.kt │ ├── rank │ ├── Rank.kt │ ├── Tag.kt │ └── TotalProcessing.kt │ ├── science │ └── Style2paints.kt │ ├── search │ ├── Ascii2d.kt │ ├── Google.kt │ ├── Iqdb.kt │ ├── Saucenao.kt │ ├── Search.kt │ ├── SearchPicCenter.kt │ ├── Trace.kt │ └── Yandex.kt │ ├── sexy │ ├── LoliconCenter.kt │ ├── SexyCenter.kt │ └── WarehouseCenter.kt │ └── utils │ ├── CacheUtil.kt │ ├── DataUtil.kt │ ├── DateUtil.kt │ ├── DownloadUtil.kt │ ├── ImageUtil.kt │ ├── LogUtil.kt │ ├── RequestUtil.kt │ └── ZipUtil.kt └── resources ├── META-INF └── services │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin └── tags.json /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 报告 2 | description: 提交一个 bug 3 | labels: 4 | - "bug" 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | 感谢你来到这里 11 | 12 | 在反馈前, 请确认你已经做了下面这些事情 13 | - 对照 [Releases](https://github.com/Nekoer/mirai-plugins-pixiv/releases),相关问题未在近期更新中解决 14 | - 搜索了已有的 [issues](https://github.com/Nekoer/mirai-plugins-pixiv/issues) 列表中有没相关的信息 15 | 16 | - type: textarea 17 | id: issue-description 18 | attributes: 19 | label: 问题描述 20 | description: 在此详细描述你遇到的问题 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | id: reproduce 26 | attributes: 27 | label: 复现 28 | description: 在这里简略说明如何让这个问题再次发生 29 | placeholder: | 30 | 在这里简略说明如何让这个问题再次发生 31 | 可使用 1. 2. 3. 的列表格式,或其他任意恰当的格式 32 | 如果你不确定如何复现, 请尽量描述发生当时的情景 33 | validations: 34 | required: true 35 | 36 | - type: input 37 | id: version-pixiv 38 | attributes: 39 | label: pixiv 版本 40 | description: "填写你正在使用的版本号,如 `1.6.8`" 41 | placeholder: 1.6.8 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: journal-system 47 | attributes: 48 | label: 系统日志 49 | description: | 50 | 请提供全面的相关日志. 请不要截图. 51 | 如果日志过大, 可以在 `补充信息` 上传文件. 52 | 如果你遇到的问题是 "消息收不到", "收消息报错" 等与协议有关的问题, 请一定提交日志. 若不提交日志, 你的问题可能会被直接关闭. 53 | render: 'text' 54 | validations: 55 | required: false 56 | 57 | - type: textarea 58 | id: journal-network 59 | attributes: 60 | label: 网络日志 61 | description: | 62 | 如果网络日志 (Net xxx) 不包含在系统日志中, 请额外提供网络日志. 若已经包含, 请忽略. 63 | 请提供全面的网络日志. 请不要截图. 64 | 若使用 Mirai Console 一般网络日志位于 bots/<****>/logs 里 65 | 如果日志过大, 可以在 `补充信息` 上传文件. 66 | render: text 67 | validations: 68 | required: false 69 | 70 | - type: textarea 71 | id: additional 72 | attributes: 73 | label: 补充信息 74 | description: 如有必要,你可以在下文继续添加其他信息 75 | 76 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 特性申请 3 | about: 申请 pixiv 添加新的特性 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /.github/WIKI/ACGMX/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/WIKI/ACGMX/1.png -------------------------------------------------------------------------------- /.github/WIKI/ACGMX/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/WIKI/ACGMX/2.png -------------------------------------------------------------------------------- /.github/WIKI/ACGMX/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/WIKI/ACGMX/3.png -------------------------------------------------------------------------------- /.github/WIKI/ACGMX/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/WIKI/ACGMX/4.png -------------------------------------------------------------------------------- /.github/WIKI/SAUCENAO/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/WIKI/SAUCENAO/1.png -------------------------------------------------------------------------------- /.github/WIKI/SAUCENAO/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/WIKI/SAUCENAO/2.png -------------------------------------------------------------------------------- /.github/WIKI/SAUCENAO/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/WIKI/SAUCENAO/3.png -------------------------------------------------------------------------------- /.github/jetbrains-variant-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/.github/jetbrains-variant-3.png -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 7 | 8 | name: Java CI with Gradle 9 | 10 | on: 11 | push: 12 | branches: [ "master" ] 13 | pull_request: 14 | branches: [ "master" ] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Set up JDK 11 27 | uses: actions/setup-java@v4 28 | with: 29 | java-version: '11' 30 | distribution: 'temurin' 31 | - name: Grant execute permission for gradlew 32 | run: chmod +x ./gradlew 33 | - name: Setup Gradle 34 | uses: gradle/actions/setup-gradle@v3 35 | - name: Build Plugin 36 | run: ./gradlew build buildPlugin -Pnt 37 | - name: Build Plugin Legacy 38 | run: ./gradlew build buildPluginLegacy -Pnt 39 | - name: Build Overflow Plugin 40 | run: ./gradlew build buildPlugin -Poverflow 41 | - name: Build Overflow Plugin Legacy 42 | run: ./gradlew build buildPluginLegacy -Poverflow 43 | #取文件名 44 | - name: Upload a Build Artifact 45 | uses: actions/upload-artifact@v4 46 | if: success() 47 | with: 48 | name: "pixiv" 49 | path: /home/runner/work/mirai-plugins-pixiv/mirai-plugins-pixiv/build/mirai/*.jar 50 | retention-days: 0 51 | if-no-files-found: error 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | 120 | # Local Test Launch point 121 | src/test/kotlin/RunTerminal.kt 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mirai-plugins-pixiv 2 | 3 | [![MiraiForum](https://img.shields.io/badge/post-on%20MiraiForum-yellow)](https://mirai.mamoe.net/topic/461) 4 | ![Stars](https://img.shields.io/github/stars/Nekoer/mirai-plugins-pixiv) 5 | ![Downloads](https://img.shields.io/github/downloads/Nekoer/mirai-plugins-pixiv/total) 6 | [![Release](https://img.shields.io/github/v/release/Nekoer/mirai-plugins-pixiv)](https://github.com/Nekoer/mirai-plugins-pixiv/releases) 7 | 8 | * 可查看pixiv排行榜,作者作品,插画图片等等 9 | 10 | - pixiv排行榜 11 | - 查看图片 12 | - 查看作者作品 13 | - 搜图 14 | - 搜番 15 | - 搜标签 16 | - 涩图 17 | 18 | ~~请复制的时候去掉#以及后面的字~~ 这就不要我说了吧( 19 | 20 | ``` 21 | admins: #管理员 22 | - 243462032 23 | groups: #有涩图权限的群 24 | - 960879198 25 | - 77708393 26 | - 1475993 27 | command: 28 | getDetailOfId: 'psid-' #根据id查看插画 29 | picToSearch: 'ptst-' #以图搜图 30 | showRank: 'rank-' #排行榜 day|week|month|setu 31 | findUserWorksById: 'user-' #查看作者作品 32 | searchInfoByPic: 'ptsf-' #以图搜番 33 | setu: setu 34 | tag: 'tag-' #搜标签 tag-xxx-页码 35 | help: 帮助 36 | config: 37 | setuEnable: 38 | pixiv: true #不需要翻墙 39 | yande: true #需要翻墙 40 | konachan: true #需要翻墙 41 | lolicon: true #不需要翻墙 42 | localImage: true #本地图库 43 | forward: 44 | rankAndTagAndUserByForward: false #排行榜转发模式 45 | imageToForward: false #图片详情转发模式 46 | token: 47 | acgmx: # https://www.acgmx.com/account申请 48 | saucenao: #saucenao.com注册账号后能看到api_key 49 | proxy: # http请求代理 下面是clash的例子 50 | host: 127.0.0.1 51 | port: 7890 52 | recall: 5000 # 涩图经过多少秒撤回 设置为0即可不撤回 53 | tlsVersion: TLSv1.2 54 | cache: 55 | enable: false #缓存开关 56 | directory: 'Mirai根目录\image' #图片缓存路径 57 | localImagePath: 'Mirai根目录\image' #本地图库路径 默认和缓存路径一致 58 | google: 59 | googleUrl: 'https://www.google.com.hk' #Google镜像源 60 | resultNum: 6 #显示的结果数量 61 | ``` 62 | 63 | ## Bot部署 docker-compose 64 | 65 | ``` 66 | version: '3' 67 | services: 68 | overflow: 69 | image: hcyacg/overflow:latest 70 | container_name: overflow 71 | restart: always 72 | ports: 73 | - "3001:3001" 74 | environment: 75 | - TZ=Asia/Shanghai 76 | volumes: 77 | #/share/Public/overflow替换成你本地的目录 78 | - /share/Public/overflow/plugins:/overflow/plugins 79 | - /share/Public/overflow/config:/overflow/config 80 | - /share/Public/overflow/data:/overflow/data 81 | network_mode: host 82 | llonebot-docker: 83 | image: mlikiowa/llonebot-docker:latest 84 | tty: true 85 | container_name: llonebot-docker 86 | restart: always 87 | ports: 88 | - "5900:5900" 89 | - "3000:3000" 90 | - "3001:3001" 91 | environment: 92 | - TZ=Asia/Shanghai 93 | volumes: 94 | - ./LiteLoader/:/opt/QQ/resources/app/LiteLoader 95 | network_mode: host 96 | ``` 97 | 98 | 99 | 100 | 101 | 102 | ## 鸣谢 103 | 104 | > [IntelliJ IDEA](https://zh.wikipedia.org/zh-hans/IntelliJ_IDEA) 是一个在各个方面都最大程度地提高开发人员的生产力的 IDE,适用于 JVM 平台语言。 105 | 106 | 特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai-plugins-pixiv) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai-plugins-pixiv) 等 IDE 的授权 107 | 108 | [](https://www.jetbrains.com/?from=mirai-plugins-pixiv) 109 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | val kotlinVersion = "1.9.20" 5 | kotlin("jvm") version kotlinVersion 6 | kotlin("plugin.serialization") version kotlinVersion 7 | 8 | id("org.jetbrains.kotlin.plugin.noarg") version kotlinVersion 9 | id("org.jetbrains.kotlin.plugin.allopen") version kotlinVersion 10 | id("net.mamoe.mirai-console") version "2.16.0" 11 | 12 | id("me.him188.maven-central-publish") version "1.0.0-dev-3" 13 | } 14 | 15 | group = "com.hcyacg" 16 | val tempVersion = "1.7.8" 17 | version = tempVersion 18 | 19 | repositories { 20 | maven("https://maven.aliyun.com/repository/central") 21 | maven("https://maven.aliyun.com/repository/gradle-plugin") 22 | maven("https://s01.oss.sonatype.org/content/repositories/snapshots") 23 | mavenCentral() 24 | } 25 | 26 | 27 | dependencies { 28 | implementation("org.apache.commons:commons-lang3:3.17.0") 29 | 30 | // 根据项目属性动态添加的依赖项 31 | if (project.hasProperty("overflow")) { 32 | val overflowVersion = "1.0.1" 33 | compileOnly("top.mrxiaom.mirai:overflow-core-api:$overflowVersion") 34 | testConsoleRuntime("top.mrxiaom.mirai:overflow-core:$overflowVersion") 35 | version = "$tempVersion-overfolw" 36 | } 37 | 38 | 39 | implementation("commons-codec:commons-codec:1.17.1") 40 | implementation("org.apache.httpcomponents.client5:httpclient5:5.4.1") 41 | implementation("org.jsoup:jsoup:1.18.1") 42 | implementation("com.squareup.okhttp3:okhttp:4.12.0") 43 | implementation("com.madgag:animated-gif-lib:1.4") 44 | implementation("io.github.oshai:kotlin-logging-jvm:7.0.0") 45 | 46 | compileOnly("org.bytedeco:javacv-platform:1.5.10") 47 | // compileOnly 48 | implementation(kotlin("stdlib-jdk8")) 49 | 50 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 51 | 52 | } 53 | 54 | mirai { 55 | if (project.hasProperty("overflow")) { 56 | noTestCore = true 57 | setupConsoleTestRuntime { 58 | // 移除 mirai-core 依赖 59 | classpath = classpath.filter { 60 | !it.nameWithoutExtension.startsWith("mirai-core-jvm") 61 | } 62 | } 63 | } 64 | 65 | if (project.hasProperty("nt")) { 66 | noTestCore = true 67 | setupConsoleTestRuntime { 68 | // 移除 mirai-core 依赖 69 | classpath = classpath.filter { 70 | !it.nameWithoutExtension.startsWith("mirai-console") 71 | !it.nameWithoutExtension.startsWith("mirai-console-terminal") 72 | !it.nameWithoutExtension.startsWith("mirai-core-all") 73 | !it.nameWithoutExtension.startsWith("mirai-core-jvm") 74 | } 75 | } 76 | } 77 | } 78 | 79 | noArg { 80 | annotation("com.hcyacg.anno.NoArgOpenDataClass") 81 | } 82 | 83 | allOpen { 84 | annotation("com.hcyacg.anno.NoArgOpenDataClass") 85 | } 86 | 87 | val compileKotlin: KotlinCompile by tasks 88 | compileKotlin.kotlinOptions { 89 | jvmTarget = "1.8" 90 | 91 | } 92 | 93 | val compileTestKotlin: KotlinCompile by tasks 94 | compileTestKotlin.kotlinOptions { 95 | jvmTarget = "1.8" 96 | } 97 | 98 | mavenCentralPublish { 99 | artifactId = "pixiv" 100 | groupId = "com.hcyacg" 101 | projectName = "mirai plugins pixiv" 102 | 103 | //developer(1,"Nekoer","hcyacg@vip.qq.com","","","","") 104 | // workingDir = rootProject.buildDir.resolve("pub").apply { mkdirs() } 105 | 106 | // description from project.description by default 107 | //githubProject("Nekoer", "mirai-plugins-pixiv") 108 | singleDevGithubProject("Nekoer", "mirai-plugins-pixiv") 109 | useCentralS01() 110 | licenseFromGitHubProject("AGPL-3.0", "master") 111 | 112 | publication { 113 | artifact(tasks.getByName("buildPlugin")) 114 | artifact(tasks.getByName("buildPluginLegacy")) 115 | } 116 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | publication.credentials=0aec0c2d2d2d2d2d424547494e20504750205055424c4943204b455920424c4f434b2d2d2d2d2d0a0a6d51494e42474a70575451424541444f554558566b477870626358664f70313253355a2b4851485652484c6e544d4e6250612f30736d6f4d386e766f2b78586f0a56514c55304a44772b34373430436c3063633251787146456272705a53574c7064585952704f54453335504f5863325a735a446970504e783235776b7175412f0a487467305437756c743369775849324233385751474a434c7a77323638324a4d623474786b41797172584e306853684b34697655365051373679382f625174350a6e744262384e3142474c38756e69632f434977636a74696a455441436665365843374155775237522f772f734e5838676b5732662b594a7a396177373969312f0a776f726f33596b6c53517a45386777596f636778647547774a436436742b53734354473532436c4c787842734842735463785063535a6f5973452b73454a44570a33656155706e68516b5767755239766543317936525173797761316d444e655674427552704b656f306b56676d5657644263456f334c5130544e756d36796b310a7178777a464b544962303564435573522b4f494846744c534256645778626a63613957464c6a696b552b367a737576786d6d2b6f375a7058505a62504774362b0a624f727a6f53734e6745564a775255714146396f417450594c5a59787862385656674575304f30757841677532414445754432306a5134634e7847515479324e0a6448774f39654f2f3479306b3658546c485669786265596e5472645434715a5355385a4944562f382f46324663522b4a76426e4a48324b5071494f652b6e6f740a39552f6f686c6c35796a56786b3672393059467439526d744167353431674e6b4f4d4965356747565555594e6f316546516d65386b455a45754c66417366446d0a5057662b71496966597763577a446a4430624f4f3733535a51502f6f41516d3148472b4b54654f4f4f6f64566a46394531566539576a4d6c6b514152415141420a7442704f5a5774765a5849675047686a6557466a5a304232615841756358457559323974506f6b4356415154415167415068596842484b5275552f683766586c0a656967553153315449664e74566f5a3042514a6961566b304168734442516b44776d63414251734a434163434268554b4351674c4167515741674d42416834420a4168654141416f4a4543315449664e74566f5a30436d73502f31726b375035616e51643845744f376544764a6d624d2f6d6d31356e645477686e6448444d31420a715837754c5a616d31695533652f464545443741684c52686565344849436655575531442f4f6a4e4376567958466a696f68527949464b75493574716a334f470a665133543164626a6d53564666317337674868766745637169612f6338375364763075564a625975302b7a4d48376e396d63327a6a30564262436e496a3255580a4a6c39613032776c3730714a515459414d36776f77474462576541614268656d6f66517a776277627a4b61393341597446765a76324241416c39646a2b676f310a777369643950617a69556d7234686f657a43554558685476615259322b6d39716f49425062594870767557574a4759362f78414d6a6b4d59764b2f704f3359480a4a70657577476751675834456c6d766d424d316d67477a4867734c50306977314c4b305a366853354446774955453334347748374755524273515473676f68510a7567517045384e6f76616b44633267306a67575871504d79774552672b7a4752614b454e5955774657454c476237535037456843616b566e57587530416765300a2b7742513139716f7271594930635936705073675434514f4f7753323455566351466f3273526d435131324e636f35483048326a6c59366e4e5063794d6554330a55774e696d38556b356b3974657631693157632f2f497663385067782b494d66514a455536795348474655315343522f6b41636a50472b617455314158716b390a535a67504972394964414641532b5856556b7533364159493248432f5345584a65676464445947317245673671537267774f587963723168347a77773062374e0a4e354c5777597a3870693367452f366661596d55354f6c7245512b6e6d342f3962576d57304961496576786364384862716e70564f4e79646f2b632f5a4f36490a396855560a3d765949700a2d2d2d2d2d454e4420504750205055424c4943204b455920424c4f434b2d2d2d2d2d0a12811b2d2d2d2d2d424547494e205047502050524956415445204b455920424c4f434b2d2d2d2d2d0a0a6c51644742474a70575451424541444f554558566b477870626358664f70313253355a2b4851485652484c6e544d4e6250612f30736d6f4d386e766f2b78586f0a56514c55304a44772b34373430436c3063633251787146456272705a53574c7064585952704f54453335504f5863325a735a446970504e783235776b7175412f0a487467305437756c743369775849324233385751474a434c7a77323638324a4d623474786b41797172584e306853684b34697655365051373679382f625174350a6e744262384e3142474c38756e69632f434977636a74696a455441436665365843374155775237522f772f734e5838676b5732662b594a7a396177373969312f0a776f726f33596b6c53517a45386777596f636778647547774a436436742b53734354473532436c4c787842734842735463785063535a6f5973452b73454a44570a33656155706e68516b5767755239766543317936525173797761316d444e655674427552704b656f306b56676d5657644263456f334c5130544e756d36796b310a7178777a464b544962303564435573522b4f494846744c534256645778626a63613957464c6a696b552b367a737576786d6d2b6f375a7058505a62504774362b0a624f727a6f53734e6745564a775255714146396f417450594c5a59787862385656674575304f30757841677532414445754432306a5134634e7847515479324e0a6448774f39654f2f3479306b3658546c485669786265596e5472645434715a5355385a4944562f382f46324663522b4a76426e4a48324b5071494f652b6e6f740a39552f6f686c6c35796a56786b3672393059467439526d744167353431674e6b4f4d4965356747565555594e6f316546516d65386b455a45754c66417366446d0a5057662b71496966597763577a446a4430624f4f3733535a51502f6f41516d3148472b4b54654f4f4f6f64566a46394531566539576a4d6c6b514152415141420a2f67634441747459313464424e556c4e38455472327a692b592f4c6c414f57776f65756c70676a3150464b2b6c76323736336d2b2b3433626c5a4545796470760a732f534574646a4d5655757a412b4b5568762b357a6d42674548576b365a79464b696c786c517971662f5173737a6b67423950736d452b477779664c507239540a4658504a754c596f6f4f46757836623967666d53596e7a592f52767a4171586a33355239776f702f6537574c6e495774747273724171707673626a484c3465750a575653314a4a366a73494f4e486f6653666842664646473347794368563153683938706c576e32436c657a484e4b55377a63325150664b4754475150784f42520a496371334264666d6f697864323551716d35464e5670716564736a782f2b6e525731586b357743466158584e5369336d4a6735614e31397538627a4a624f4c700a5333527a4b346b38386b6b4732416a3631537067617449307779505a304e6c5233412f366c6936576274456d387572416a775232336e33705a4542736d6b4f570a443538536e716753664b797257704e585745334477324378446541697a5655366272784478632b2b37684d6e694f33666e6e7a654961364f75427639457165450a6b5a537a736a7a2b36376a76696e52376b5a5a354470734756376844307a4d6f5063365534543564726b4b51674b57616854306c38507330696d39566662714d0a6761626b5a6946426f684a7a4c754d6c6e70337a2b7236304477456553353145374a3676676b663070664a34696b5564716d35447447337a4e4d6b446f6d65690a6354497337366f674e52444d5a34646e5145624b736c4570375a62625368336d733541495775566f6e314965634d4b70424e4e5136396c54764f3069633956610a526a447973625942327a77644e425861456e4f37514f74594b7a6961492f4850693064366f6273486a6b4c6642532b327139586e484a543061594945773431330a7357563158794e775777516c797a563234556d617376454450667847733774713659356b502f3145705162437657304159774356325251467249387639354d430a61656e4c4b345a35523537685163565a4161746e65383333535559304f694a66706d79747538535a552b6b4b595737683666655a7442356a7966625237615a310a6a634d30694936774753704f482f6c5339766657793363372b62333356445a4665695a564d6642636b5831414b736e7157526f37387a6378563869536b366b570a6d77447151472b6331555343424e362f7953486a3534626355754e6b563667534163656e6746516f704d6e4c2b352f36676e63504434567245533153587a53480a5776466a5679326d6f32574c6534464b48764f396d354c6c5767674f336c37317046307653324a782f4867385136734a5739426b63434177727858524b6d38620a5353344449456259356174306d2b514b326b5666776761793333794a614c7374634e76387247306b305a316a627a512f6c2f58513136522f4e72576f2b2f70710a712f456e5533786f54744a716d49454e546969706954637048537038756738644a7146524c37677a4e784f384f32704f5362724e594478395a564b58546e744d0a4a366137316976576c6c454d676c78492f456276706670567a674f5751395a703278564739665146582b6b6b6f3342336e79316874776e4a534e6e4447536e680a586a5a486c5a516a39586a4c7133692f4e58483047574645776b2b43672b336b637158327072426d75487269654b465a743851366d3649506453734e35306f350a5a593835383247433164316b2b4777756b6e73333035517763612b57715344674a6b35336d517144394563493232484956396b4f7a5465366f433053744a61660a32583954314b6559444f395949416555776761756f39636c484575736d4e336861313678365a5a703442714b466a5338714e477651636e7a3175717a524457730a78334d6447396c37744469763279556a64323241762f79396842495739457837724f77484f57614244774f2b474b4b46735a317641304d2f71364349302b534d0a61506146782b6f4e4b4c4f306e4f6632643374454d7a6e736b6d2f6534314a4644773239626a3273716b4d412f492b7055356f6c6d5551785a3876575a4779680a4332434b2b56344954336e4d2f48756d4d3249377554454e37626f582f72444c56456f3939396b394176677a76365850303042705542524a37695837584e75630a775049394664736346714d38656a4d4a63696234674953647a2f5a45744d6f486c644f6e2f654a6c796144414d4930754d6f4c7231545063436243557a522b6f0a3937672f654473326d536c74766d2b613833574669584654576a76754a56794a30773172466f3839585631732f4d6b376f7a4d3042395677347a4d59526f38760a43616d4b6349715862546c487552566e647a484244746b3239737775334974727a4b70737432783564385839776d75644158384853707130476b356c6132396c0a6369413861474e3559574e6e51485a70634335786353356a6232302b69514a5542424d424341412b4669454563704735542b4874396556364b4254564c564d680a38323157686e5146416d4a705754514347774d46435150435a77414643776b494277494746516f4a434173434242594341774543486745434634414143676b510a4c564d6838323157686e514b61772f2f577554732f6c716442337753303774344f386d5a737a2b6162586d64315043476430634d7a554770667534746c7162570a4a546437385551515073434574474635376763674a39525a54555038364d304b39584a63574f4b69464849675571346a6d32715063345a394464505631754f5a0a4a55562f577a754165472b415279714a72397a7a744a322f5335556c74693754374d77667566325a7a624f50525546734b6369505a52636d58317254624358760a536f6c424e67417a72436a41594e745a34426f4746366168394450427642764d7072336342693057396d2f594541435831325036436a5843794a333039724f4a0a536176694768374d4a515265464f3970466a6236623271676745397467656d2b355a596b5a6a722f4541794f51786938722b6b376467636d6c363741614243420a66675357612b59457a576141624d654377732f534c44557372526e71464c6b4d5841685154666a6a4166735a52454778424f79436946433642436b54773269390a71514e7a6144534f425a656f387a4c41524744374d5a466f6f5131685441565951735a7674492f7353454a715257645a653751434237543741464458327169750a70676a52786a716b2b79425068413437424c626852567841576a617847594a44585931796a6b665166614f566a716330397a49783550645441324b627853546d0a543231362f574c565a7a2f3869397a772b444834677839416b5254724a496359565456494a482b5142794d3862357131545542657154314a6d413869763068300a4155424c356456535337666f42676a59634c394952636c364231304e67625773534471704b75444135664a797657486a50444452767330336b7462426a50796d0a4c6541542f703970695a546b36577352443665626a2f3174615a6251686f68362f46783377647571656c5534334a326a357a396b376f6a324652553d0a3d6f5449380a2d2d2d2d2d454e44205047502050524956415445204b455920424c4f434b2d2d2d2d2d0a1a082f4577625536704f222c74327371474439522f306a7850626e5952526157644c75434271567575714d325753345a63364e46314a73322a0a636f6d2e686379616367 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-7.6.4-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MSYS* | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /libs/mirai-console-2.17.0-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/libs/mirai-console-2.17.0-all.jar -------------------------------------------------------------------------------- /libs/mirai-console-terminal-2.17.0-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/libs/mirai-console-terminal-2.17.0-all.jar -------------------------------------------------------------------------------- /libs/mirai-core-all-2.17.0-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nekoer/mirai-plugins-pixiv/aafed1aecf396f588335a5e2e12d83105952ed51/libs/mirai-core-all-2.17.0-all.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "pixiv" -------------------------------------------------------------------------------- /src/main/java/com/hcyacg/lowpoly/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.hcyacg.lowpoly; 2 | 3 | 4 | public class Configuration { 5 | 6 | public static final int DEFAULT_ACCURACY = 50; 7 | public static final float DEFAULT_SCALE = 1; 8 | public static final boolean DEFAULT_FILL = true; 9 | public static final String DEFAULT_FORMAT = "png"; 10 | public static final boolean DEFAULT_ANTI_ALIASING = false; 11 | public static final int DEFAULT_POINT_COUNT = 300; 12 | 13 | public static final String ACCURACY = "accuracy"; 14 | public static final String SCALE = "scale"; 15 | public static final String FILL = "fill"; 16 | public static final String FORMAT = "format"; 17 | public static final String ANTI_ALIASING = "antiAliasing"; 18 | public static final String POINT_COUNT = "pointCount"; 19 | 20 | 21 | public final int accuracy;//精度值,越小精度越高 22 | public final float scale;//缩放,源图片和目标图片的尺寸比例 23 | public final boolean fill;//是否填充颜色,为false时只绘制线条 24 | public final String format;//输出图片格式 25 | public final boolean antiAliasing;//是否抗锯齿 26 | public final int pointCount;//随机点的数量 27 | 28 | public Configuration() { 29 | this(DEFAULT_ACCURACY, DEFAULT_SCALE, DEFAULT_FILL, DEFAULT_FORMAT, DEFAULT_ANTI_ALIASING, DEFAULT_POINT_COUNT); 30 | } 31 | 32 | public Configuration(int accuracy, float scale, boolean fill, String format, boolean antiAliasing, int pointCount) { 33 | this.accuracy = accuracy; 34 | this.scale = scale; 35 | this.fill = fill; 36 | this.format = format; 37 | this.antiAliasing = antiAliasing; 38 | this.pointCount = pointCount; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/hcyacg/lowpoly/Delaunay.java: -------------------------------------------------------------------------------- 1 | package com.hcyacg.lowpoly; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * Delaunay算法 9 | */ 10 | final class Delaunay { 11 | 12 | private static final float EPSILON = 1.0f / 1048576.0f; 13 | 14 | private static int[][] superTriangle(List vertices) { 15 | int xmin = Integer.MAX_VALUE; 16 | int ymin = Integer.MAX_VALUE; 17 | int xmax = Integer.MIN_VALUE; 18 | int ymax = Integer.MIN_VALUE; 19 | 20 | float dx, dy, dmax, xmid, ymid; 21 | 22 | for (int i = vertices.size() - 1; i >= 0; i--) { 23 | int[] p = vertices.get(i); 24 | if (p[0] < xmin) xmin = p[0]; 25 | if (p[0] > xmax) xmax = p[0]; 26 | if (p[1] < ymin) ymin = p[1]; 27 | if (p[1] > ymax) ymax = p[1]; 28 | } 29 | 30 | dx = xmax - xmin; 31 | dy = ymax - ymin; 32 | 33 | dmax = Math.max(dx, dy); 34 | 35 | xmid = (xmin + dx * 0.5f); 36 | ymid = (ymin + dy * 0.5f); 37 | 38 | return new int[][]{{(int) (xmid - 20 * dmax), (int) (ymid - dmax)}, 39 | {(int) xmid, (int) (ymid + 20 * dmax)}, 40 | {(int) (xmid + 20 * dmax), (int) (ymid - dmax)}}; 41 | } 42 | 43 | private static CircumCircle circumcircle(List vertices, int i, int j, int k) { 44 | int x1 = vertices.get(i)[0]; 45 | int y1 = vertices.get(i)[1]; 46 | int x2 = vertices.get(j)[0]; 47 | int y2 = vertices.get(j)[1]; 48 | int x3 = vertices.get(k)[0]; 49 | int y3 = vertices.get(k)[1]; 50 | 51 | 52 | int fabsy1y2 = Math.abs(y1 - y2); 53 | int fabsy2y3 = Math.abs(y2 - y3); 54 | 55 | float xc, yc, m1, m2, mx1, mx2, my1, my2, dx, dy; 56 | 57 | // if (fabsy1y2 <= 0 && fabsy2y3 <= 0) { 58 | // throw new RuntimeException("Eek! Coincident points!"); 59 | // } 60 | 61 | if (fabsy1y2 <= 0) { 62 | m2 = -((float) (x3 - x2) / (y3 - y2)); 63 | mx2 = (x2 + x3) / 2f; 64 | my2 = (y2 + y3) / 2f; 65 | xc = (x2 + x1) / 2f; 66 | yc = m2 * (xc - mx2) + my2; 67 | } else if (fabsy2y3 <= 0) { 68 | m1 = -((float) (x2 - x1) / (y2 - y1)); 69 | mx1 = (x1 + x2) / 2f; 70 | my1 = (y1 + y2) / 2f; 71 | xc = (x3 + x2) / 2f; 72 | yc = m1 * (xc - mx1) + my1; 73 | } else { 74 | m1 = -((float) (x2 - x1) / (y2 - y1)); 75 | m2 = -((float) (x3 - x2) / (y3 - y2)); 76 | mx1 = (x1 + x2) / 2f; 77 | mx2 = (x2 + x3) / 2f; 78 | my1 = (y1 + y2) / 2f; 79 | my2 = (y2 + y3) / 2f; 80 | xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2); 81 | yc = (fabsy1y2 > fabsy2y3) ? 82 | m1 * (xc - mx1) + my1 : 83 | m2 * (xc - mx2) + my2; 84 | } 85 | 86 | dx = x2 - xc; 87 | dy = y2 - yc; 88 | 89 | return new CircumCircle(i, j, k, xc, yc, (dx * dx + dy * dy)); 90 | } 91 | 92 | private static void dedup(ArrayList edges) { 93 | int a, b, m, n; 94 | for (int j = edges.size(); j > 0; ) { 95 | while (j > edges.size()) { 96 | j--; 97 | } 98 | if (j <= 0) { 99 | break; 100 | } 101 | b = edges.get(--j); 102 | a = edges.get(--j); 103 | 104 | for (int i = j; i > 0; ) { 105 | n = edges.get(--i); 106 | m = edges.get(--i); 107 | 108 | if ((a == m && b == n) || (a == n && b == m)) { 109 | if (j + 1 < edges.size()) 110 | edges.remove(j + 1); 111 | edges.remove(j); 112 | if (i + 1 < edges.size()) 113 | edges.remove(i + 1); 114 | edges.remove(i); 115 | break; 116 | } 117 | } 118 | } 119 | } 120 | 121 | static List triangulate(List vertices) { 122 | int n = vertices.size(); 123 | 124 | if (n < 3) { 125 | return new ArrayList<>(); 126 | } 127 | 128 | Integer[] indices = new Integer[n]; 129 | 130 | for (int i = n - 1; i >= 0; i--) { 131 | indices[i] = i; 132 | } 133 | 134 | Arrays.sort(indices, (o1, o2) -> vertices.get(o2)[0] - vertices.get(o1)[0]); 135 | 136 | int[][] st = superTriangle(vertices); 137 | 138 | vertices.add(st[0]); 139 | vertices.add(st[1]); 140 | vertices.add(st[2]); 141 | 142 | ArrayList open = new ArrayList<>(); 143 | open.add(circumcircle(vertices, n, n + 1, n + 2)); 144 | 145 | ArrayList closed = new ArrayList<>(); 146 | 147 | ArrayList edges = new ArrayList<>(); 148 | 149 | for (int i = indices.length - 1; i >= 0; i--) { 150 | 151 | int c = indices[i]; 152 | 153 | for (int j = open.size() - 1; j >= 0; j--) { 154 | 155 | CircumCircle cj = open.get(j); 156 | int[] vj = vertices.get(c); 157 | 158 | float dx = vj[0] - cj.x; 159 | 160 | if (dx > 0 && dx * dx > cj.r) { 161 | closed.add(cj); 162 | open.remove(j); 163 | continue; 164 | } 165 | 166 | float dy = vj[1] - cj.y; 167 | 168 | if (dx * dx + dy * dy - cj.r > EPSILON) { 169 | continue; 170 | } 171 | 172 | edges.add(cj.i); 173 | edges.add(cj.j); 174 | edges.add(cj.j); 175 | edges.add(cj.k); 176 | edges.add(cj.k); 177 | edges.add(cj.i); 178 | 179 | open.remove(j); 180 | } 181 | 182 | dedup(edges); 183 | 184 | for (int j = edges.size(); j > 0; ) { 185 | int b = edges.get(--j); 186 | int a = edges.get(--j); 187 | open.add(circumcircle(vertices, a, b, c)); 188 | } 189 | 190 | edges.clear(); 191 | 192 | } 193 | 194 | for (int i = open.size() - 1; i >= 0; i--) { 195 | closed.add(open.get(i)); 196 | } 197 | 198 | open.clear(); 199 | 200 | ArrayList out = new ArrayList<>(); 201 | 202 | for (int i = closed.size() - 1; i >= 0; i--) { 203 | CircumCircle ci = closed.get(i); 204 | if (ci.i < n && ci.j < n && ci.k < n) { 205 | out.add((int) ci.i); 206 | out.add((int) ci.j); 207 | out.add((int) ci.k); 208 | } 209 | } 210 | return out; 211 | } 212 | 213 | private static class CircumCircle { 214 | public int i, j, k; 215 | float x, y, r; 216 | 217 | public CircumCircle(int i, int j, int k, float x, float y, float r) { 218 | this.i = i; 219 | this.j = j; 220 | this.k = k; 221 | this.x = x; 222 | this.y = y; 223 | this.r = r; 224 | } 225 | } 226 | 227 | 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/com/hcyacg/lowpoly/LowPoly.java: -------------------------------------------------------------------------------- 1 | package com.hcyacg.lowpoly; 2 | 3 | import javax.imageio.ImageIO; 4 | import java.awt.*; 5 | import java.awt.image.BufferedImage; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | 13 | public final class LowPoly { 14 | 15 | public static ByteArrayOutputStream generate(InputStream inputStream, Configuration configuration) throws IOException { 16 | return generate(inputStream, configuration.accuracy, configuration.scale, configuration.fill, configuration.format, configuration.antiAliasing, configuration.pointCount); 17 | } 18 | 19 | /** 20 | * 生成low poly风格的图片 21 | * 22 | * @param inputStream 源图片 23 | * @param accuracy 精度值,越小精度越高 24 | * @param scale 缩放,源图片和目标图片的尺寸比例 25 | * @param fill 是否填充颜色,为false时只绘制线条 26 | * @param format 输出图片格式 27 | * @param antiAliasing 是否抗锯齿 28 | * @param pointCount 随机点的数量 29 | * @throws IOException 30 | */ 31 | public static ByteArrayOutputStream generate(InputStream inputStream, int accuracy, float scale, boolean fill, String format, boolean antiAliasing, int pointCount) throws IOException { 32 | if (inputStream == null) { 33 | return null; 34 | } 35 | BufferedImage image = ImageIO.read(inputStream); 36 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 37 | 38 | int width = image.getWidth(); 39 | int height = image.getHeight(); 40 | 41 | ArrayList collectors = new ArrayList<>(); 42 | ArrayList particles = new ArrayList<>(); 43 | 44 | Sobel.sobel(image, (magnitude, x, y) -> { 45 | if (magnitude > 40) { 46 | collectors.add(new int[]{x, y}); 47 | } 48 | }); 49 | 50 | for (int i = 0; i < pointCount; i++) { 51 | particles.add(new int[]{(int) (Math.random() * width), (int) (Math.random() * height)}); 52 | } 53 | 54 | int len = collectors.size() / accuracy; 55 | for (int i = 0; i < len; i++) { 56 | int random = (int) (Math.random() * collectors.size()); 57 | particles.add(collectors.get(random)); 58 | collectors.remove(random); 59 | } 60 | 61 | particles.add(new int[]{0, 0}); 62 | particles.add(new int[]{0, height}); 63 | particles.add(new int[]{width, 0}); 64 | particles.add(new int[]{width, height}); 65 | 66 | List triangles = Delaunay.triangulate(particles); 67 | 68 | float x1, x2, x3, y1, y2, y3, cx, cy; 69 | 70 | BufferedImage out = new BufferedImage((int) (width * scale), (int) (height * scale), BufferedImage.TYPE_INT_ARGB); 71 | 72 | Graphics g = out.getGraphics(); 73 | ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAliasing ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_DEFAULT); 74 | 75 | for (int i = 0; i < triangles.size(); i += 3) { 76 | x1 = particles.get(triangles.get(i))[0]; 77 | x2 = particles.get(triangles.get(i + 1))[0]; 78 | x3 = particles.get(triangles.get(i + 2))[0]; 79 | y1 = particles.get(triangles.get(i))[1]; 80 | y2 = particles.get(triangles.get(i + 1))[1]; 81 | y3 = particles.get(triangles.get(i + 2))[1]; 82 | 83 | cx = (x1 + x2 + x3) / 3; 84 | cy = (y1 + y2 + y3) / 3; 85 | 86 | Color color = new Color(image.getRGB((int) cx, (int) cy)); 87 | 88 | g.setColor(color); 89 | if (fill) { 90 | g.fillPolygon(new int[]{(int) (x1 * scale), (int) (x2 * scale), (int) (x3 * scale)}, new int[]{(int) (y1 * scale), (int) (y2 * scale), (int) (y3 * scale)}, 3); 91 | } else { 92 | g.drawPolygon(new int[]{(int) (x1 * scale), (int) (x2 * scale), (int) (x3 * scale)}, new int[]{(int) (y1 * scale), (int) (y2 * scale), (int) (y3 * scale)}, 3); 93 | } 94 | } 95 | 96 | ImageIO.write(out, format, byteArrayOutputStream); 97 | 98 | return byteArrayOutputStream; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/hcyacg/lowpoly/Sobel.java: -------------------------------------------------------------------------------- 1 | package com.hcyacg.lowpoly; 2 | 3 | import java.awt.*; 4 | import java.awt.image.BufferedImage; 5 | 6 | /** 7 | * 边缘检测算法 8 | */ 9 | final class Sobel { 10 | 11 | private static final int[][] kernelX = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; 12 | 13 | private static final int[][] kernelY = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; 14 | 15 | static void sobel(BufferedImage image, SobelCallback callback) { 16 | int w = image.getWidth(); 17 | int h = image.getHeight(); 18 | 19 | for (int y = 0; y < h; y++) { 20 | for (int x = 0; x < w; x++) { 21 | int pixelX = ( 22 | (kernelX[0][0] * getAvg(image, x - 1, y - 1)) + 23 | (kernelX[0][1] * getAvg(image, x, y - 1)) + 24 | (kernelX[0][2] * getAvg(image, x + 1, y - 1)) + 25 | (kernelX[1][0] * getAvg(image, x - 1, y)) + 26 | (kernelX[1][1] * getAvg(image, x, y)) + 27 | (kernelX[1][2] * getAvg(image, x + 1, y)) + 28 | (kernelX[2][0] * getAvg(image, x - 1, y + 1)) + 29 | (kernelX[2][1] * getAvg(image, x, y + 1)) + 30 | (kernelX[2][2] * getAvg(image, x + 1, y + 1)) 31 | ); 32 | int pixelY = ( 33 | (kernelY[0][0] * getAvg(image, x - 1, y - 1)) + 34 | (kernelY[0][1] * getAvg(image, x, y - 1)) + 35 | (kernelY[0][2] * getAvg(image, x + 1, y - 1)) + 36 | (kernelY[1][0] * getAvg(image, x - 1, y)) + 37 | (kernelY[1][1] * getAvg(image, x, y)) + 38 | (kernelY[1][2] * getAvg(image, x + 1, y)) + 39 | (kernelY[2][0] * getAvg(image, x - 1, y + 1)) + 40 | (kernelY[2][1] * getAvg(image, x, y + 1)) + 41 | (kernelY[2][2] * getAvg(image, x + 1, y + 1)) 42 | ); 43 | 44 | int magnitude = (int) Math.sqrt((pixelX * pixelX) + (pixelY * pixelY)); 45 | callback.call(magnitude, x, y); 46 | } 47 | } 48 | 49 | } 50 | 51 | private static int getAvg(BufferedImage image, int x, int y) { 52 | if (x < 0 || y < 0 || x >= image.getWidth() || y >= image.getHeight()) { 53 | return 0; 54 | } 55 | Color color = new Color(image.getRGB(x, y)); 56 | return (color.getRed() + color.getGreen() + color.getBlue()) / 3; 57 | } 58 | 59 | protected interface SobelCallback { 60 | void call(int magnitude, int x, int y); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/hcyacg/lowpoly/utils/CheckFileTypeUtil.java: -------------------------------------------------------------------------------- 1 | package com.hcyacg.lowpoly.utils; 2 | 3 | import kotlinx.serialization.json.Json; 4 | import net.mamoe.mirai.utils.MiraiLogger; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | public class CheckFileTypeUtil { 15 | 16 | 17 | private static final MiraiLogger logger = MiraiLogger.Factory.INSTANCE.create(CheckFileTypeUtil.class); 18 | 19 | private CheckFileTypeUtil() { 20 | 21 | } 22 | 23 | /** 24 | * 缓存文件头信息-文件头信息 25 | */ 26 | private static final HashMap FILE_TYPE_MAP = new HashMap<>(); 27 | 28 | static { 29 | // JPEG, jpg 30 | FILE_TYPE_MAP.put("FFD8FF", "jpg"); 31 | FILE_TYPE_MAP.put("89504E", "png"); 32 | FILE_TYPE_MAP.put("47494638", "gif"); 33 | // Matroska stream file (mkv) 34 | FILE_TYPE_MAP.put("1A45DFA3934282886D6174726F736B61", "mkv"); 35 | // MPEG-4 video files (mp4) 36 | FILE_TYPE_MAP.put("000000", "mp4"); 37 | FILE_TYPE_MAP.put("255044", "pdf"); 38 | // MS Compound Document v1 or Lotus Approach APR file 39 | // doc, dot, xls, xlt, xla, ppt, apr, ppa, pps, pot, msi, sdw, db 40 | FILE_TYPE_MAP.put("D0CF11E0", "doc"); 41 | FILE_TYPE_MAP.put("504B0304", "docx"); 42 | FILE_TYPE_MAP.put("D0CF11E0", "xls"); 43 | FILE_TYPE_MAP.put("504B0304", "xlsx"); 44 | FILE_TYPE_MAP.put("D0CF11E0", "ppt"); 45 | FILE_TYPE_MAP.put("504b0304", "pptx"); 46 | 47 | // TIFF, tif 48 | FILE_TYPE_MAP.put("49492A", "tif"); 49 | // 16色位图 (bmp) 50 | FILE_TYPE_MAP.put("424d22", "bmp"); 51 | // 24位位图 (bmp) 52 | FILE_TYPE_MAP.put("424d82", "bmp"); 53 | // 256色位图 (bmp) 54 | FILE_TYPE_MAP.put("424d8e", "bmp"); 55 | FILE_TYPE_MAP.put("414331", "dwg"); 56 | FILE_TYPE_MAP.put("3C2144", "htm"); 57 | // HyperText Markup Language 3 (html) 58 | FILE_TYPE_MAP.put("3C21444F4354", "html"); 59 | FILE_TYPE_MAP.put("48544d", "css"); 60 | FILE_TYPE_MAP.put("696b2e", "js"); 61 | // Rich Text Format (rtf) 62 | FILE_TYPE_MAP.put("7B5C72", "rtf"); 63 | // Photoshop (psd) 64 | FILE_TYPE_MAP.put("384250", "psd"); 65 | // Icon File (ico) 66 | FILE_TYPE_MAP.put("00000100", "ico"); 67 | // Email (eml) 68 | FILE_TYPE_MAP.put("44656C69766572792D646174653A", "eml"); 69 | // MS Access (mdb, mda, mde, mdt) 70 | FILE_TYPE_MAP.put("5374616E64617264204A", "mdb"); 71 | // Postscript (ps, eps) 72 | FILE_TYPE_MAP.put("252150532D41646F6265", "ps"); 73 | // Adobe EPS File (eps) 74 | FILE_TYPE_MAP.put("25215053", "eps"); 75 | // Real Media streaming media file (rm, rmvb) 76 | FILE_TYPE_MAP.put("2E524D46", "rmvb"); 77 | FILE_TYPE_MAP.put("2E524D", "rm"); 78 | // Flash video file (flv, f4v) 79 | FILE_TYPE_MAP.put("464C5601", "flv"); 80 | // MPEG-1 Audio Layer 3 (MP3) audio file (mp3) 81 | FILE_TYPE_MAP.put("494433", "mp3"); 82 | // MPEG (mpg) 83 | FILE_TYPE_MAP.put("000001BA", "mpg"); 84 | // MPEG Movie (mpg, mpeg) 85 | FILE_TYPE_MAP.put("000001B3", "mpg"); 86 | FILE_TYPE_MAP.put("3026b2", "wmv"); 87 | // Windows Media (asf) 88 | FILE_TYPE_MAP.put("3026B2758E66CF11", "asf"); 89 | // Wave (wav) 90 | FILE_TYPE_MAP.put("57415645", "wav"); 91 | // Audio Video Interleave (avi) 92 | FILE_TYPE_MAP.put("41564920", "avi"); 93 | // Musical Instrument Digital Interface (MIDI) sound file (MID, MIDI) 94 | FILE_TYPE_MAP.put("4D546864", "mid"); 95 | // ZIP Archive (zip, jar, zipx) 96 | FILE_TYPE_MAP.put("504B3030504B0304", "zip"); 97 | // RAR Archive File (rar) 98 | FILE_TYPE_MAP.put("52617221", "rar"); 99 | FILE_TYPE_MAP.put("235468", "ini"); 100 | // EXE, DLL, OCX, OLB, IMM, IME 101 | FILE_TYPE_MAP.put("4d5a90", "exe"); 102 | FILE_TYPE_MAP.put("3c2540", "jsp"); 103 | FILE_TYPE_MAP.put("4d616e", "mf"); 104 | // XML Document (xml) 105 | FILE_TYPE_MAP.put("3C3F786D6C", "xml"); 106 | FILE_TYPE_MAP.put("494e53", "sql"); 107 | FILE_TYPE_MAP.put("706163", "java"); 108 | FILE_TYPE_MAP.put("406563", "bat"); 109 | // Gzip Archive File (gz, tar, tgz) 110 | FILE_TYPE_MAP.put("1f8b", "gz"); 111 | FILE_TYPE_MAP.put("6c6f67", "properties"); 112 | FILE_TYPE_MAP.put("cafeba", "class"); 113 | // Microsoft Compiled HTML Help File (chm) 114 | FILE_TYPE_MAP.put("49545346", "chm"); 115 | FILE_TYPE_MAP.put("040000", "mxp"); 116 | FILE_TYPE_MAP.put("643130", "torrent"); 117 | // Quicktime (mov) 118 | FILE_TYPE_MAP.put("6D6F6F76", "mov"); 119 | // WordPerfect (wpd) 120 | FILE_TYPE_MAP.put("FF575043", "wpd"); 121 | // Outlook Express (dbx) 122 | FILE_TYPE_MAP.put("CFAD12FEC5FD746F", "dbx"); 123 | // Microsoft Outlook Personal Folder file (pst) 124 | FILE_TYPE_MAP.put("2142444E", "pst"); 125 | // Quicken (qdf) 126 | FILE_TYPE_MAP.put("AC9EBD8F", "qdf"); 127 | // Windows Password (pwl) 128 | FILE_TYPE_MAP.put("E3828596", "pwl"); 129 | // Real Audio File (ra, ram) 130 | FILE_TYPE_MAP.put("2E7261FD", "ram"); 131 | } 132 | 133 | /** 134 | * 根据文件路径获取文件原始格式 135 | * 136 | * @param filePath 文件路径 137 | * @return 文件头信息 138 | */ 139 | public static String getFileType(String filePath) { 140 | 141 | // 根据文件路径获取文件头信息 142 | String header = getFileHeader(filePath); 143 | 144 | String fileType = ""; 145 | 146 | if (StringUtils.isNotBlank(header)) { 147 | List lFileTypes = new ArrayList<>(FILE_TYPE_MAP.keySet()); 148 | 149 | for (String lFileType : lFileTypes) { 150 | if (header.contains(lFileType.toUpperCase()) || lFileType.toUpperCase().contains(header)) { 151 | fileType = FILE_TYPE_MAP.get(lFileType); 152 | break; 153 | } 154 | } 155 | } 156 | return fileType; 157 | } 158 | 159 | /** 160 | * 根据文件路径获取文件头信息 161 | * 162 | * @param filePath 文件路径 163 | * @return 文件头信息 164 | */ 165 | public static String getFileHeader(String filePath) { 166 | 167 | String value = null; 168 | try (FileInputStream is = new FileInputStream(filePath)) { 169 | byte[] b = new byte[4]; 170 | /* 171 | * int read() 从此输入流中读取一个数据字节。int read(byte[] b) 从此输入流中将最多 b.length 172 | * 个字节的数据读入一个 byte 数组中。 int read(byte[] b, int off, int len) 173 | * 从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。 174 | */ 175 | is.read(b, 0, b.length); 176 | 177 | // 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写 178 | value = bytesToHexString(b); 179 | 180 | } catch (IOException e) { 181 | logger.info("获取文件头信息失败"); 182 | } 183 | return value; 184 | } 185 | 186 | /** 187 | * 将要读取文件头信息的文件的byte数组转换成string类型表示 188 | * 189 | * @param src 要读取文件头信息的文件的byte数组 190 | * @return 文件头信息 191 | */ 192 | private static String bytesToHexString(byte[] src) { 193 | 194 | StringBuilder builder = new StringBuilder(); 195 | if (src == null || src.length <= 0) { 196 | return null; 197 | } 198 | String hv; 199 | for (byte b : src) { 200 | // 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写 201 | hv = Integer.toHexString(b & 0xFF).toUpperCase(); 202 | if (hv.length() < 2) { 203 | builder.append(0); 204 | } 205 | builder.append(hv); 206 | } 207 | return builder.toString(); 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/AutoUpdate.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg 2 | 3 | import com.hcyacg.Pixiv.save 4 | import com.hcyacg.entity.GithubReleaseItem 5 | import com.hcyacg.initial.Github 6 | import com.hcyacg.utils.DownloadUtil 7 | import com.hcyacg.utils.RequestUtil 8 | import com.hcyacg.utils.logger 9 | import kotlinx.serialization.json.Json 10 | import kotlinx.serialization.json.decodeFromJsonElement 11 | import net.mamoe.mirai.console.ConsoleFrontEndImplementation 12 | import net.mamoe.mirai.console.MiraiConsole 13 | import net.mamoe.mirai.console.plugin.version 14 | import okhttp3.Headers 15 | import okhttp3.RequestBody 16 | import java.util.* 17 | 18 | 19 | /** 20 | * 自动更新 21 | */ 22 | object AutoUpdate { 23 | const val githubUrl = "https://api.github.com/repos/Nekoer/mirai-plugins-pixiv/releases?per_page=1" 24 | private val headers = Headers.Builder() 25 | private val requestBody: RequestBody? = null 26 | private val logger by logger() 27 | private val json = Json { ignoreUnknownKeys = true } 28 | 29 | /** 30 | * 加载数据并轮询 每小时查询一次 31 | */ 32 | @OptIn(ConsoleFrontEndImplementation::class) 33 | fun load() { 34 | Timer().schedule(object : TimerTask() { 35 | override fun run() { 36 | val data = 37 | RequestUtil.request(RequestUtil.Companion.Method.GET, githubUrl, requestBody, headers.build()) 38 | 39 | val githubRelease = data?.let { json.decodeFromJsonElement>(it) } ?: return 40 | 41 | /** 42 | * 判断是否有内容 43 | */ 44 | if (githubRelease.isEmpty()) { 45 | return 46 | } 47 | 48 | if (githubRelease[0].id!! != Github.versionId) { 49 | /** 50 | * 如果本地version-id与github的不同,则说明可能有新版本;继续进行tag和插件版本进行对比 51 | */ 52 | if (!githubRelease[0].tagName.contentEquals(Pixiv.version.toString())) { 53 | 54 | /** 55 | * 对比后进行下载 56 | */ 57 | 58 | 59 | val temp = if (githubRelease[0].assets?.get(0)?.browserDownloadUrl!!.indexOf("?") > -1) { 60 | githubRelease[0].assets?.get(0)?.browserDownloadUrl!!.substring( 61 | 0, 62 | githubRelease[0].assets?.get(0)?.browserDownloadUrl!!.indexOf("?") 63 | ).split("/").last() 64 | } else { 65 | githubRelease[0].assets?.get(0)?.browserDownloadUrl!!.split("/").last() 66 | } 67 | 68 | logger.info { "$temp 更新开始" } 69 | 70 | DownloadUtil.download( 71 | githubRelease[0].assets?.get(0)?.browserDownloadUrl!!.replace( 72 | "https://github.com/", 73 | "https://download.fastgit.org/" 74 | ), 75 | MiraiConsole.pluginManager.pluginsFolder.path, 76 | object : DownloadUtil.OnDownloadListener { 77 | override fun onDownloadSuccess() { 78 | logger.info { "$temp 更新完成" } 79 | 80 | /** 81 | * 插件下载完后覆盖本地的version-id并保存 82 | */ 83 | Github.versionId = githubRelease[0].id!! 84 | Github.save() 85 | 86 | /** 87 | * 启动mcl后关闭本程序 88 | */ 89 | logger.info { "请删除旧版本插件并重启程序" } 90 | 91 | // val name = ManagementFactory.getRuntimeMXBean().name 92 | // val pid: String = name.split("@")[0] 93 | // val cmd = arrayOf("cmd.exe", "/c", "taskkill -f /pid $pid") 94 | // Runtime.getRuntime().exec(arrayOf("cmd.exe", "/k", "java -jar ${File("").canonicalPath + File.separator}mcl.jar"),null,File(File("").canonicalPath)).waitFor() 95 | // Runtime.getRuntime().exec(cmd,null, File(File("").canonicalPath)).waitFor() 96 | } 97 | 98 | override fun onDownloading(progress: Int) { 99 | logger.info { "$temp 已下载 $progress %" } 100 | } 101 | 102 | override fun onDownloadFailed() { 103 | logger.warn { "$temp 更新失败" } 104 | } 105 | } 106 | ) 107 | 108 | } 109 | } 110 | } 111 | }, Date(), 60 * 60 * 1000L) 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/Nsfw.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg 2 | 3 | 4 | import com.hcyacg.initial.Config 5 | import com.hcyacg.utils.* 6 | import kotlinx.serialization.json.* 7 | import net.mamoe.mirai.console.MiraiConsole 8 | import net.mamoe.mirai.event.events.GroupMessageEvent 9 | import net.mamoe.mirai.message.data.At 10 | import net.mamoe.mirai.message.data.MessageChain 11 | import net.mamoe.mirai.message.data.QuoteReply 12 | import okhttp3.* 13 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 14 | import okhttp3.RequestBody.Companion.toRequestBody 15 | import org.jsoup.Jsoup 16 | import java.io.File 17 | import java.math.RoundingMode 18 | import java.net.InetSocketAddress 19 | import java.net.Proxy 20 | import java.text.DecimalFormat 21 | import java.util.* 22 | import java.util.concurrent.TimeUnit 23 | 24 | 25 | object Nsfw { 26 | private val logger by logger() 27 | private val headers = Headers.Builder() 28 | private var requestBody: RequestBody? = null 29 | private val client = OkHttpClient().newBuilder().connectTimeout(60000, TimeUnit.MILLISECONDS) 30 | .readTimeout(60000, TimeUnit.MILLISECONDS).followRedirects(false) 31 | private val tags = mutableMapOf() 32 | private val json = Json 33 | const val URL = "https://fastly.jsdelivr.net/gh/Nekoer/mirai-plugins-pixiv@master/src/main/resources/tags.json" 34 | init { 35 | val file = File(MiraiConsole.pluginManager.pluginsConfigFolder.path + File.separator +"com.hcyacg.pixiv"+ File.separator+ "tags.json") 36 | 37 | if (!file.exists()){ 38 | DownloadUtil.download( 39 | URL, 40 | file.parentFile.path, 41 | object : DownloadUtil.OnDownloadListener { 42 | override fun onDownloadSuccess() { 43 | logger.info { "tags.json下载成功" } 44 | val jsonTemp = json.parseToJsonElement(file.readText()) 45 | jsonTemp.jsonObject["data"]!!.jsonObject.forEach { t, u -> 46 | tags[t] = u.jsonPrimitive.content 47 | } 48 | } 49 | 50 | override fun onDownloading(progress: Int) { 51 | 52 | } 53 | 54 | override fun onDownloadFailed() { 55 | logger.warn { 56 | ("tags.json下载失败,请使用代理或者手动下载 ${ 57 | URL.replace( 58 | "raw.githubusercontent.com", 59 | "raw.fastgit.org" 60 | ) 61 | } 并存放到 ${file.parentFile.path}") 62 | } 63 | 64 | } 65 | } 66 | ) 67 | }else{ 68 | val data = RequestUtil.request(RequestUtil.Companion.Method.GET,URL,null, headers.build()) 69 | val jsonTemp = json.parseToJsonElement(file.readText()) 70 | if (data != null) { 71 | if (!data.jsonObject["version"]!!.jsonPrimitive.content.contentEquals(jsonTemp.jsonObject["version"]!!.jsonPrimitive.content)){ 72 | DownloadUtil.download( 73 | URL, 74 | file.parentFile.path, 75 | object : DownloadUtil.OnDownloadListener { 76 | override fun onDownloadSuccess() { 77 | logger.info { "tags.json下载成功" } 78 | jsonTemp.jsonObject["data"]!!.jsonObject.forEach { t, u -> 79 | tags[t] = u.jsonPrimitive.content 80 | } 81 | } 82 | 83 | override fun onDownloading(progress: Int) { 84 | 85 | } 86 | 87 | override fun onDownloadFailed() { 88 | logger.warn { 89 | ("tags.json下载失败,请使用代理或者手动下载 ${ 90 | URL.replace( 91 | "raw.githubusercontent.com", 92 | "raw.fastgit.org" 93 | ) 94 | } 并存放到 ${file.parentFile.path}") 95 | } 96 | } 97 | } 98 | ) 99 | }else{ 100 | jsonTemp.jsonObject["data"]!!.jsonObject.forEach { t, u -> 101 | tags[t] = u.jsonPrimitive.content 102 | } 103 | } 104 | }else{ 105 | jsonTemp.jsonObject["data"]!!.jsonObject.forEach { t, u -> 106 | tags[t] = u.jsonPrimitive.content 107 | } 108 | } 109 | } 110 | 111 | 112 | } 113 | 114 | suspend fun load(event: GroupMessageEvent) { 115 | logger.info { "监控中……" } 116 | val picUri = DataUtil.getImageLink(event.message) ?: return 117 | event.subject.sendMessage(At(event.sender).plus("检测中,请稍后")) 118 | 119 | val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM) 120 | val body = ImageUtil.getImage(picUri, CacheUtil.Type.NONSUPPORT).toByteArray() 121 | val bodies = body.toRequestBody( 122 | "multipart/form-data".toMediaTypeOrNull(), 123 | 0, body.size 124 | ) 125 | val name = UUID.randomUUID().toString() + ".jpeg" 126 | requestBody.addFormDataPart("file", name, bodies) 127 | requestBody.addFormDataPart("network_type", "general") 128 | 129 | var uri = "http://dev.kanotype.net:8003/deepdanbooru/upload" 130 | 131 | val host = Config.proxy.host 132 | val port = Config.proxy.port 133 | headers.add("Content-Type", "multipart/form-data;boundary=----WebKitFormBoundaryHxQHPpxAl7v7sCSa") 134 | val response: Response? = try { 135 | if (host.isBlank() || port == -1) { 136 | client.build().newCall(Request.Builder().url(uri).headers(headers.build()).post(requestBody.build()).build()).execute() 137 | } else { 138 | val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port)) 139 | client.proxy(proxy).build().newCall(Request.Builder().url(uri).headers(headers.build()).post(requestBody.build()).build()).execute() 140 | } 141 | }catch (e: Exception){ 142 | logger.error { e.message } 143 | null 144 | } 145 | 146 | val location = response?.header("location") 147 | 148 | //构建消息体 149 | var quoteReply: MessageChain = QuoteReply(event.message).plus("") 150 | val format = DecimalFormat("#.##") 151 | //舍弃规则,RoundingMode.FLOOR表示直接舍弃。 152 | format.roundingMode = RoundingMode.FLOOR 153 | 154 | if (response?.code != 302){ 155 | logger.warn { "HTTP代码:${response?.code}" } 156 | 157 | uri = "http://${Config.deepdanbooru}/deepdanbooru" 158 | val requestB = MultipartBody.Builder().setType(MultipartBody.FORM) 159 | 160 | requestB.addFormDataPart("image" ,name, bodies) 161 | 162 | headers.add("Content-Type", "multipart/form-data;boundary=ebf9f03029db4c2799ae16b5428b06bd1") 163 | headers.add("Accept", "application/json") 164 | val data = try { 165 | RequestUtil.request(RequestUtil.Companion.Method.POST, uri,requestB.build(),headers.build()) 166 | }catch (e:Exception){ 167 | logger.warn { e.message } 168 | null 169 | } 170 | logger.debug { data } 171 | data?.jsonArray?.forEach { 172 | val tag = it.jsonObject["tag"]?.jsonPrimitive?.content 173 | val score = it.jsonObject["score"]?.jsonPrimitive?.float 174 | 175 | var lv = format.format(score).replace("0.", "") 176 | if (lv.length < 2) { 177 | lv = lv.plus("0") 178 | } 179 | if (lv.toInt() >= 90) { 180 | lv = lv.plus("%") 181 | quoteReply = if (null != tags[tag] && tags[tag] != ""){ 182 | quoteReply.plus("${tags[tag]}(${tag}):$lv\n") 183 | }else{ 184 | quoteReply.plus("${tag}:$lv\n") 185 | } 186 | } 187 | } 188 | 189 | }else { 190 | var doc = Jsoup.connect("http://dev.kanotype.net:8003$location").get() 191 | doc = Jsoup.parse(doc.html()) 192 | val tables = doc.body().getElementsByTag("table") 193 | 194 | 195 | 196 | tables.forEach { table -> 197 | val theads = table.select("thead > tr > th") 198 | val tbody = table.getElementsByTag("tbody") 199 | theads.forEach { thead -> 200 | val title = thead.text() 201 | title.let { 202 | if (it.contentEquals("General Tags")){ 203 | if (tbody.size>=1){ 204 | val trs = tbody[0].select("tr") 205 | //其他标签 general 206 | quoteReply = quoteReply.plus("===其他标签===\n") 207 | trs.forEach {tr -> 208 | val td = tr.select("td") 209 | val tag = td[0].getElementsByTag("a").text() 210 | val score = td[1].text() 211 | var lv = format.format(score.toFloat()).replace("0.", "") 212 | if (lv.length < 2) { 213 | lv = lv.plus("0") 214 | } 215 | if (lv.toInt() >= 90) { 216 | lv = lv.plus("%") 217 | quoteReply = if (null != tags[tag] && tags[tag] != ""){ 218 | quoteReply.plus("${tags[tag]}(${tag}):$lv\n") 219 | }else{ 220 | quoteReply.plus("${tag}:$lv\n") 221 | } 222 | } 223 | }} 224 | 225 | } 226 | if (it.contentEquals("Character Tags")){ 227 | if (tbody.size>=2){ 228 | val trs = tbody[1].select("tr") 229 | //角色识别 character 230 | quoteReply = quoteReply.plus("===角色识别===\n") 231 | trs.forEach {tr -> 232 | logger.debug { tr } 233 | val td = tr.select("td") 234 | val tag = td[0].getElementsByTag("a").text() 235 | val score = td[1].text() 236 | 237 | var lv = format.format(score.toFloat()).replace("0.", "") 238 | if (lv.length < 2) { 239 | lv = lv.plus("0") 240 | } 241 | lv = lv.plus("%") 242 | quoteReply = if (null != tags[tag] && tags[tag] != ""){ 243 | quoteReply.plus("${tags[tag]}(${tag}):$lv\n") 244 | }else{ 245 | quoteReply.plus("${tag}:$lv\n") 246 | } 247 | } 248 | } 249 | 250 | } 251 | if (it.contentEquals("System Tags")){ 252 | if (tbody.size>=3){ 253 | val trs = tbody[2].select("tr") 254 | //安全性 system 255 | quoteReply = quoteReply.plus("===安全性===\n") 256 | trs.forEach {tr -> 257 | val td = tr.select("td") 258 | val tag = td[0].getElementsByTag("a").text() 259 | val score = td[1].text() 260 | 261 | var lv = format.format(score.toFloat()).replace("0.", "") 262 | 263 | if (lv.length < 2) { 264 | lv = lv.plus("0") 265 | } 266 | 267 | lv = lv.plus("%") 268 | quoteReply = if (null != tags[tag] && tags[tag] != ""){ 269 | quoteReply.plus("${tags[tag]}(${tag}):$lv\n") 270 | }else{ 271 | quoteReply.plus("${tag}:$lv\n") 272 | } 273 | } 274 | } 275 | 276 | } 277 | } 278 | } 279 | 280 | 281 | 282 | } 283 | } 284 | 285 | event.subject.sendMessage(quoteReply.plus("\n").plus("本功能不保证长期使用,并且标签为机翻,如果有错误请到Github仓库PR")) 286 | } 287 | 288 | private fun translate(data: MutableList): JsonElement? { 289 | return try { 290 | // val url = "http://fanyi.youdao.com/translate?&doctype=json&type=EN2ZH_CN&i=$data" 291 | // val temp = RequestUtil.request(RequestUtil.Companion.Method.GET, url, null, headers.build()) 292 | // temp!!.jsonObject["translateResult"]!!.jsonArray[0].jsonArray[0].jsonObject["tgt"]!!.jsonPrimitive.content 293 | val url = "http://api.interpreter.caiyunai.com/v1/translator" 294 | val headers = Headers.Builder() 295 | headers.add("content-type", "application/json") 296 | headers.add("x-authorization","token 彩云小译Token") 297 | var msg = "[" 298 | data.toTypedArray().forEach { 299 | msg = msg.plus("\"${it.replace("_"," ").replace("-"," ")}\",") 300 | } 301 | msg = msg.substring(0,msg.length - 1).plus("]") 302 | val body = "{\n" + 303 | " \"source\": ${msg},\n" + 304 | " \"trans_type\": \"en2zh\",\n" + 305 | " \"request_id\": \"demo\",\n" + 306 | " \"detect\": true\n" + 307 | " }" 308 | val temp = RequestUtil.request(RequestUtil.Companion.Method.POST, url, body.toRequestBody(), headers.build()) 309 | logger.debug { temp } 310 | temp?.jsonObject?.get("target")?.jsonArray 311 | } catch (e: Exception) { 312 | e.printStackTrace() 313 | null 314 | } 315 | } 316 | 317 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/Pixiv.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg 2 | 3 | import com.hcyacg.details.PicDetails 4 | import com.hcyacg.details.UserDetails 5 | import com.hcyacg.initial.Command 6 | import com.hcyacg.initial.Config 7 | import com.hcyacg.initial.Github 8 | import com.hcyacg.initial.Setting 9 | import com.hcyacg.lowpoly.LowPoly 10 | import com.hcyacg.rank.Rank 11 | import com.hcyacg.rank.Tag 12 | import com.hcyacg.search.SearchPicCenter 13 | import com.hcyacg.search.Trace 14 | import com.hcyacg.sexy.LoliconCenter 15 | import com.hcyacg.sexy.SexyCenter 16 | import com.hcyacg.sexy.WarehouseCenter 17 | import com.hcyacg.utils.CacheUtil 18 | import com.hcyacg.utils.DataUtil 19 | import com.hcyacg.utils.ImageUtil 20 | import kotlinx.coroutines.Dispatchers 21 | import kotlinx.coroutines.withContext 22 | import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription 23 | import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin 24 | import net.mamoe.mirai.contact.Contact.Companion.sendImage 25 | import net.mamoe.mirai.event.events.BotLeaveEvent 26 | import net.mamoe.mirai.event.events.GroupMessageEvent 27 | import net.mamoe.mirai.event.events.UserMessageEvent 28 | import net.mamoe.mirai.event.globalEventChannel 29 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 30 | import java.io.ByteArrayInputStream 31 | import java.util.concurrent.ConcurrentHashMap 32 | import java.util.regex.Pattern 33 | 34 | object Pixiv : KotlinPlugin( 35 | JvmPluginDescription( 36 | id = "com.hcyacg.pixiv", 37 | name = "pixiv插画", 38 | version = "1.7.6", 39 | ) { 40 | author("Nekoer") 41 | info("""pixiv插画""") 42 | } 43 | ) { 44 | 45 | override fun onDisable() { 46 | Setting.save() 47 | Config.save() 48 | Command.save() 49 | Github.save() 50 | } 51 | 52 | override fun onEnable() { 53 | Setting.reload() 54 | Config.reload() 55 | Command.reload() 56 | Github.reload() 57 | // AutoUpdate.load() 58 | 59 | 60 | val searchImageWaitMap = ConcurrentHashMap() 61 | val searchAnimeWaitMap = ConcurrentHashMap() 62 | val nsfwWaitMap = ConcurrentHashMap() 63 | 64 | globalEventChannel().subscribeAlways { 65 | //测试成功 66 | val getDetailOfId: Pattern = 67 | Pattern.compile("(?i)^(${Command.getDetailOfId})([0-9]*[1-9][0-9]*)|-([0-9]*[1-9][0-9]*)\$") 68 | if (getDetailOfId.matcher(message.contentToString()) 69 | .find() && !Setting.black.contains(group.id.toString()) 70 | ) { 71 | PicDetails.load(this) 72 | } 73 | 74 | //测试成功 75 | val rank: Pattern = 76 | Pattern.compile("(?i)^(${Command.showRank})(daily|weekly|monthly|rookie|original|male|female|daily_r18|weekly_r18|male_r18|female_r18|r18g)-([0-9]*[1-9][0-9]*)\$") 77 | if (rank.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString())) { 78 | Rank.showRank(this) 79 | } 80 | 81 | //测试成功 82 | val findUserWorksById: Pattern = 83 | Pattern.compile("(?i)^(${Command.findUserWorksById})([0-9]*[1-9][0-9]*)|-([0-9]*[1-9][0-9]*)\$") 84 | if ( 85 | findUserWorksById.matcher(message.contentToString()) 86 | .find() && !Setting.black.contains(group.id.toString()) 87 | ) { 88 | UserDetails.findUserWorksById(this) 89 | } 90 | //测试成功 91 | val searchInfoByPic: Pattern = Pattern.compile("(?i)^(${Command.searchInfoByPic})(?:\\n?.+)?\$") 92 | if (searchInfoByPic.matcher(message.contentToString()) 93 | .find() && !Setting.black.contains(group.id.toString()) 94 | && (searchAnimeWaitMap["${this.group.id}-${this.sender.id}"] ?: 0) < System.currentTimeMillis() 95 | ) { 96 | if (DataUtil.getImageLink(this.message) == null) { 97 | searchAnimeWaitMap["${this.group.id}-${this.sender.id}"] = 98 | System.currentTimeMillis() + Config.waitTime * 1000 99 | "请在${Config.waitTime}秒内发送搜番图片" 100 | } else { 101 | Trace.searchInfoByPic(this) 102 | } 103 | } 104 | if ( 105 | (searchAnimeWaitMap["${this.group.id}-${this.sender.id}"] ?: 0) >= System.currentTimeMillis() 106 | ) { 107 | searchAnimeWaitMap["${this.group.id}-${this.sender.id}"] = 0 108 | Trace.searchInfoByPic(this) 109 | } 110 | 111 | //(?i)^[@]\d+[ ][${Command.detect}](\n)?.+|(${Command.detect})?.+$ 112 | //(?i)^(${Command.detect})(\n)?.+$ 113 | //(?i)^(${Command.detect})(?:\n?.+)?$ 114 | val detect: Pattern = Pattern.compile("(?i)^(${Command.detect})(?:\\n?.+)?\$") 115 | if ( 116 | detect.matcher(message.contentToString()).find() 117 | && !Setting.black.contains(group.id.toString()) 118 | && (nsfwWaitMap["${this.group.id}-${this.sender.id}"] ?: 0) < System.currentTimeMillis() 119 | ) { 120 | if (DataUtil.getImageLink(this.message) == null) { 121 | nsfwWaitMap["${this.group.id}-${this.sender.id}"] = 122 | System.currentTimeMillis() + Config.waitTime * 1000 123 | "请在${Config.waitTime}秒内发送检测图片" 124 | } else { 125 | Nsfw.load(this) 126 | } 127 | } 128 | if ( 129 | (nsfwWaitMap["${this.group.id}-${this.sender.id}"] ?: 0) >= System.currentTimeMillis() 130 | ) { 131 | nsfwWaitMap["${this.group.id}-${this.sender.id}"] = 0 132 | Nsfw.load(this) 133 | } 134 | 135 | val setu: Pattern = Pattern.compile("(?i)^(${Command.setu})\$") 136 | if (setu.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString())) { 137 | SexyCenter.init(this) 138 | } 139 | 140 | val setuTag: Pattern = Pattern.compile("(?i)^(${Command.setu})[ ]\\S* ?(r18)?$") 141 | if (setuTag.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString())) { 142 | SexyCenter.yandeTagSearch(this) 143 | } 144 | 145 | //测试成功 146 | val tag: Pattern = Pattern.compile("(?i)^(${Command.tag})([\\s\\S]*)-([0-9]*[1-9][0-9]*)\$") 147 | if (tag.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString())) { 148 | Tag.init(this) 149 | } 150 | //测试成功 151 | val picToSearch: Pattern = Pattern.compile("(?i)^(${Command.picToSearch})(?:\\n?.+)?\$") 152 | if ( 153 | picToSearch.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString()) && 154 | (searchImageWaitMap["${this.group.id}-${this.sender.id}"] ?: 0) < System.currentTimeMillis() 155 | ) { 156 | if (DataUtil.getImageLink(this.message) == null) { 157 | searchImageWaitMap["${this.group.id}-${this.sender.id}"] = 158 | System.currentTimeMillis() + Config.waitTime * 1000 159 | "请在${Config.waitTime}秒内发送搜图图片" 160 | } else { 161 | SearchPicCenter.forward(this) 162 | } 163 | } 164 | if ( 165 | (searchImageWaitMap["${this.group.id}-${this.sender.id}"] ?: 0) >= System.currentTimeMillis() 166 | ) { 167 | searchImageWaitMap["${this.group.id}-${this.sender.id}"] = 0 168 | SearchPicCenter.forward(this) 169 | } 170 | 171 | val lolicon: Pattern = Pattern.compile("(?i)^(${Command.lolicon})( ([^ ]*)( (r18))?)?\$") 172 | if ( 173 | lolicon.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString()) 174 | ) { 175 | LoliconCenter.load(this) 176 | } 177 | 178 | if (Command.help.contentEquals(message.contentToString()) && !Setting.black.contains(group.id.toString())) { 179 | Helper.load( 180 | this 181 | ) 182 | } 183 | 184 | if ("切换涩图开关".contentEquals(message.contentToString()) && !Setting.black.contains(group.id.toString())) { 185 | Helper.setuEnable( 186 | this 187 | ) 188 | } 189 | if ("切换缓存开关".contentEquals(message.contentToString()) && !Setting.black.contains(group.id.toString())) { 190 | Helper.enableLocal( 191 | this 192 | ) 193 | } 194 | if ("切换转发开关".contentEquals(message.contentToString()) && !Setting.black.contains(group.id.toString())) { 195 | Helper.enableForward( 196 | this 197 | ) 198 | } 199 | if ("ban".contentEquals(message.contentToString()) or "unban".contentEquals(message.contentToString())) { 200 | Helper.black( 201 | this 202 | ) 203 | } 204 | if ("切换图片转发开关".contentEquals(message.contentToString()) && !Setting.black.contains(group.id.toString())) { 205 | Helper.enableImageToForward( 206 | this 207 | ) 208 | } 209 | if ("切换晶格化开关".contentEquals(message.contentToString()) && !Setting.black.contains(group.id.toString())) { 210 | Helper.enableLowPoly( 211 | this 212 | ) 213 | } 214 | if ( 215 | message.contentToString().contains("设置撤回") && !Setting.black.contains(group.id.toString()) 216 | ) { 217 | Helper.changeRecall( 218 | this 219 | ) 220 | } 221 | 222 | if ( 223 | message.contentToString().contains("设置loli图片大小") && !Setting.black.contains(group.id.toString()) 224 | ) { 225 | Helper.setLoliconSize( 226 | this 227 | ) 228 | } 229 | 230 | if ( 231 | message.contentToString().contains("suki") && !Setting.black.contains(group.id.toString()) 232 | ) { 233 | WarehouseCenter.init( 234 | this 235 | ) 236 | } 237 | 238 | val vip = Pattern.compile("(?i)^(购买)(月费|季度|半年|年费)会员\$") 239 | if ( 240 | vip.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString()) 241 | ) { 242 | Vip.buy(this) 243 | } 244 | 245 | val enableSetu = Pattern.compile("(?i)^(关闭|开启)(pixiv|yande|lolicon|local|konachan)\$") 246 | if ( 247 | enableSetu.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString()) 248 | ) { 249 | Helper.enableSetu(this) 250 | } 251 | 252 | val enableSearch = Pattern.compile("(?i)^(关闭|开启)(ascii2d|google|saucenao|yandex|iqdb)\$") 253 | if ( 254 | enableSearch.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString()) 255 | ) { 256 | Helper.enableSearch(this) 257 | } 258 | 259 | 260 | val lowPoly = Pattern.compile("(?i)^(${Command.lowPoly}).+\$") 261 | if ( 262 | lowPoly.matcher(message.contentToString()).find() && !Setting.black.contains(group.id.toString()) 263 | ) { 264 | val picUri = DataUtil.getImageLink(this.message) 265 | if (picUri == null) { 266 | "请输入正确的命令 ${Command.lowPoly}图片" 267 | } 268 | val byte = ImageUtil.getImage(picUri!!, CacheUtil.Type.NONSUPPORT).toByteArray() 269 | val toExternalResource = LowPoly.generate( 270 | ByteArrayInputStream(byte), 271 | 200, 272 | 1F, 273 | true, 274 | "png", 275 | false, 276 | 200 277 | ).toByteArray().toExternalResource() 278 | 279 | 280 | withContext(Dispatchers.IO) { 281 | subject.sendImage(toExternalResource) 282 | } 283 | toExternalResource.close() 284 | } 285 | // content { "test".contentEquals(message.contentToString()) } quoteReply {PicDetails.getUgoira()} 286 | 287 | // val coloring: Pattern = Pattern.compile("(?i)^(上色)$") 288 | // content { coloring.matcher(message.contentToString()).find() } quoteReply { Style2paints.coloring(this, pluginLogger) } 289 | // 290 | } 291 | 292 | //获取到退群事件,删除groups中的相同群号 293 | globalEventChannel().subscribeAlways { 294 | Setting.groups.remove(it.groupId.toString()) 295 | Setting.save() 296 | Setting.reload() 297 | } 298 | globalEventChannel().subscribeAlways { 299 | if (message.contentToString().contains("ban") || message.contentToString().contains("unban")) { 300 | Helper.directBlack(this) 301 | } 302 | } 303 | 304 | } 305 | 306 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/Vip.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg 2 | 3 | import com.hcyacg.initial.Config 4 | import com.hcyacg.initial.Setting 5 | import com.hcyacg.utils.ImageUtil 6 | import com.hcyacg.utils.RequestUtil 7 | import kotlinx.serialization.json.jsonArray 8 | import kotlinx.serialization.json.jsonObject 9 | import kotlinx.serialization.json.jsonPrimitive 10 | import net.mamoe.mirai.event.events.GroupMessageEvent 11 | import net.mamoe.mirai.message.data.At 12 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 13 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 14 | import okhttp3.Headers 15 | 16 | object Vip { 17 | private val headers = Headers.Builder().add("authorization", Config.token.acgmx) 18 | 19 | //https://api.acgmx.com/pays/pay?vip=1&json=false&base64=true 20 | 21 | suspend fun buy(event:GroupMessageEvent){ 22 | if (!Setting.admins.contains(event.sender.id.toString())) { 23 | event.subject.sendMessage(At(event.sender).plus("\n").plus("您没有权限设置")) 24 | return 25 | } 26 | val message = event.message.contentToString() 27 | var type = 1 28 | var typeName = "月费" 29 | 30 | 31 | val packages = RequestUtil.request(RequestUtil.Companion.Method.GET, "https://api.acgmx.com/vips/vips", null, headers.build()) 32 | 33 | 34 | 35 | when (message.replace("购买","").replace("会员","")){ 36 | "月费" -> { 37 | type = 1 38 | typeName = "月费" 39 | } 40 | "季度" -> { 41 | type = 2 42 | typeName = "季度" 43 | } 44 | "半年" -> { 45 | type = 3 46 | typeName = "半年" 47 | } 48 | "年费" -> { 49 | type = 4 50 | typeName = "年费" 51 | } 52 | } 53 | val url = "https://api.acgmx.com/pays/pay?vip=$type&json=false&base64=true" 54 | 55 | val request = RequestUtil.request(RequestUtil.Companion.Method.GET, url, null, headers.build()) 56 | if (request != null) { 57 | val base = request.jsonObject["data"]?.jsonPrimitive?.content 58 | val toExternalResource = base?.let { ImageUtil.generateImage(it) }?.toExternalResource() 59 | val image = toExternalResource?.uploadAsImage(event.group) 60 | val price = packages?.jsonObject?.get("data")?.jsonArray?.get(0)?.jsonObject?.get("vipPackages")?.jsonArray?.get(type -1)?.jsonObject?.get("price")?.jsonPrimitive?.content 61 | var discount = packages?.jsonObject?.get("data")?.jsonArray?.get(0)?.jsonObject?.get("vipPackages")?.jsonArray?.get(type -1)?.jsonObject?.get("discount")?.jsonPrimitive?.content 62 | if (null == discount){ 63 | discount = "0" 64 | } 65 | 66 | image?.let { event.subject.sendMessage(it.plus("\n").plus("お買い上げありがとうございます").plus("\n").plus("你正在购买${typeName}会员,请使用支付宝进行支付~").plus("\n").plus("共计${price},折扣${discount}%")) } 67 | } 68 | 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/anno/NoArgOpenDataClass.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.anno 2 | 3 | annotation class NoArgOpenDataClass() 4 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/details/UserDetails.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.details 2 | 3 | import com.hcyacg.initial.Command 4 | import com.hcyacg.initial.Config 5 | import com.hcyacg.initial.Setting 6 | import com.hcyacg.utils.CacheUtil 7 | import com.hcyacg.utils.ImageUtil 8 | import com.hcyacg.utils.RequestUtil 9 | import com.hcyacg.utils.logger 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.withContext 12 | import kotlinx.serialization.json.JsonElement 13 | import kotlinx.serialization.json.jsonArray 14 | import kotlinx.serialization.json.jsonObject 15 | import kotlinx.serialization.json.jsonPrimitive 16 | import net.mamoe.mirai.contact.nameCardOrNick 17 | import net.mamoe.mirai.event.events.GroupMessageEvent 18 | import net.mamoe.mirai.message.data.* 19 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 20 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 21 | import okhttp3.Headers 22 | import okhttp3.RequestBody 23 | import org.apache.commons.lang3.StringUtils 24 | 25 | object UserDetails { 26 | private val headers = Headers.Builder().add("token", Config.token.acgmx).add("referer", "https://www.acgmx.com") 27 | private val requestBody: RequestBody? = null 28 | private val logger by logger() 29 | suspend fun findUserWorksById(event: GroupMessageEvent){ 30 | 31 | val data: JsonElement? 32 | val authorData: JsonElement? 33 | var enable = false 34 | try{ 35 | if (!event.message.contentToString().contains(Command.findUserWorksById)){ 36 | return 37 | } 38 | event.subject.sendMessage(At(event.sender).plus("正在获取中,请稍后")) 39 | if (Setting.groups.contains(event.group.id.toString())) { 40 | enable = true 41 | } 42 | 43 | val authorId = event.message.contentToString().replace(Command.findUserWorksById,"").replace(" ","").split("-")[0] 44 | 45 | 46 | val page = if(event.message.contentToString().replace(Command.findUserWorksById,"").replace(" ","").split("-").size == 2){ 47 | event.message.contentToString().replace(Command.findUserWorksById,"").replace(" ","").split("-")[1].toInt() 48 | }else{ 49 | 1 50 | } 51 | val offset: Int 52 | val num: Int 53 | if (page % 3 != 0){ 54 | offset = ((page - (page % 3)) / 3) * 30 + 30 55 | num = page % 3 * 10 56 | }else{ 57 | offset = (page / 3) * 30 58 | num = 30 59 | } 60 | 61 | if (StringUtils.isBlank(authorId)){ 62 | event.subject.sendMessage("请输入正确的命令 ${Command.findUserWorksById}作者Id-页码") 63 | return 64 | } 65 | 66 | /** 67 | * 获取作者信息 68 | */ 69 | authorData = RequestUtil.request( 70 | RequestUtil.Companion.Method.GET, 71 | "https://api.acgmx.com/public/search/users/details?id=$authorId", 72 | requestBody, 73 | headers.build() 74 | ) 75 | 76 | 77 | //作者插画作品数量 78 | val totalIllusts = authorData?.jsonObject?.get("profile")?.jsonObject?.get("total_illusts")?.jsonPrimitive?.content!!.toInt() 79 | 80 | val author = authorData.jsonObject["user"]?.jsonObject?.get("name")?.jsonPrimitive?.content 81 | 82 | /** 83 | * 获取作者作品信息 84 | */ 85 | data = RequestUtil.request( 86 | RequestUtil.Companion.Method.GET, 87 | "https://api.acgmx.com/public/search/users/illusts?id=$authorId&offset=$offset", 88 | requestBody, 89 | headers.build() 90 | ) 91 | 92 | val tempData = data?.jsonObject?.get("illusts")?.jsonArray 93 | var message : Message = At(event.sender).plus("\n").plus("======${author}作品======").plus("\n") 94 | 95 | 96 | if (null == tempData){ 97 | return 98 | } 99 | 100 | if (tempData.size == 0){ 101 | event.subject.sendMessage(message.plus("当前页为空")) 102 | return 103 | } 104 | 105 | val nodes = mutableListOf() 106 | for (i in (num-10) until num){ 107 | if (tempData.size - 1 < num){ 108 | event.subject.sendMessage(message.plus("当前页为空")) 109 | return 110 | } 111 | 112 | val id = tempData[i].jsonObject["id"]?.jsonPrimitive?.content 113 | val title = tempData[i].jsonObject["title"]?.jsonPrimitive?.content 114 | 115 | // val large = 116 | // tempData[i].jsonObject["image_urls"]?.jsonObject?.get("large")?.jsonPrimitive?.content 117 | val large = if (null != tempData[i].jsonObject["meta_single_page"]?.jsonObject?.get("original_image_url")?.jsonPrimitive?.content){ 118 | tempData[i].jsonObject["meta_single_page"]?.jsonObject?.get("original_image_url")?.jsonPrimitive?.content 119 | }else{ 120 | tempData[i].jsonObject["meta_pages"]?.jsonArray?.get(0)?.jsonObject?.get("image_urls")?.jsonObject?.get("original")?.jsonPrimitive?.content 121 | } 122 | val type = tempData[i].jsonObject["type"]?.jsonPrimitive?.content 123 | 124 | val sanityLevel = tempData[i].jsonObject["sanity_level"]?.jsonPrimitive?.content?.toInt() 125 | message = message.plus("${(page * 10) - 9 + (i % 10)}. $title - $id").plus("\n") 126 | 127 | if (Config.forward.rankAndTagAndUserByForward) { 128 | var tempMessage = PlainText("${(page * 10) - 9 + (i % 10)}. $title - $id").plus("\n") 129 | // val detail = PicDetails.getDetailOfId(id!!) 130 | 131 | if ("ugoira".contentEquals(type)) { 132 | val toExternalResource = PicDetails.getUgoira(id!!.toLong())?.toExternalResource() 133 | val imageId: String? = toExternalResource?.uploadAsImage(event.group)?.imageId 134 | withContext(Dispatchers.IO) { 135 | toExternalResource?.close() 136 | } 137 | if (null != imageId) { 138 | /** 139 | * 判断是否配置了撤回时间 140 | */ 141 | 142 | tempMessage = if (sanityLevel != 6 || enable) { 143 | tempMessage.plus(Image(imageId)) 144 | } else { 145 | tempMessage.plus("无权限查看涩图") 146 | } 147 | } 148 | 149 | } else { 150 | val toExternalResource = 151 | ImageUtil.getImage( 152 | large!!.replace("i.pximg.net", "i.acgmx.com"), 153 | CacheUtil.Type.PIXIV 154 | ).toByteArray().toExternalResource() 155 | val imageId: String = toExternalResource.uploadAsImage(event.group).imageId 156 | withContext(Dispatchers.IO) { 157 | toExternalResource.close() 158 | } 159 | tempMessage = if (sanityLevel != 6 || enable) { 160 | tempMessage.plus(Image(imageId)) 161 | } else { 162 | tempMessage.plus("无权限查看涩图") 163 | } 164 | } 165 | 166 | nodes.add( 167 | ForwardMessage.Node( 168 | senderId = event.bot.id, 169 | senderName = event.bot.nameCardOrNick, 170 | time = System.currentTimeMillis().toInt(), 171 | message = tempMessage 172 | ) 173 | ) 174 | } 175 | } 176 | // loop@ for ((index,o) in tempData.withIndex()){ 177 | // if(index > 9){ 178 | // break@loop 179 | // } 180 | // val id = JSONObject.parseObject(o.toString()).getString("id") 181 | // val title = JSONObject.parseObject(o.toString()).getString("title") 182 | // message = message.plus("${index +1 }. $title - $id").plus("\n") 183 | // } 184 | 185 | if (Config.forward.rankAndTagAndUserByForward) { 186 | val forward = RawForwardMessage(nodes).render(object : ForwardMessage.DisplayStrategy { 187 | override fun generateTitle(forward: RawForwardMessage): String { 188 | return "Tag排行榜" 189 | } 190 | 191 | override fun generateSummary(forward: RawForwardMessage): String { 192 | return "查看${nodes.size}条图片" 193 | } 194 | }) 195 | event.subject.sendMessage(forward) 196 | } else { 197 | event.subject.sendMessage(message.plus("作品共 ${if(totalIllusts % 10 != 0){ (totalIllusts/10) +1 }else{totalIllusts/10}} 页,目前处于${page}页")) 198 | } 199 | }catch (e:Exception){ 200 | logger.error { e.message } 201 | e.printStackTrace() 202 | event.subject.sendMessage("请输入正确的命令 ${Command.findUserWorksById}作者Id-页码") 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/entity/Anilist.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.entity 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | import kotlinx.serialization.SerialName 6 | 7 | 8 | @Serializable 9 | data class Anilist( 10 | @SerialName("data") 11 | val `data`: AData? = AData() 12 | ) 13 | 14 | @Serializable 15 | data class AData( 16 | @SerialName("Media") 17 | val media: Media? = Media() 18 | ) 19 | 20 | @Serializable 21 | data class Media( 22 | @SerialName("coverImage") 23 | val coverImage: CoverImage? = CoverImage(), 24 | @SerialName("id") 25 | val id: Int? = 0, 26 | @SerialName("title") 27 | val title: Title? = Title() 28 | ) 29 | 30 | @Serializable 31 | data class CoverImage( 32 | @SerialName("extraLarge") 33 | val extraLarge: String? = "" 34 | ) 35 | 36 | @Serializable 37 | data class Title( 38 | @SerialName("native") 39 | val native: String? = "" 40 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/entity/GithubRelease.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.entity 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | import kotlinx.serialization.SerialName 6 | 7 | @Serializable 8 | class GithubRelease : ArrayList() { 9 | companion object { 10 | private const val serialVersionUID: Long = 8952155984629561105L 11 | } 12 | } 13 | 14 | @Serializable 15 | data class GithubReleaseItem( 16 | @SerialName("assets") 17 | val assets: List? = listOf(), 18 | @SerialName("assets_url") 19 | val assetsUrl: String? = "", 20 | @SerialName("author") 21 | val author: Author? = Author(), 22 | @SerialName("body") 23 | val body: String? = "", 24 | @SerialName("created_at") 25 | val createdAt: String? = "", 26 | @SerialName("draft") 27 | val draft: Boolean? = false, 28 | @SerialName("html_url") 29 | val htmlUrl: String? = "", 30 | @SerialName("id") 31 | val id: Int? = 0, 32 | @SerialName("name") 33 | val name: String? = "", 34 | @SerialName("node_id") 35 | val nodeId: String? = "", 36 | @SerialName("prerelease") 37 | val prerelease: Boolean? = false, 38 | @SerialName("published_at") 39 | val publishedAt: String? = "", 40 | @SerialName("tag_name") 41 | val tagName: String? = "", 42 | @SerialName("tarball_url") 43 | val tarballUrl: String? = "", 44 | @SerialName("target_commitish") 45 | val targetCommitish: String? = "", 46 | @SerialName("upload_url") 47 | val uploadUrl: String? = "", 48 | @SerialName("url") 49 | val url: String? = "", 50 | @SerialName("zipball_url") 51 | val zipballUrl: String? = "" 52 | ) 53 | 54 | @Serializable 55 | data class Asset( 56 | @SerialName("browser_download_url") 57 | val browserDownloadUrl: String? = "", 58 | @SerialName("content_type") 59 | val contentType: String? = "", 60 | @SerialName("created_at") 61 | val createdAt: String? = "", 62 | @SerialName("download_count") 63 | val downloadCount: Int? = 0, 64 | @SerialName("id") 65 | val id: Int? = 0, 66 | // @SerialName("label") 67 | // val label: Any? = Any(), 68 | @SerialName("name") 69 | val name: String? = "", 70 | @SerialName("node_id") 71 | val nodeId: String? = "", 72 | @SerialName("size") 73 | val size: Int? = 0, 74 | @SerialName("state") 75 | val state: String? = "", 76 | @SerialName("updated_at") 77 | val updatedAt: String? = "", 78 | @SerialName("uploader") 79 | val uploader: Uploader? = Uploader(), 80 | @SerialName("url") 81 | val url: String? = "" 82 | ) 83 | 84 | @Serializable 85 | data class Author( 86 | @SerialName("avatar_url") 87 | val avatarUrl: String? = "", 88 | @SerialName("events_url") 89 | val eventsUrl: String? = "", 90 | @SerialName("followers_url") 91 | val followersUrl: String? = "", 92 | @SerialName("following_url") 93 | val followingUrl: String? = "", 94 | @SerialName("gists_url") 95 | val gistsUrl: String? = "", 96 | @SerialName("gravatar_id") 97 | val gravatarId: String? = "", 98 | @SerialName("html_url") 99 | val htmlUrl: String? = "", 100 | @SerialName("id") 101 | val id: Int? = 0, 102 | @SerialName("login") 103 | val login: String? = "", 104 | @SerialName("node_id") 105 | val nodeId: String? = "", 106 | @SerialName("organizations_url") 107 | val organizationsUrl: String? = "", 108 | @SerialName("received_events_url") 109 | val receivedEventsUrl: String? = "", 110 | @SerialName("repos_url") 111 | val reposUrl: String? = "", 112 | @SerialName("site_admin") 113 | val siteAdmin: Boolean? = false, 114 | @SerialName("starred_url") 115 | val starredUrl: String? = "", 116 | @SerialName("subscriptions_url") 117 | val subscriptionsUrl: String? = "", 118 | @SerialName("type") 119 | val type: String? = "", 120 | @SerialName("url") 121 | val url: String? = "" 122 | ) 123 | 124 | @Serializable 125 | data class Uploader( 126 | @SerialName("avatar_url") 127 | val avatarUrl: String? = "", 128 | @SerialName("events_url") 129 | val eventsUrl: String? = "", 130 | @SerialName("followers_url") 131 | val followersUrl: String? = "", 132 | @SerialName("following_url") 133 | val followingUrl: String? = "", 134 | @SerialName("gists_url") 135 | val gistsUrl: String? = "", 136 | @SerialName("gravatar_id") 137 | val gravatarId: String? = "", 138 | @SerialName("html_url") 139 | val htmlUrl: String? = "", 140 | @SerialName("id") 141 | val id: Int? = 0, 142 | @SerialName("login") 143 | val login: String? = "", 144 | @SerialName("node_id") 145 | val nodeId: String? = "", 146 | @SerialName("organizations_url") 147 | val organizationsUrl: String? = "", 148 | @SerialName("received_events_url") 149 | val receivedEventsUrl: String? = "", 150 | @SerialName("repos_url") 151 | val reposUrl: String? = "", 152 | @SerialName("site_admin") 153 | val siteAdmin: Boolean? = false, 154 | @SerialName("starred_url") 155 | val starredUrl: String? = "", 156 | @SerialName("subscriptions_url") 157 | val subscriptionsUrl: String? = "", 158 | @SerialName("type") 159 | val type: String? = "", 160 | @SerialName("url") 161 | val url: String? = "" 162 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/entity/Lolicon.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.entity 2 | import kotlinx.serialization.Serializable 3 | 4 | import kotlinx.serialization.SerialName 5 | 6 | 7 | @Serializable 8 | data class Lolicon( 9 | @SerialName("data") 10 | val `data`: List? = listOf(), 11 | @SerialName("error") 12 | val error: String? = "" 13 | ) 14 | 15 | @Serializable 16 | data class LData( 17 | @SerialName("author") 18 | val author: String? = "", 19 | @SerialName("ext") 20 | val ext: String? = "", 21 | @SerialName("height") 22 | val height: Int? = 0, 23 | @SerialName("p") 24 | val p: Int? = 0, 25 | @SerialName("pid") 26 | val pid: Int? = 0, 27 | @SerialName("r18") 28 | val r18: Boolean? = false, 29 | @SerialName("tags") 30 | val tags: List? = listOf(), 31 | @SerialName("title") 32 | val title: String? = "", 33 | @SerialName("uid") 34 | val uid: Int? = 0, 35 | @SerialName("uploadDate") 36 | val uploadDate: Long? = 0, 37 | @SerialName("urls") 38 | val urls: Urls? = Urls(), 39 | @SerialName("width") 40 | val width: Int? = 0, 41 | @SerialName("aiType") 42 | val aiType: Int? = 0 43 | ) 44 | 45 | @Serializable 46 | data class Urls( 47 | @SerialName("original") 48 | val original: String? = "" 49 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/entity/PixivImageDetail.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.entity 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | import kotlinx.serialization.SerialName 6 | 7 | @Serializable 8 | data class PixivImageDetail( 9 | @SerialName("caption") 10 | val caption: String? = "", 11 | @SerialName("create_date") 12 | val createDate: String? = "", 13 | @SerialName("height") 14 | val height: Int? = 0, 15 | @SerialName("id") 16 | val id: Int? = 0, 17 | @SerialName("image_urls") 18 | val imageUrls: ImageUrls? = ImageUrls(), 19 | @SerialName("is_bookmarked") 20 | val isBookmarked: Boolean? = false, 21 | @SerialName("is_muted") 22 | val isMuted: Boolean? = false, 23 | @SerialName("meta_pages") 24 | val metaPages: List? = listOf(), 25 | @SerialName("meta_single_page") 26 | val metaSinglePage: MetaSinglePage? = MetaSinglePage(), 27 | @SerialName("page_count") 28 | val pageCount: Int? = 0, 29 | @SerialName("restrict") 30 | val restrict: Int? = 0, 31 | @SerialName("sanity_level") 32 | val sanityLevel: Int? = 0, 33 | // @SerialName("series") 34 | // val series: Any? = Any(), 35 | @SerialName("tags") 36 | val tags: List? = listOf(), 37 | @SerialName("title") 38 | val title: String? = "", 39 | @SerialName("tools") 40 | val tools: List? = listOf(), 41 | @SerialName("total_bookmarks") 42 | val totalBookmarks: Int? = 0, 43 | @SerialName("total_view") 44 | val totalView: Int? = 0, 45 | @SerialName("type") 46 | val type: String? = "", 47 | @SerialName("user") 48 | val user: User? = User(), 49 | @SerialName("visible") 50 | val visible: Boolean? = false, 51 | @SerialName("width") 52 | val width: Int? = 0, 53 | @SerialName("x_restrict") 54 | val xRestrict: Int? = 0 55 | ) 56 | 57 | @Serializable 58 | data class ImageUrls( 59 | @SerialName("large") 60 | val large: String? = "", 61 | @SerialName("medium") 62 | val medium: String? = "", 63 | @SerialName("square_medium") 64 | val squareMedium: String? = "" 65 | ) 66 | 67 | @Serializable 68 | data class MetaPage( 69 | @SerialName("image_urls") 70 | val imageUrls: ImageUrlsX? = ImageUrlsX() 71 | ) 72 | 73 | @Serializable 74 | data class MetaSinglePage( 75 | @SerialName("original_image_url") 76 | val originalImageUrl: String? = "" 77 | ) 78 | 79 | @Serializable 80 | data class Tag( 81 | @SerialName("name") 82 | val name: String? = "", 83 | @SerialName("translated_name") 84 | val translatedName: String? = "" 85 | ) 86 | 87 | @Serializable 88 | data class User( 89 | @SerialName("account") 90 | val account: String? = "", 91 | @SerialName("id") 92 | val id: Int? = 0, 93 | @SerialName("is_followed") 94 | val isFollowed: Boolean? = false, 95 | @SerialName("name") 96 | val name: String? = "", 97 | @SerialName("profile_image_urls") 98 | val profileImageUrls: ProfileImageUrls? = ProfileImageUrls() 99 | ) 100 | 101 | @Serializable 102 | data class ImageUrlsX( 103 | @SerialName("large") 104 | val large: String? = "", 105 | @SerialName("medium") 106 | val medium: String? = "", 107 | @SerialName("original") 108 | val original: String? = "", 109 | @SerialName("square_medium") 110 | val squareMedium: String? = "" 111 | ) 112 | 113 | @Serializable 114 | data class ProfileImageUrls( 115 | @SerialName("medium") 116 | val medium: String? = "" 117 | ) 118 | // 119 | //object MetaPagesSerializer : JsonTransformingSerializer>(List.serializer()) { 120 | // // If response is an array, then return a empty Icon 121 | // override fun transformDeserialize(element: JsonElement): JsonElement { 122 | // return if (element is JsonArray || element is List<*>) { 123 | // var name = "" 124 | // for (index in 0 until element.jsonArray.size) { 125 | // name = if (index == element.jsonArray.size - 1) { 126 | // name.plus(element.jsonArray[index].jsonPrimitive.content) 127 | // } else { 128 | // name.plus(element.jsonArray[index].jsonPrimitive.content).plus("和") 129 | // } 130 | // } 131 | // Json.encodeToJsonElement(name) 132 | // } else { 133 | // element 134 | // } 135 | // } 136 | //} 137 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/entity/Trace.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.entity 2 | import kotlinx.serialization.Serializable 3 | 4 | import kotlinx.serialization.SerialName 5 | 6 | 7 | @Serializable 8 | data class Trace( 9 | @SerialName("error") 10 | val error: String? = "", 11 | @SerialName("frameCount") 12 | val frameCount: Int? = 0, 13 | @SerialName("result") 14 | val result: List? = listOf() 15 | ) 16 | 17 | @Serializable 18 | data class Result( 19 | @SerialName("anilist") 20 | val anilist: Int? = 0, 21 | @SerialName("episode") 22 | val episode: Int? = 0, 23 | @SerialName("filename") 24 | val filename: String? = "", 25 | @SerialName("from") 26 | val from: Double? = 0.0, 27 | @SerialName("image") 28 | val image: String? = "", 29 | @SerialName("similarity") 30 | val similarity: Double? = 0.0, 31 | @SerialName("to") 32 | val to: Double? = 0.0, 33 | @SerialName("video") 34 | val video: String? = "" 35 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/entity/YandexImage.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.entity 2 | import kotlinx.serialization.Serializable 3 | 4 | import kotlinx.serialization.SerialName 5 | 6 | 7 | @Serializable 8 | data class YandexImage( 9 | @SerialName("height") 10 | val height: Int? = 0, 11 | @SerialName("image_id") 12 | val imageId: String? = "", 13 | @SerialName("image_shard") 14 | val imageShard: Int? = 0, 15 | @SerialName("namespace") 16 | val namespace: String? = "", 17 | @SerialName("url") 18 | val url: String? = "", 19 | @SerialName("width") 20 | val width: Int? = 0 21 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/entity/YandexSearchResult.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.entity 2 | import kotlinx.serialization.Serializable 3 | 4 | import kotlinx.serialization.SerialName 5 | 6 | 7 | @Serializable 8 | data class YandexSearchResult( 9 | @SerialName("counterPaths") 10 | val counterPaths: CounterPaths? = CounterPaths(), 11 | @SerialName("faviconSpriteSeed") 12 | val faviconSpriteSeed: String? = "", 13 | @SerialName("lazyThumbsFromIndex") 14 | val lazyThumbsFromIndex: Int? = 0, 15 | @SerialName("loadedPagesCount") 16 | val loadedPagesCount: Int? = 0, 17 | @SerialName("pageSize") 18 | val pageSize: Int? = 0, 19 | @SerialName("sites") 20 | val sites: List? = listOf(), 21 | @SerialName("title") 22 | val title: String? = "", 23 | @SerialName("withFavicon") 24 | val withFavicon: Boolean? = false 25 | ) 26 | 27 | @Serializable 28 | data class CounterPaths( 29 | @SerialName("item") 30 | val item: String? = "", 31 | @SerialName("itemDomainClick") 32 | val itemDomainClick: String? = "", 33 | @SerialName("itemThumbClick") 34 | val itemThumbClick: String? = "", 35 | @SerialName("itemTitleClick") 36 | val itemTitleClick: String? = "", 37 | @SerialName("loadPage") 38 | val loadPage: String? = "" 39 | ) 40 | 41 | @Serializable 42 | data class Site( 43 | @SerialName("description") 44 | val description: String? = "", 45 | @SerialName("domain") 46 | val domain: String? = "", 47 | @SerialName("originalImage") 48 | val originalImage: OriginalImage? = OriginalImage(), 49 | @SerialName("thumb") 50 | val thumb: Thumb? = Thumb(), 51 | @SerialName("title") 52 | val title: String? = "", 53 | @SerialName("url") 54 | val url: String? = "" 55 | ) 56 | 57 | @Serializable 58 | data class OriginalImage( 59 | @SerialName("height") 60 | val height: Int? = 0, 61 | @SerialName("url") 62 | val url: String? = "", 63 | @SerialName("width") 64 | val width: Int? = 0 65 | ) 66 | 67 | @Serializable 68 | data class Thumb( 69 | @SerialName("height") 70 | val height: Int? = 0, 71 | @SerialName("url") 72 | val url: String? = "", 73 | @SerialName("width") 74 | val width: Int? = 0 75 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/Command.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial 2 | import net.mamoe.mirai.console.data.AutoSavePluginConfig 3 | import net.mamoe.mirai.console.data.ValueDescription 4 | import net.mamoe.mirai.console.data.ValueName 5 | import net.mamoe.mirai.console.data.value 6 | 7 | /** 8 | * 自定义触发命令 9 | */ 10 | object Command: AutoSavePluginConfig("Command") { 11 | @ValueName("getDetailOfId") 12 | @ValueDescription("根据id获得图片及其数据 psid-87984524") 13 | var getDetailOfId: String by value("psid-") 14 | 15 | @ValueName("picToSearch") 16 | @ValueDescription("搜索图片 ptst-图片") 17 | var picToSearch: String by value("ptst-") 18 | 19 | 20 | @ValueName("showRank") 21 | @ValueDescription("排行榜 rank-daily-页码 可选daily|weekly|monthly|rookie|original|male|female|daily_r18|weekly_r18|male_r18|female_r18|r18g") 22 | var showRank: String by value("rank-") 23 | 24 | @ValueName("findUserWorksById") 25 | @ValueDescription("获取作者所有的插画 user-87915-页码") 26 | var findUserWorksById: String by value("user-") 27 | 28 | @ValueName("searchInfoByPic") 29 | @ValueDescription("搜索番剧 ptsf-图片") 30 | var searchInfoByPic: String by value("ptsf-") 31 | 32 | @ValueName("setu") 33 | @ValueDescription("涩图 setu 或者setu loli") 34 | var setu: String by value("来点色图") 35 | 36 | @ValueName("lolicon") 37 | @ValueDescription("Lolicon 详细看https://api.lolicon.app/#/setu?id=tag") 38 | var lolicon: String by value("loli") 39 | 40 | @ValueName("tag") 41 | @ValueDescription("搜索标签排行榜 tag-萝莉-页码") 42 | var tag: String by value("tag-") 43 | 44 | @ValueName("detect") 45 | @ValueDescription("检测图片的涩情程度并打上标签") 46 | var detect: String by value("检测") 47 | 48 | @ValueName("help") 49 | @ValueDescription("帮助") 50 | var help: String by value("帮助") 51 | 52 | @ValueName("lowPoly") 53 | @ValueDescription("晶格化命令") 54 | var lowPoly: String by value("晶格化") 55 | 56 | @ValueName("warehouse") 57 | @ValueDescription("从指定库存发送图片") 58 | var warehouse:String by value("来点库存") 59 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/Config.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial 2 | import com.hcyacg.anno.NoArgOpenDataClass 3 | import com.hcyacg.initial.entity.Cache 4 | import com.hcyacg.initial.entity.Enable 5 | import com.hcyacg.initial.entity.ForWard 6 | import com.hcyacg.initial.entity.GoogleConfig 7 | import kotlinx.serialization.* 8 | import net.mamoe.mirai.console.data.AutoSavePluginConfig 9 | import net.mamoe.mirai.console.data.ValueDescription 10 | import net.mamoe.mirai.console.data.ValueName 11 | import net.mamoe.mirai.console.data.value 12 | import java.io.File 13 | 14 | 15 | object Config : AutoSavePluginConfig("Config") { 16 | @ValueName("enable") 17 | @ValueDescription("开关 search:搜索图片的引擎开关 sexy:涩图库来源开关") 18 | var enable: Enable by value() 19 | 20 | @ValueName("waitTime") 21 | @ValueDescription("等待时间 搜图搜番时指令与图片分开发送模式下的等待时间(秒)") 22 | var waitTime: Int by value(60) 23 | 24 | @ValueName("forward") 25 | @ValueDescription("转发模式 rankAndTagAndUserByForward:排行榜、标签、作者三个变成转发模式; imageToForward:查看图片详情变成转发模式 可发送该作品的所有图片") 26 | val forward: ForWard by value() 27 | 28 | @ValueName("token") 29 | @ValueDescription("Token必填项 https://www.acgmx.com/ https://saucenao.com/user.php") 30 | var token: Token by value() 31 | 32 | @ValueName("proxy") 33 | @ValueDescription("代理 默认clash 127.0.0.1:7890 不知道的话window 搜索代理设置") 34 | var proxy: Proxy by value() 35 | 36 | @ValueName("recall") 37 | @ValueDescription("自动撤回 涩图专用 单位ms") 38 | var recall: Long by value(5000L) 39 | 40 | @ValueName("tlsVersion") 41 | var tlsVersion: String by value("TLSv1.2") 42 | 43 | @ValueName("cache") 44 | @ValueDescription("缓存 图片缓存将按照来源分开存放") 45 | var cache: Cache by value() 46 | 47 | @ValueName("localImagePath") 48 | @ValueDescription("本地图库目录") 49 | val localImagePath: String by value(System.getProperty("user.dir") + File.separator + "image") 50 | 51 | @ValueName("saucenaoEco") 52 | @ValueDescription("Saucenao的省流经济模式 可以节约Token次数 也可避免部分Onebot实现产生浪费 可能降低准确度") 53 | var saucenaoEco: Boolean by value(false) 54 | 55 | @ValueName("google") 56 | @ValueDescription("Google搜索配置项 镜像源和搜索显示的数量") 57 | val googleConfig: GoogleConfig by value() 58 | 59 | @ValueName("lowPoly") 60 | @ValueDescription("涩图晶格化开关") 61 | var lowPoly: Boolean by value(false) 62 | 63 | @ValueName("loliconSize") 64 | @ValueDescription("lolicon图大小:original,regular,small,thumb,mini") 65 | var loliconSize:String by value("original") 66 | 67 | @ValueName("deepdanbooru") 68 | @ValueDescription("https://github.com/nanoskript/deepdanbooru-docker") 69 | var deepdanbooru: String by value("192.168.10.108:7777") 70 | } 71 | 72 | @NoArgOpenDataClass 73 | @Serializable 74 | data class Proxy( 75 | @SerialName("host") 76 | var host: String = "", 77 | @SerialName("port") 78 | var port: Int = -1 79 | ) 80 | 81 | @NoArgOpenDataClass 82 | @Serializable 83 | data class Token( 84 | @SerialName("acgmx") 85 | var acgmx: String = "", 86 | @SerialName("saucenao") 87 | var saucenao: String = "" 88 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/Github.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial 2 | 3 | import net.mamoe.mirai.console.data.AutoSavePluginData 4 | import net.mamoe.mirai.console.data.ValueName 5 | import net.mamoe.mirai.console.data.value 6 | 7 | object Github :AutoSavePluginData("Github"){ 8 | @ValueName("version-id") 9 | var versionId :Int by value(0) 10 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/Setting.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial 2 | 3 | 4 | import net.mamoe.mirai.console.data.AutoSavePluginConfig 5 | import net.mamoe.mirai.console.data.ValueDescription 6 | import net.mamoe.mirai.console.data.ValueName 7 | import net.mamoe.mirai.console.data.value 8 | 9 | object Setting : AutoSavePluginConfig("Setting") { 10 | 11 | @ValueName("admins") 12 | @ValueDescription("插件管理员") 13 | var admins: MutableList by value() 14 | 15 | @ValueName("groups") 16 | @ValueDescription("可以使用涩图的QQ群") 17 | var groups: MutableList by value() 18 | 19 | @ValueName("black") 20 | @ValueDescription("黑名单群") 21 | var black: MutableList by value() 22 | 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/entity/Cache.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial.entity 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | import java.io.File 6 | 7 | @Serializable 8 | data class Cache( 9 | @SerialName("enable") 10 | var enable: Boolean = true, 11 | @SerialName("directory") 12 | val directory: String = System.getProperty("user.dir") + File.separator + "image" 13 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/entity/Enable.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial.entity 2 | 3 | import com.hcyacg.anno.NoArgOpenDataClass 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @Serializable 8 | data class Enable( 9 | @SerialName("search") 10 | var search :Search = Search(), 11 | @SerialName("sexy") 12 | var sexy :Sexy = Sexy() 13 | ) 14 | 15 | @NoArgOpenDataClass 16 | @Serializable 17 | data class Search( 18 | @SerialName("google") 19 | var google: Boolean = true, 20 | @SerialName("ascii2d") 21 | var ascii2d: Boolean = true, 22 | @SerialName("iqdb") 23 | var iqdb: Boolean = true, 24 | @SerialName("saucenao") 25 | var saucenao: Boolean = true, 26 | @SerialName("yandex") 27 | var yandex: Boolean = true 28 | ) 29 | 30 | @NoArgOpenDataClass 31 | @Serializable 32 | data class Sexy( 33 | @SerialName("pixiv") 34 | var pixiv :Boolean = true, 35 | @SerialName("yande") 36 | var yande :Boolean = true, 37 | @SerialName("konachan") 38 | var konachan :Boolean = true, 39 | @SerialName("lolicon") 40 | var lolicon :Boolean = true, 41 | @SerialName("localImage") 42 | var localImage :Boolean = true 43 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/entity/ForWard.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial.entity 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ForWard( 8 | @SerialName("rankAndTagAndUserByForward") 9 | var rankAndTagAndUserByForward:Boolean = false, 10 | @SerialName("imageToForward") 11 | var imageToForward:Boolean = false 12 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/initial/entity/GoogleConfig.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.initial.entity 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class GoogleConfig( 8 | @SerialName("googleImageUrl") 9 | val googleImageUrl:String = "https://images.google.com", 10 | @SerialName("resultNum") 11 | val resultNum:Int = 2 12 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/rank/Rank.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.rank 2 | 3 | import com.hcyacg.details.PicDetails 4 | import com.hcyacg.initial.Command 5 | import com.hcyacg.initial.Config 6 | import com.hcyacg.initial.Setting 7 | import com.hcyacg.lowpoly.LowPoly 8 | import com.hcyacg.utils.CacheUtil 9 | import com.hcyacg.utils.ImageUtil 10 | import com.hcyacg.utils.logger 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.withContext 13 | import kotlinx.serialization.json.JsonElement 14 | import kotlinx.serialization.json.jsonArray 15 | import kotlinx.serialization.json.jsonObject 16 | import kotlinx.serialization.json.jsonPrimitive 17 | import net.mamoe.mirai.contact.nameCardOrNick 18 | import net.mamoe.mirai.event.events.GroupMessageEvent 19 | import net.mamoe.mirai.message.data.* 20 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 21 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 22 | import java.io.ByteArrayInputStream 23 | import java.text.SimpleDateFormat 24 | import java.util.* 25 | 26 | 27 | object Rank { 28 | private val sdf = SimpleDateFormat("yyyy-MM-dd") 29 | private val logger by logger() 30 | suspend fun showRank(event: GroupMessageEvent) { 31 | val data: JsonElement? 32 | val perPage = 10 33 | //获取日本排行榜时间,当前天数-2 34 | val calendar = Calendar.getInstance() 35 | calendar.add(Calendar.DAY_OF_MONTH, -2) 36 | val date: String = sdf.format(calendar.time) 37 | 38 | var page: Int 39 | var mode: String? = null 40 | var enable = false 41 | 42 | try { 43 | if (Setting.groups.contains(event.group.id.toString())) { 44 | enable = true 45 | } 46 | /** 47 | * 对接收到到的命令进行分析获取 48 | */ 49 | try { 50 | page = 51 | event.message.content.replace(Command.showRank, "").replace(" ", "").split("-")[1].toInt() 52 | } catch (e: Exception) { 53 | mode = event.message.content.replace(Command.showRank, "").replace(" ", "") 54 | page = 1 55 | } 56 | 57 | if (page < 1) { 58 | page = 1 59 | } 60 | 61 | 62 | val num = if (page % 3 != 0) { 63 | page % 3 * 10 64 | } else { 65 | 30 66 | } 67 | 68 | if (null == mode) { 69 | try { 70 | mode = event.message.content.replace(Command.showRank, "").replace(" ", "").split("-")[0] 71 | } catch (e: java.lang.Exception) { 72 | event.subject.sendMessage("请输入正确的排行榜命令 ${Command.showRank}[day|week|month|setu]-页码") 73 | return 74 | } 75 | } 76 | /** 77 | * 判断是否为已有参数 78 | * daily 每日 79 | * weekly 每周 80 | * monthly 每月 81 | * rookie 新画师 82 | * original 原创 83 | * male 男性向 84 | * female 女性向 85 | * daily_r18 每日工口 86 | * weekly_r18 每周工口 87 | * male_r18 男性工口 88 | * female_r18 女性腐向 89 | * r18g 工口加强型(猎奇) 90 | */ 91 | val modeList = mutableListOf( 92 | "daily", 93 | "weekly", 94 | "monthly", 95 | "rookie", 96 | "original", 97 | "male", 98 | "female", 99 | "daily_r18", 100 | "weekly_r18", 101 | "male_r18", 102 | "female_r18", 103 | "r18g" 104 | ) 105 | 106 | if (!modeList.contains(mode)) { 107 | event.subject.sendMessage("请输入正确的排行榜命令 ${Command.showRank}[$modeList]-页码") 108 | return 109 | } 110 | 111 | /** 112 | * 进行数据分发请求 113 | */ 114 | 115 | val r18List = mutableListOf("daily_r18", "weekly_r18", "male_r18", "female_r18", "r18g") 116 | if (r18List.contains(mode)) { 117 | /** 118 | * 判断该群是否有权查看涩图 119 | */ 120 | if (Setting.groups.indexOf(event.group.id.toString()) < 0) { 121 | event.subject.sendMessage("该群暂时无权限查看涩图排行榜") 122 | return 123 | } 124 | } 125 | data = TotalProcessing().dealWith("illust", mode, page, perPage, date) 126 | event.subject.sendMessage(At(event.sender).plus("正在获取中,请稍后")) 127 | 128 | /** 129 | * 针对数据为空进行通知 130 | */ 131 | if (null == data || data.jsonObject["errors"].toString().isEmpty()) { 132 | event.subject.sendMessage("当前排行榜暂无数据") 133 | return 134 | } 135 | 136 | if (null != data.jsonObject["code"]){ 137 | if (data.jsonObject["code"].toString().toInt() == 400){ 138 | event.subject.sendMessage("需要会员,请购买后使用,请输入购买(月费|季度|半年|年费)会员") 139 | return 140 | } 141 | } 142 | 143 | 144 | 145 | var message: Message = At(event.sender).plus("\n").plus("======插画排行榜($mode)======").plus("\n") 146 | val illusts = data.jsonObject["illusts"]?.jsonArray 147 | 148 | val nodes = mutableListOf() 149 | var isR18 = false 150 | 151 | for (i in (num - 10) until num) { 152 | val id = illusts?.get(i)?.jsonObject?.get("id")?.jsonPrimitive?.content 153 | val title = illusts?.get(i)?.jsonObject?.getValue("title")?.jsonPrimitive?.content 154 | 155 | val user = illusts?.get(i)?.jsonObject?.get("user")?.jsonObject?.get("name")?.jsonPrimitive?.content 156 | 157 | 158 | // val large = 159 | // illusts?.get(i)?.jsonObject?.get("image_urls")?.jsonObject?.get("large")?.jsonPrimitive?.content 160 | 161 | val large = 162 | if (null != illusts?.get(i)?.jsonObject?.get("meta_single_page")?.jsonObject?.get("original_image_url")?.jsonPrimitive?.content) { 163 | illusts[i].jsonObject["meta_single_page"]?.jsonObject?.get("original_image_url")?.jsonPrimitive?.content 164 | } else { 165 | illusts?.get(i)?.jsonObject?.get("meta_pages")?.jsonArray?.get(0)?.jsonObject?.get("image_urls")?.jsonObject?.get( 166 | "original" 167 | )?.jsonPrimitive?.content 168 | } 169 | 170 | val type = illusts?.get(i)?.jsonObject?.get("type")?.jsonPrimitive?.content 171 | val pageCount = illusts?.get(i)?.jsonObject?.get("page_count")?.jsonPrimitive?.content 172 | val sanityLevel = illusts?.get(i)?.jsonObject?.get("sanity_level")?.jsonPrimitive?.content?.toInt() 173 | if (sanityLevel == 6 && !isR18) { 174 | isR18 = true 175 | } 176 | message = message.plus("${(page * 10) - 9 + (i % 10)}. $title - $user - $id").plus("\n") 177 | 178 | if (Config.forward.rankAndTagAndUserByForward) { 179 | var tempMessage = 180 | PlainText("${(page * 10) - 9 + (i % 10)}. $title - $user - $id").plus(" 作品共${pageCount}张") 181 | .plus("\n") 182 | // val detail = PicDetails.getDetailOfId(id!!) 183 | 184 | if ("ugoira".contentEquals(type)) { 185 | val toExternalResource = if(sanityLevel == 6 && Config.lowPoly){ 186 | val byte = PicDetails.getUgoira(id!!.toLong()) 187 | LowPoly.generate( 188 | ByteArrayInputStream(byte), 189 | 200, 190 | 1F, 191 | true, 192 | "png", 193 | false, 194 | 200 195 | ).toByteArray().toExternalResource() 196 | }else{ 197 | PicDetails.getUgoira(id!!.toLong())?.toExternalResource() 198 | } 199 | 200 | val imageId: String? = toExternalResource?.uploadAsImage(event.group)?.imageId 201 | withContext(Dispatchers.IO) { 202 | toExternalResource?.close() 203 | } 204 | if (null != imageId) { 205 | /** 206 | * 判断是否配置了撤回时间 207 | */ 208 | 209 | tempMessage = if (sanityLevel == 6 && enable) { 210 | tempMessage.plus(Image(imageId)) 211 | } else { 212 | tempMessage.plus("无权限查看涩图") 213 | } 214 | } 215 | 216 | } else { 217 | val toExternalResource = if(sanityLevel == 6 && Config.lowPoly){ 218 | val byte = ImageUtil.getImage(large!!.replace("i.pximg.net", "i.acgmx.com"),CacheUtil.Type.PIXIV).toByteArray() 219 | LowPoly.generate( 220 | ByteArrayInputStream(byte), 221 | 200, 222 | 1F, 223 | true, 224 | "png", 225 | false, 226 | 200 227 | ).toByteArray().toExternalResource() 228 | }else{ 229 | ImageUtil.getImage( 230 | large!!.replace("i.pximg.net", "i.acgmx.com"), 231 | CacheUtil.Type.PIXIV 232 | ).toByteArray().toExternalResource() 233 | } 234 | 235 | val imageId: String = toExternalResource.uploadAsImage(event.group).imageId 236 | withContext(Dispatchers.IO) { 237 | toExternalResource.close() 238 | } 239 | tempMessage = if (sanityLevel != 6 || enable) { 240 | tempMessage.plus(Image(imageId)) 241 | } else { 242 | tempMessage.plus("无权限查看涩图") 243 | } 244 | } 245 | 246 | nodes.add( 247 | ForwardMessage.Node( 248 | senderId = event.bot.id, 249 | senderName = event.bot.nameCardOrNick, 250 | time = System.currentTimeMillis().toInt(), 251 | message = tempMessage 252 | ) 253 | ) 254 | } 255 | 256 | } 257 | 258 | if (Config.forward.rankAndTagAndUserByForward) { 259 | val forward = RawForwardMessage(nodes).render(object : ForwardMessage.DisplayStrategy { 260 | override fun generateTitle(forward: RawForwardMessage): String { 261 | return "Pixiv排行榜" 262 | } 263 | 264 | override fun generateSummary(forward: RawForwardMessage): String { 265 | return "查看${nodes.size}条图片" 266 | } 267 | }) 268 | if (isR18 && Config.recall != 0L) { 269 | event.subject.sendMessage(forward).recallIn(Config.recall) 270 | } else { 271 | event.subject.sendMessage(forward) 272 | } 273 | 274 | } else { 275 | event.subject.sendMessage(message) 276 | } 277 | } catch (e: Exception) { 278 | e.printStackTrace() 279 | } 280 | // event.subject.sendMessage(message) 281 | } 282 | 283 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/rank/Tag.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.rank 2 | 3 | import com.hcyacg.details.PicDetails 4 | import com.hcyacg.initial.Command 5 | import com.hcyacg.initial.Config 6 | import com.hcyacg.initial.Setting 7 | import com.hcyacg.utils.CacheUtil 8 | import com.hcyacg.utils.ImageUtil 9 | import com.hcyacg.utils.RequestUtil 10 | import com.hcyacg.utils.logger 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.withContext 13 | import kotlinx.serialization.json.jsonArray 14 | import kotlinx.serialization.json.jsonObject 15 | import kotlinx.serialization.json.jsonPrimitive 16 | import net.mamoe.mirai.contact.nameCardOrNick 17 | import net.mamoe.mirai.event.events.GroupMessageEvent 18 | import net.mamoe.mirai.message.data.* 19 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 20 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 21 | import okhttp3.Headers 22 | import okhttp3.RequestBody 23 | 24 | /** 25 | * @Author: Nekoer 26 | * @Desc: TODO 27 | * @Date: 2021/8/20 21:52 28 | */ 29 | object Tag { 30 | private val headers = Headers.Builder().add("token", Config.token.acgmx).add("referer", "https://www.acgmx.com") 31 | private var requestBody: RequestBody? = null 32 | private val logger by logger() 33 | suspend fun init(event: GroupMessageEvent) { 34 | var enable = false 35 | 36 | try { 37 | if (Setting.groups.contains(event.group.id.toString())) { 38 | enable = true 39 | } 40 | val q = event.message.content.replace(Command.tag, "").replace(" ", "").split("-")[0] 41 | val page = event.message.content.replace(Command.tag, "").replace(" ", "").split("-")[1].toInt() 42 | val offset: Int 43 | val num: Int 44 | if (page % 3 != 0) { 45 | offset = ((page - (page % 3)) / 3) * 30 + 30 46 | num = page % 3 * 10 47 | } else { 48 | offset = (page / 3) * 30 49 | num = 30 50 | } 51 | 52 | 53 | val data = RequestUtil.request( 54 | RequestUtil.Companion.Method.GET, 55 | "https://api.acgmx.com/public/search?q=$q&offset=$offset", 56 | requestBody, 57 | headers.build() 58 | ) 59 | 60 | /** 61 | * 针对数据为空进行通知 62 | */ 63 | if (null == data || data.jsonObject["errors"].toString().isEmpty()) { 64 | event.subject.sendMessage("当前排行榜暂无数据") 65 | return 66 | } 67 | 68 | var message: Message = At(event.sender).plus("\n").plus("======标签排行榜($q)======").plus("\n") 69 | val illusts = data.jsonObject["illusts"]?.jsonArray 70 | 71 | if (null == illusts) { 72 | event.subject.sendMessage("tag数据为空") 73 | return 74 | } 75 | val nodes = mutableListOf() 76 | var isR18 = false 77 | for (i in (num - 10) until num) { 78 | if (illusts.size > i) { 79 | val id = illusts[i].jsonObject["id"]?.jsonPrimitive?.content 80 | val title = illusts[i].jsonObject["title"]?.jsonPrimitive?.content 81 | 82 | val user = illusts[i].jsonObject["user"]?.jsonObject?.get("name")?.jsonPrimitive?.content 83 | val pageCount = illusts[i].jsonObject["page_count"]?.jsonPrimitive?.content 84 | // val large = 85 | // illusts[i].jsonObject["image_urls"]?.jsonObject?.get("large")?.jsonPrimitive?.content 86 | 87 | val large = if (null != illusts[i].jsonObject["meta_single_page"]?.jsonObject?.get("original_image_url")?.jsonPrimitive?.content){ 88 | illusts[i].jsonObject["meta_single_page"]?.jsonObject?.get("original_image_url")?.jsonPrimitive?.content 89 | }else{ 90 | illusts[i].jsonObject["meta_pages"]?.jsonArray?.get(0)?.jsonObject?.get("image_urls")?.jsonObject?.get("original")?.jsonPrimitive?.content 91 | } 92 | val type = illusts[i].jsonObject["type"]?.jsonPrimitive?.content 93 | 94 | val sanityLevel = illusts[i].jsonObject["sanity_level"]?.jsonPrimitive?.content?.toInt() 95 | if (sanityLevel == 6 && !isR18){ 96 | isR18 = true 97 | } 98 | 99 | message = message.plus("${(page * 10) - 9 + (i % 10)}. $title - $user - $id").plus("\n") 100 | if (Config.forward.rankAndTagAndUserByForward) { 101 | var tempMessage = PlainText("${(page * 10) - 9 + (i % 10)}. $title - $user - $id").plus(" 作品共${pageCount}张").plus("\n") 102 | // val detail = PicDetails.getDetailOfId(id!!) 103 | 104 | if ("ugoira".contentEquals(type)) { 105 | val toExternalResource = PicDetails.getUgoira(id!!.toLong())?.toExternalResource() 106 | val imageId: String? = toExternalResource?.uploadAsImage(event.group)?.imageId 107 | withContext(Dispatchers.IO) { 108 | toExternalResource?.close() 109 | } 110 | if (null != imageId) { 111 | /** 112 | * 判断是否配置了撤回时间 113 | */ 114 | 115 | tempMessage = if (sanityLevel != 6 && enable) { 116 | tempMessage.plus(Image(imageId)) 117 | } else { 118 | tempMessage.plus("无权限查看涩图") 119 | } 120 | } 121 | 122 | } else { 123 | val toExternalResource = 124 | ImageUtil.getImage( 125 | large!!.replace("i.pximg.net", "i.acgmx.com"), 126 | CacheUtil.Type.PIXIV 127 | ).toByteArray().toExternalResource() 128 | val imageId: String = toExternalResource.uploadAsImage(event.group).imageId 129 | withContext(Dispatchers.IO) { 130 | toExternalResource.close() 131 | } 132 | tempMessage = if (sanityLevel == 6 || enable) { 133 | tempMessage.plus(Image(imageId)) 134 | } else { 135 | tempMessage.plus("无权限查看涩图") 136 | } 137 | } 138 | 139 | nodes.add( 140 | ForwardMessage.Node( 141 | senderId = event.bot.id, 142 | senderName = event.bot.nameCardOrNick, 143 | time = System.currentTimeMillis().toInt(), 144 | message = tempMessage 145 | ) 146 | ) 147 | } 148 | } 149 | } 150 | if (Config.forward.rankAndTagAndUserByForward) { 151 | val forward = RawForwardMessage(nodes).render(object : ForwardMessage.DisplayStrategy { 152 | override fun generateTitle(forward: RawForwardMessage): String { 153 | return "Tag排行榜" 154 | } 155 | 156 | override fun generateSummary(forward: RawForwardMessage): String { 157 | return "查看${nodes.size}条图片" 158 | } 159 | }) 160 | if (isR18 && Config.recall != 0L){ 161 | event.subject.sendMessage(forward).recallIn(Config.recall) 162 | }else{ 163 | event.subject.sendMessage(forward) 164 | } 165 | } else { 166 | event.subject.sendMessage(message) 167 | } 168 | } catch (e: Exception) { 169 | e.printStackTrace() 170 | } 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/rank/TotalProcessing.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.rank 2 | 3 | 4 | import com.hcyacg.initial.Config 5 | import com.hcyacg.utils.RequestUtil 6 | import com.hcyacg.utils.logger 7 | import kotlinx.serialization.json.JsonElement 8 | import okhttp3.Headers 9 | import okhttp3.RequestBody 10 | 11 | /** 12 | * 排行榜总处理中心 13 | */ 14 | class TotalProcessing { 15 | private val headers = Headers.Builder().add("token", Config.token.acgmx).add("referer", "https://www.acgmx.com") 16 | private val requestBody: RequestBody? = null 17 | private val logger by logger() 18 | /** 19 | * 动态拼接参数并返回数据 20 | */ 21 | fun dealWith(type: String, mode: String, page: Int, perPage: Int, date: String) : JsonElement? { 22 | return try{ 23 | RequestUtil.request( 24 | RequestUtil.Companion.Method.GET, 25 | "https://api.acgmx.com/public/ranking?ranking_type=${type}&mode=${mode}&date=$date&per_page=$perPage&page=$page", 26 | requestBody, 27 | headers.build() 28 | ) 29 | }catch (e:Exception){ 30 | e.printStackTrace() 31 | null 32 | } 33 | 34 | } 35 | 36 | 37 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/science/Style2paints.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.science 2 | 3 | import com.hcyacg.utils.CacheUtil 4 | import com.hcyacg.utils.DataUtil 5 | import com.hcyacg.utils.ImageUtil 6 | import com.hcyacg.utils.logger 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.delay 9 | import kotlinx.coroutines.withContext 10 | import net.mamoe.mirai.event.events.GroupMessageEvent 11 | import net.mamoe.mirai.message.data.Image 12 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 13 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 14 | import okhttp3.* 15 | import java.io.File 16 | import java.util.* 17 | import java.util.concurrent.TimeUnit 18 | 19 | object Style2paints { 20 | 21 | private val client = OkHttpClient().newBuilder().connectTimeout(60000, TimeUnit.MILLISECONDS) 22 | .readTimeout(60000, TimeUnit.MILLISECONDS) 23 | private val logger by logger() 24 | 25 | suspend fun coloring(event: GroupMessageEvent) { 26 | /** 27 | * 获取图片的代码 28 | */ 29 | val url = DataUtil.getImageLink(event.message) ?: return 30 | 31 | val base64 = Base64.getEncoder().encodeToString(ImageUtil.getImage(url, CacheUtil.Type.NONSUPPORT).toByteArray()) 32 | 33 | 34 | var requestBody: RequestBody = FormBody.Builder() 35 | .add("room", "new") 36 | // .add("step", "new") 37 | .add("sketch", "data:image/png;base64,$base64") 38 | // .add("method", "colorization") 39 | .build() 40 | val sketchRes: Response = 41 | client.build().newCall(Request.Builder().url("http://127.0.0.1:8233/upload_sketch").post(requestBody).build()) 42 | .execute() 43 | 44 | val sketch = sketchRes.body?.string().toString().split("_") 45 | sketchRes.close() 46 | 47 | val room = sketch[0] 48 | val time = sketch[1] 49 | 50 | /** 51 | * room: H14M18S59 52 | points: [] 53 | face: null 54 | faceID: 65563 55 | need_render: 0 56 | skipper: null 57 | inv4: 1 58 | r: 0.99 59 | g: 0.83 60 | b: 0.66 61 | h: 0.16666666666666666 62 | d: 0 63 | */ 64 | val num = 35 .. 66 65 | 66 | 67 | //http://localhost/request_result 68 | requestBody = FormBody.Builder() 69 | .add("room", room) 70 | .add("points", "[]") 71 | .add("face", "null") 72 | .add("faceID", "655${num.random()}") 73 | .add("need_render", "0") 74 | .add("skipper", "null") 75 | .add("inv4", "1") 76 | .add("r", "0.99") 77 | .add("g", "0.83") 78 | .add("b", "0.66") 79 | .add("h", "0.16666666666666666") 80 | .add("d", "0") 81 | .build() 82 | val resultRes: Response = 83 | client.build().newCall(Request.Builder().url("http://127.0.0.1:8233/request_result").post(requestBody).build()) 84 | .execute() 85 | val result = resultRes.body?.string().toString().split("_")[1] 86 | resultRes.close() 87 | val file = 88 | 89 | File("E:${File.separator}Desktop${File.separator}style2paints45beta1214B${File.separator}assets${File.separator}game${File.separator}rooms${File.separator}${room}${File.separator}${result}.blended_smoothed_careful.png") 90 | //E:\Desktop\style2paints45beta1214B\assets\game\rooms\H14M28S44\H14M28S46.blended_smoothed_careful.png 91 | var t: Int = 0; 92 | 93 | while (true) { 94 | t++ 95 | if (file.exists()) { 96 | delay(1000) 97 | val image = file.toExternalResource() 98 | val imageId: String = image.uploadAsImage(event.group).imageId 99 | withContext(Dispatchers.IO) { 100 | image.close() 101 | } 102 | event.subject.sendMessage(Image(imageId)) 103 | break 104 | } 105 | 106 | if (t == 60) { 107 | break 108 | } 109 | delay(1000) 110 | } 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/search/Ascii2d.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.search 2 | 3 | import com.hcyacg.initial.Command 4 | import com.hcyacg.initial.Config 5 | import com.hcyacg.utils.CacheUtil 6 | import com.hcyacg.utils.DataUtil 7 | import com.hcyacg.utils.ImageUtil 8 | import com.hcyacg.utils.logger 9 | import net.mamoe.mirai.event.events.GroupMessageEvent 10 | import net.mamoe.mirai.message.data.At 11 | import net.mamoe.mirai.message.data.Image 12 | import net.mamoe.mirai.message.data.Message 13 | import net.mamoe.mirai.message.data.PlainText 14 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 15 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 16 | import org.apache.hc.client5.http.classic.methods.HttpGet 17 | import org.apache.hc.client5.http.config.TlsConfig 18 | import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler 19 | import org.apache.hc.client5.http.impl.classic.HttpClients 20 | import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder 21 | import org.apache.hc.core5.http.HttpHost 22 | import org.apache.hc.core5.http.ssl.TLS 23 | import org.apache.hc.core5.util.Timeout 24 | import org.jsoup.Jsoup 25 | import org.jsoup.nodes.Document 26 | import org.jsoup.select.Elements 27 | import java.util.* 28 | 29 | object Ascii2d: Search { 30 | 31 | private var md5: String = "" 32 | private const val BASEURL: String = "https://ascii2d.net" 33 | private val logger by logger() 34 | 35 | override suspend fun load(event: GroupMessageEvent): List { 36 | val list = mutableListOf() 37 | try { 38 | /** 39 | * 获取图片的代码 40 | */ 41 | val picUri = DataUtil.getImageLink(event.message) 42 | if (picUri == null) { 43 | event.subject.sendMessage("请输入正确的命令 ${Command.picToSearch}图片") 44 | return list 45 | } 46 | val cm = PoolingHttpClientConnectionManagerBuilder.create() 47 | .setDefaultTlsConfig(TlsConfig.custom() 48 | .setHandshakeTimeout(Timeout.ofSeconds(30)) 49 | .setSupportedProtocols(TLS.V_1_1,TLS.V_1_2,TLS.V_1_3) 50 | .build()) 51 | .build() 52 | val host = Config.proxy.host 53 | val port = Config.proxy.port 54 | 55 | val httpClient = if (host.isBlank() || port == -1) { 56 | HttpClients.custom().setConnectionManager(cm).build() 57 | } else{ 58 | HttpClients.custom().setConnectionManager(cm) 59 | .setProxy(HttpHost(Config.proxy.host,Config.proxy.port)).build() 60 | } 61 | val ascii2d = "https://ascii2d.net/search/url/${DataUtil.urlEncode(picUri)}" 62 | // val headers = mutableMapOf() 63 | // headers["User-Agent"] = "PostmanRuntime/7.28.4" 64 | 65 | 66 | val httpGet = HttpGet(ascii2d) 67 | httpGet.addHeader("User-Agent", "PostmanRuntime/7.29.0") 68 | 69 | val result = httpClient.execute(httpGet,BasicHttpClientResponseHandler()) 70 | 71 | val doc: Document = Jsoup.parse(result) 72 | val elementsByClass = doc.select(".item-box") 73 | 74 | elementsByClass.forEach { 75 | val link = it.select(".detail-box a") 76 | if (link.isEmpty()) { 77 | md5 = it.selectFirst(".image-box img")?.attr("alt").toString().lowercase(Locale.getDefault()) 78 | } else { 79 | list.add(color(elementsByClass, event)) 80 | list.add(bovw(event)) 81 | return list 82 | } 83 | } 84 | 85 | list.clear() 86 | return list 87 | } catch (e: Exception) { 88 | if (isNetworkException(e)) { 89 | logger.warn { "连接至Ascii2d的网络出现异常,请检查网络" } 90 | list.add(PlainText("Ascii2d网络异常")) 91 | } else { 92 | logger.error{ e.message } 93 | } 94 | return list 95 | } 96 | } 97 | 98 | private suspend fun color(elements: Elements, event: GroupMessageEvent): Message { 99 | val message: Message = At(event.sender).plus("\n") 100 | elements.forEach { 101 | val link = it.select(".detail-box a") 102 | if (link.size > 1) { 103 | val title = link[0].html() 104 | 105 | val thumbnail = BASEURL + it.select(".image-box img").attr("src") 106 | val uri = link[0].attr("href") 107 | val author = link[1].html() 108 | val authorUrl = link[1].attr("href") 109 | 110 | val externalResource = 111 | ImageUtil.getImage(thumbnail, CacheUtil.Type.NONSUPPORT).toByteArray().toExternalResource() 112 | val imageId: String = externalResource.uploadAsImage(event.group).imageId 113 | externalResource.close() 114 | 115 | 116 | return message.plus(Image(imageId)).plus("\n") 117 | .plus("当前为Ascii2D 颜色检索").plus("\n") 118 | .plus("标题:${title}").plus("\n") 119 | .plus("作者:${author}").plus("\n") 120 | .plus("网址:${uri}").plus("\n") 121 | .plus("作者网址:${authorUrl}") 122 | } 123 | } 124 | return message.plus("程序出现一些问题~请稍后再尝试") 125 | } 126 | 127 | private suspend fun bovw(event: GroupMessageEvent): Message { 128 | val bovwUri = "https://ascii2d.net/search/bovw/$md5" 129 | val headers = mutableMapOf() 130 | headers["User-Agent"] = "PostmanRuntime/7.28.4" 131 | 132 | val doc: Document = Jsoup.connect(bovwUri).headers(headers).timeout(60000).get() 133 | val elements = doc.select(".item-box") 134 | val message: Message = At(event.sender).plus("\n") 135 | elements.forEach { 136 | val link = it.select(".detail-box a") 137 | if (link.isNotEmpty()) { 138 | val title = link[0].html() 139 | 140 | val thumbnail = BASEURL + it.select(".image-box img").attr("src") 141 | val uri = link[0].attr("href") 142 | 143 | 144 | val externalResource = 145 | ImageUtil.getImage(thumbnail, CacheUtil.Type.NONSUPPORT).toByteArray().toExternalResource() 146 | val imageId: String = externalResource.uploadAsImage(event.group).imageId 147 | externalResource.close() 148 | return if (link.size > 1) { 149 | val author = link[1].html() 150 | val authorUrl = link[1].attr("href") 151 | message.plus(Image(imageId)).plus("\n") 152 | .plus("当前为Ascii2D 特征检索").plus("\n") 153 | .plus("标题:${title}").plus("\n") 154 | .plus("网址:${uri}").plus("\n") 155 | .plus("作者:${author}").plus("\n") 156 | .plus("作者网址:${authorUrl}") 157 | } else { 158 | message.plus(Image(imageId)).plus("\n") 159 | .plus("当前为Ascii2D 特征检索").plus("\n") 160 | .plus("标题:${title}").plus("\n") 161 | .plus("网址:${uri}").plus("\n") 162 | } 163 | } 164 | } 165 | return message.plus("程序出现一些问题~请稍后再尝试") 166 | } 167 | 168 | 169 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/search/Google.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.search 2 | 3 | import com.hcyacg.initial.Command 4 | import com.hcyacg.initial.Config 5 | import com.hcyacg.utils.DataUtil 6 | import com.hcyacg.utils.ImageUtil 7 | import com.hcyacg.utils.logger 8 | import net.mamoe.mirai.event.events.GroupMessageEvent 9 | import net.mamoe.mirai.message.data.* 10 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 11 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 12 | import org.jsoup.Jsoup 13 | import org.jsoup.nodes.Document 14 | 15 | object Google: Search { 16 | private val logger by logger() 17 | 18 | override suspend fun load(event: GroupMessageEvent): List { 19 | 20 | val list = mutableListOf() 21 | val ua = 22 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" 23 | val host = Config.proxy.host 24 | val port = Config.proxy.port 25 | /** 26 | * 获取图片的代码 27 | */ 28 | val picUri = DataUtil.getImageLink(event.message) 29 | if (picUri == null) { 30 | event.subject.sendMessage("请输入正确的命令 ${Command.picToSearch}图片") 31 | return list 32 | } 33 | val doc = try { 34 | if (host.isBlank() || port == -1) { 35 | Jsoup.connect( 36 | "${Config.googleConfig.googleImageUrl}/searchbyimage?safe=off&sbisrc=tg&image_url=${ 37 | DataUtil.urlEncode(picUri) 38 | }&hl=zh-CN" 39 | ).header("User-Agent", ua).timeout(60000).get() 40 | } else { 41 | Jsoup.connect( 42 | "${Config.googleConfig.googleImageUrl}/searchbyimage?safe=off&sbisrc=tg&image_url=${ 43 | DataUtil.urlEncode(picUri) 44 | }&hl=zh-CN" 45 | ).header("User-Agent", ua).proxy(host, port).timeout(60000).get() 46 | } 47 | } catch (e: Exception) { 48 | return if (isNetworkException(e)) { 49 | logger.warn { "连接至Google的网络出现异常,请检查网络" } 50 | list.add(PlainText("Google网络异常")) 51 | list 52 | } else { 53 | logger.error{ e.message } 54 | list 55 | } 56 | } 57 | 58 | // Google精准搜索 59 | try { 60 | val links = doc.select("a[href]") 61 | val linkHref = links.find { it.text() == "全部尺寸" } 62 | if (linkHref != null) { 63 | val docAllSize: Document = if (host.isBlank() || port == -1) { 64 | Jsoup.connect("${Config.googleConfig.googleImageUrl}${linkHref.attr("href")}") 65 | .header("User-Agent", ua).timeout(60000).get() 66 | } else { 67 | Jsoup.connect("${Config.googleConfig.googleImageUrl}${linkHref.attr("href")}") 68 | .header("User-Agent", ua).proxy(host, port).timeout(60000).get() 69 | } 70 | val element = docAllSize.selectFirst("div[data-ri]") 71 | if (element != null) { 72 | val title = element.selectFirst("h3")?.text() 73 | val url = element.selectFirst("a[rel=\"noopener\"]")?.attr("href") 74 | val imageDataId = element.selectFirst("img")?.attr("data-iid") 75 | val image = docAllSize.html().substringAfter("_setImgSrc('${imageDataId}','").substringBefore("');") 76 | val imageId = if (image.isNotBlank()) { 77 | val toExternalResource = ImageUtil.generateImage(image)?.toExternalResource() 78 | toExternalResource?.uploadAsImage(event.group)?.imageId 79 | } else { 80 | null 81 | } 82 | 83 | list.add(buildMessageChain { 84 | +At(event.sender) 85 | +PlainText("\n") 86 | if (imageId != null) { 87 | +Image(imageId) 88 | } 89 | +PlainText("\n当前为Google精准搜索\n") 90 | +PlainText("标题:${title}\n") 91 | +PlainText("网址:${url}") 92 | }) 93 | } 94 | } 95 | } catch (e: Exception) { 96 | if (isNetworkException(e)) { 97 | logger.warn { "连接至Google的网络出现异常,请检查网络" } 98 | list.add(PlainText("Google精准搜索网络异常")) 99 | } else { 100 | logger.error{e.message} 101 | } 102 | } 103 | 104 | // Google相似搜索 105 | try { 106 | var num = 0 107 | doc.select("#search .ULSxyf").last()?.select(".g")?.forEach { 108 | if (num < Config.googleConfig.resultNum - 1) { 109 | val title = it.selectFirst("h3")?.text() 110 | val url = it.selectFirst("a")?.attr("href") 111 | val imageDataId = it.select("img").last()?.id() 112 | val image = doc.html().substringBefore("';var ii=['${imageDataId}']").substringAfterLast("(function(){var s='") 113 | logger.debug { image } 114 | val imageId = if (image.isNotBlank()) { 115 | val toExternalResource = ImageUtil.generateImage(image)?.toExternalResource() 116 | toExternalResource?.uploadAsImage(event.group)?.imageId 117 | } else { 118 | null 119 | } 120 | 121 | list.add(buildMessageChain { 122 | +At(event.sender) 123 | +PlainText("\n") 124 | if (imageId != null) { 125 | +Image(imageId) 126 | } 127 | +PlainText("\n当前为Google相似搜索\n") 128 | +PlainText("标题:${title}\n") 129 | +PlainText("网址:${url}") 130 | }) 131 | num += 1 132 | } 133 | } 134 | } catch (e: Exception) { 135 | if (isNetworkException(e)) { 136 | logger.warn { "连接至Google的网络出现异常,请检查网络" } 137 | list.add(PlainText("Google相似搜索网络异常")) 138 | } else { 139 | logger.error{ e.message } 140 | } 141 | } 142 | 143 | return list 144 | } 145 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/search/Iqdb.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.search 2 | 3 | import com.hcyacg.initial.Command 4 | import com.hcyacg.initial.Config 5 | import com.hcyacg.utils.CacheUtil 6 | import com.hcyacg.utils.DataUtil 7 | import com.hcyacg.utils.ImageUtil 8 | import com.hcyacg.utils.logger 9 | import net.mamoe.mirai.event.events.GroupMessageEvent 10 | import net.mamoe.mirai.message.data.At 11 | import net.mamoe.mirai.message.data.Image 12 | import net.mamoe.mirai.message.data.Message 13 | import net.mamoe.mirai.message.data.PlainText 14 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 15 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 16 | import org.jsoup.Jsoup 17 | 18 | object Iqdb: Search { 19 | private val logger by logger() 20 | 21 | override suspend fun load(event: GroupMessageEvent) :List{ 22 | val list = mutableListOf() 23 | val host = Config.proxy.host 24 | val port = Config.proxy.port 25 | try{ 26 | /** 27 | * 获取图片的代码 28 | */ 29 | val picUri = DataUtil.getImageLink(event.message) 30 | if (picUri == null) { 31 | event.subject.sendMessage("请输入正确的命令 ${Command.picToSearch}图片") 32 | return list 33 | } 34 | 35 | val lqdb = "https://www.iqdb.org" 36 | val message: Message = At(event.sender).plus("\n") 37 | 38 | 39 | val doc = try { 40 | if (host.isBlank() || port == -1) { 41 | Jsoup.connect(lqdb).data("url",picUri).timeout(60000).post() 42 | } else { 43 | Jsoup.connect(lqdb).data("url",picUri).proxy(host, port).timeout(60000).post() 44 | } 45 | } catch (e: Exception) { 46 | if (isNetworkException(e)) { 47 | logger.warn { "连接至Iqdb的网络出现异常,请检查网络" } 48 | list.add(PlainText("Iqdb网络异常")) 49 | } else { 50 | logger.error{ e.message } 51 | } 52 | return list 53 | } 54 | 55 | val select = doc.select("#pages div") 56 | select.forEach { 57 | if(!it.html().contains("Your image")){ 58 | // || it.html().contains("Additional match") 59 | if (it.html().contains("Best match")){ 60 | val pic = lqdb + it.select(".image img").attr("src") 61 | var uri = it.select(".image a").attr("href") 62 | 63 | if (!uri.contains("https") && !uri.contains("http")){ 64 | uri = "https:$uri" 65 | } 66 | 67 | val similarity = it.select("td").eq(3).text().replace(" similarity","") 68 | 69 | val externalResource = ImageUtil.getImage(pic, CacheUtil.Type.NONSUPPORT).toByteArray().toExternalResource() 70 | val imageId: String = externalResource.uploadAsImage(event.group).imageId 71 | externalResource.close() 72 | list.add(message.plus(Image(imageId)).plus("\n") 73 | .plus("当前为lqdb").plus("\n") 74 | .plus("网址:${uri}").plus("\n") 75 | .plus("相似度:${similarity}")) 76 | } 77 | 78 | } 79 | } 80 | return list 81 | } catch (e:Exception){ 82 | if (isNetworkException(e)) { 83 | logger.warn { "连接至Iqdb的网络出现异常,请检查网络" } 84 | list.add(PlainText("Iqdb网络异常")) 85 | } else { 86 | logger.error{ e.message } 87 | } 88 | return list 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/search/Search.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.search 2 | 3 | import net.mamoe.mirai.event.events.GroupMessageEvent 4 | import net.mamoe.mirai.message.data.Message 5 | import org.jsoup.HttpStatusException 6 | import java.io.IOException 7 | import java.net.ConnectException 8 | import java.net.SocketException 9 | import java.net.SocketTimeoutException 10 | 11 | interface Search { 12 | 13 | suspend fun load(event: GroupMessageEvent): List 14 | 15 | fun isNetworkException(e: Exception): Boolean { 16 | return e is HttpStatusException || e is SocketTimeoutException || e is ConnectException || e is SocketException || e is IOException 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/search/SearchPicCenter.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.search 2 | 3 | import com.hcyacg.initial.Config 4 | import com.hcyacg.utils.logger 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.launch 8 | import kotlinx.coroutines.runBlocking 9 | import net.mamoe.mirai.contact.nameCardOrNick 10 | import net.mamoe.mirai.event.events.GroupMessageEvent 11 | import net.mamoe.mirai.message.data.At 12 | import net.mamoe.mirai.message.data.ForwardMessage 13 | import net.mamoe.mirai.message.data.RawForwardMessage 14 | 15 | /** 16 | * 搜索二次元图片转发中心 17 | */ 18 | object SearchPicCenter { 19 | private val logger by logger() 20 | suspend fun forward(event: GroupMessageEvent) { 21 | val nodes = mutableListOf() 22 | event.subject.sendMessage(At(event.sender).plus("正在获取中,请稍后")) 23 | 24 | 25 | val scope = CoroutineScope(Dispatchers.Default) 26 | 27 | val srSauceNao = scope.launch { 28 | if (Config.enable.search.saucenao) { 29 | //Saucenao 搜索 30 | val picToSearch = Saucenao.load(event) 31 | picToSearch.forEach { 32 | nodes.add( 33 | ForwardMessage.Node( 34 | senderId = event.bot.id, 35 | senderName = event.bot.nameCardOrNick, 36 | time = System.currentTimeMillis().toInt(), 37 | message = it 38 | ) 39 | ) 40 | } 41 | } 42 | } 43 | 44 | val srAscii2d = scope.launch { 45 | if (Config.enable.search.ascii2d) { 46 | val picToHtmlSearch = Ascii2d.load(event) 47 | //Ascii2d 搜索 48 | picToHtmlSearch.forEach { 49 | nodes.add( 50 | ForwardMessage.Node( 51 | senderId = event.bot.id, 52 | senderName = event.bot.nameCardOrNick, 53 | time = System.currentTimeMillis().toInt(), 54 | message = it 55 | ) 56 | ) 57 | } 58 | } 59 | } 60 | 61 | val srIqdb = scope.launch { 62 | if (Config.enable.search.iqdb) { 63 | //iqdb搜索 64 | val iqdb = Iqdb.load(event) 65 | iqdb.forEach { 66 | nodes.add( 67 | ForwardMessage.Node( 68 | senderId = event.bot.id, 69 | senderName = event.bot.nameCardOrNick, 70 | time = System.currentTimeMillis().toInt(), 71 | message = it 72 | ) 73 | ) 74 | } 75 | } 76 | } 77 | 78 | val srYandex = scope.launch { 79 | if (Config.enable.search.yandex) { 80 | val yandex = Yandex.load(event) 81 | yandex.forEach { 82 | nodes.add( 83 | ForwardMessage.Node( 84 | senderId = event.bot.id, 85 | senderName = event.bot.nameCardOrNick, 86 | time = System.currentTimeMillis().toInt(), 87 | message = it 88 | ) 89 | ) 90 | } 91 | } 92 | } 93 | 94 | val srGoogle = scope.launch { 95 | if (Config.enable.search.google) { 96 | //谷歌搜图 97 | val google = Google.load(event) 98 | google.forEach { 99 | nodes.add( 100 | ForwardMessage.Node( 101 | senderId = event.bot.id, 102 | senderName = event.bot.nameCardOrNick, 103 | time = System.currentTimeMillis().toInt(), 104 | message = it 105 | ) 106 | ) 107 | } 108 | } 109 | } 110 | 111 | // 等待并发完成 112 | runBlocking { 113 | srSauceNao.join() 114 | srAscii2d.join() 115 | srIqdb.join() 116 | srYandex.join() 117 | srGoogle.join() 118 | } 119 | 120 | //合并QQ消息 发送查询到的图片线索 121 | val forward = RawForwardMessage(nodes).render(object : ForwardMessage.DisplayStrategy { 122 | override fun generateTitle(forward: RawForwardMessage): String { 123 | return "查询到的图片线索" 124 | } 125 | 126 | override fun generateSummary(forward: RawForwardMessage): String { 127 | return "查看${nodes.size}条图片线索" 128 | } 129 | }) 130 | 131 | event.subject.sendMessage(forward) 132 | } 133 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/search/Trace.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.search 2 | 3 | import com.hcyacg.entity.Anilist 4 | import com.hcyacg.utils.* 5 | import com.madgag.gif.fmsware.AnimatedGifEncoder 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | import kotlinx.serialization.json.Json 9 | import kotlinx.serialization.json.JsonElement 10 | import kotlinx.serialization.json.decodeFromJsonElement 11 | import net.mamoe.mirai.event.events.GroupMessageEvent 12 | import net.mamoe.mirai.message.data.At 13 | import net.mamoe.mirai.message.data.Image 14 | import net.mamoe.mirai.message.data.Message 15 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 16 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 17 | import okhttp3.Headers 18 | import okhttp3.RequestBody 19 | import okhttp3.RequestBody.Companion.toRequestBody 20 | import org.bytedeco.javacv.FFmpegFrameGrabber 21 | import org.bytedeco.javacv.Java2DFrameConverter 22 | import org.jsoup.HttpStatusException 23 | import java.io.ByteArrayOutputStream 24 | import java.io.IOException 25 | import java.io.InputStream 26 | import java.net.ConnectException 27 | import java.net.SocketException 28 | import java.net.SocketTimeoutException 29 | import java.text.SimpleDateFormat 30 | import java.util.* 31 | 32 | /** 33 | * 以图片搜番剧 34 | */ 35 | object Trace { 36 | private val headers = Headers.Builder() 37 | private var requestBody: RequestBody? = null 38 | private val logger by logger() 39 | 40 | suspend fun searchInfoByPic(event: GroupMessageEvent) { 41 | val data: JsonElement? 42 | // https://api.trace.moe/search?url= 43 | 44 | try { 45 | /** 46 | * 获取图片的代码 47 | */ 48 | val picUri = DataUtil.getImageLink(event.message) ?: return 49 | 50 | logger.debug { picUri } 51 | data = RequestUtil.request( 52 | RequestUtil.Companion.Method.GET, 53 | "https://api.trace.moe/search?cutBorders&url=${DataUtil.urlEncode(picUri)}", 54 | requestBody, 55 | headers.build() 56 | ) 57 | 58 | val trace = data?.let { Json.decodeFromJsonElement(it) } 59 | logger.debug { data.toString() } 60 | val result = trace?.result 61 | logger.debug { result } 62 | 63 | 64 | 65 | val message: Message = At(event.sender).plus("\n") 66 | 67 | /** 68 | * 获得搜到的番剧信息 69 | */ 70 | val anilist = result?.get(0)?.anilist 71 | val fileName = result?.get(0)?.filename 72 | val episode = result?.get(0)?.episode 73 | val from = result?.get(0)?.from 74 | val to = result?.get(0)?.to 75 | val similarity = result?.get(0)?.similarity 76 | val video = result?.get(0)?.video 77 | val image = result?.get(0)?.image 78 | // var externalResource = ImageUtil.getImage(image)?.toByteArray()?.toExternalResource() 79 | // val imageId: String = externalResource?.uploadAsImage(event.group)!!.imageId 80 | headers.add("Content-Type", "application/json") 81 | 82 | 83 | requestBody = "{\"query\": \"query{Media(id: $anilist, type: ANIME) {id title { native} coverImage {extraLarge}}}\"}" 84 | .toRequestBody() 85 | val tempData = RequestUtil.request( 86 | RequestUtil.Companion.Method.POST, 87 | "https://graphql.anilist.co", 88 | requestBody, 89 | headers.build() 90 | ) 91 | val json = Json{ignoreUnknownKeys = true} 92 | 93 | val aniListEntity = tempData?.let { json.decodeFromJsonElement(it) } 94 | 95 | // val cn = JSONObject.parseObject(JSONObject.parseObject(JSONObject.parseObject(tempData!!.getString("data")).getString("Media")).getString("title")).getString("chinese") 96 | val jp = aniListEntity?.data?.media?.title?.native 97 | val coverImage = aniListEntity?.data?.media?.coverImage?.extraLarge 98 | var externalResource = coverImage?.let { ImageUtil.getImage(it, CacheUtil.Type.NONSUPPORT).toByteArray().toExternalResource() } 99 | val imageId: String? = externalResource?.uploadAsImage(event.group)?.imageId 100 | 101 | 102 | 103 | //开始时间 104 | val formatter = SimpleDateFormat("HH:mm:ss") 105 | formatter.timeZone = TimeZone.getTimeZone("GMT+00:00") 106 | val startTime = formatter.format(from.toString().split(".")[0].toLong() * 1000) 107 | val endTime = formatter.format(to.toString().split(".")[0].toLong() * 1000) 108 | 109 | event.subject.sendMessage( 110 | message 111 | .plus(Image(imageId!!)).plus("\n") 112 | // .plus("番名:${cn}").plus("\n") 113 | .plus("番名:${jp}").plus("\n") 114 | .plus("别名:${fileName}").plus("\n") 115 | .plus("集数:${episode}").plus("\n") 116 | .plus("出现在:${startTime} - $endTime").plus("\n") 117 | .plus("相似度:${similarity?.let { DataUtil.getPercentFormat(it.toDouble(), 2, 2) }}") 118 | ) 119 | withContext(Dispatchers.IO) { 120 | externalResource?.close() 121 | } 122 | /** 123 | * 发送视频文件 124 | */ 125 | val input = ImageUtil.getVideo("$video&size=l") 126 | if (null != input){ 127 | externalResource = video2Gif(input).toByteArray().toExternalResource() 128 | event.subject.sendMessage(Image(externalResource.uploadAsImage(event.group).imageId)) 129 | } 130 | 131 | } catch (e: IOException) { 132 | logger.warn { "连接至Trace出现异常,请检查网络" } 133 | event.subject.sendMessage("Trace网络异常") 134 | 135 | } catch (e: HttpStatusException) { 136 | logger.warn{ "连接至Trace的网络超时,请检查网络" } 137 | event.subject.sendMessage("Trace网络异常") 138 | 139 | } catch (e: SocketTimeoutException) { 140 | logger.warn { "连接至Trace的网络超时,请检查网络" } 141 | event.subject.sendMessage("Trace网络异常") 142 | 143 | } catch (e: ConnectException) { 144 | logger.warn { "连接至Trace的网络出现异常,请检查网络" } 145 | event.subject.sendMessage("Trace网络异常") 146 | 147 | } catch (e: SocketException) { 148 | logger.warn { "连接至Trace的网络出现异常,请检查网络" } 149 | event.subject.sendMessage("Trace网络异常") 150 | } catch (e:IllegalStateException){ 151 | logger.error { e.message } 152 | event.subject.sendMessage("该功能发现错误,错误信息【${e.message}】") 153 | } 154 | } 155 | 156 | @Throws(Exception::class) 157 | private fun video2Gif(videoPath: InputStream): ByteArrayOutputStream { 158 | val infoStream = ByteArrayOutputStream() 159 | 160 | try{ 161 | FFmpegFrameGrabber(videoPath).use { grabber -> 162 | grabber.start() 163 | val frames: Int = grabber.lengthInFrames 164 | val encoder = AnimatedGifEncoder() 165 | encoder.setFrameRate(frames.toFloat()) 166 | encoder.start(infoStream) 167 | val converter = Java2DFrameConverter() 168 | var i = 0 169 | while (i < frames) { 170 | // 8帧合成1帧?(反正越大动图越小、越快) 171 | encoder.setDelay(grabber.delayedTime.toInt()) 172 | encoder.addFrame(converter.convert(grabber.grabImage())) 173 | grabber.frameNumber = i 174 | i += 8 175 | } 176 | encoder.finish() 177 | } 178 | return infoStream 179 | }catch (e:Exception){ 180 | logger.error { e.message } 181 | return infoStream 182 | }catch(e:NoClassDefFoundError){ 183 | logger.error { "您未使用FFmpeg版本,将缺少视频转Gif的功能" } 184 | return infoStream 185 | } 186 | } 187 | 188 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/search/Yandex.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.search 2 | 3 | import com.hcyacg.entity.YandexImage 4 | import com.hcyacg.entity.YandexSearchResult 5 | import com.hcyacg.initial.Command 6 | import com.hcyacg.utils.* 7 | import kotlinx.serialization.json.Json 8 | import kotlinx.serialization.json.decodeFromJsonElement 9 | import net.mamoe.mirai.event.events.GroupMessageEvent 10 | import net.mamoe.mirai.message.data.At 11 | import net.mamoe.mirai.message.data.Image 12 | import net.mamoe.mirai.message.data.Message 13 | import net.mamoe.mirai.message.data.PlainText 14 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 15 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 16 | import okhttp3.Headers 17 | import okhttp3.RequestBody 18 | import org.jsoup.Jsoup 19 | import org.jsoup.nodes.Document 20 | 21 | object Yandex: Search { 22 | 23 | private val logger by logger() 24 | private val headers = Headers.Builder() 25 | private val requestBody: RequestBody? = null 26 | private val json = Json { ignoreUnknownKeys = true } 27 | 28 | override suspend fun load(event: GroupMessageEvent): List { 29 | val list = mutableListOf() 30 | 31 | try { 32 | 33 | /** 34 | * 获取图片的代码 35 | */ 36 | val picUri = DataUtil.getImageLink(event.message) 37 | if (picUri == null) { 38 | event.subject.sendMessage("请输入正确的命令 ${Command.picToSearch}图片") 39 | return list 40 | } 41 | 42 | val yandexImageUpload = 43 | "https://yandex.com/images-apphost/image-download?url=${DataUtil.urlEncode(picUri)}&cbird=111&images_avatars_size=preview&images_avatars_namespace=images-cbir" 44 | val message: Message = At(event.sender).plus("\n") 45 | 46 | 47 | val data = RequestUtil.request(RequestUtil.Companion.Method.GET, yandexImageUpload, requestBody, headers.build()) 48 | val yandexImage = data?.let { json.decodeFromJsonElement(it) } 49 | 50 | val yandexSearch = "https://yandex.com/images/search?rpt=imageview&url=${yandexImage?.url?.replace("preview","orig")}&cbir_id=${yandexImage?.imageShard}/${yandexImage?.imageId}" 51 | 52 | 53 | val doc: Document = Jsoup.connect(yandexSearch).timeout(60000).get() 54 | val select = doc.select(".cbir-section_name_sites").select(".Root") 55 | select.forEach { it -> 56 | val yandexSearchResult = json.parseToJsonElement(it.attr("data-state")).let { json.decodeFromJsonElement(it) } 57 | 58 | 59 | var pic = yandexSearchResult.sites?.get(0)?.thumb?.url 60 | 61 | if (!pic!!.contains("http") && !pic.contains("http")){ 62 | pic = "https:${pic}" 63 | } 64 | 65 | val externalResource = ImageUtil.getImage(pic, CacheUtil.Type.NONSUPPORT).toByteArray().toExternalResource() 66 | val imageId: String = externalResource.uploadAsImage(event.group).imageId 67 | externalResource.close() 68 | list.add(message.plus(Image(imageId)).plus("\n") 69 | .plus("当前为Yandex").plus("\n") 70 | .plus("标题:${yandexSearchResult.sites?.get(0)?.title}").plus("\n") 71 | .plus("介绍:${yandexSearchResult.sites?.get(0)?.description}").plus("\n") 72 | .plus("网址:${yandexSearchResult.sites?.get(0)?.url}").plus("\n") 73 | ) 74 | } 75 | return list 76 | } catch (e: Exception) { 77 | if (isNetworkException(e)) { 78 | logger.warn { "连接至Yandex的网络出现异常,请检查网络" } 79 | list.add(PlainText("Yandex网络异常")) 80 | } else { 81 | logger.error{ e.message } 82 | } 83 | return list 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/sexy/LoliconCenter.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.sexy 2 | 3 | import com.hcyacg.entity.Lolicon 4 | import com.hcyacg.initial.Command 5 | import com.hcyacg.initial.Config 6 | import com.hcyacg.initial.Setting 7 | import com.hcyacg.lowpoly.LowPoly 8 | import com.hcyacg.utils.CacheUtil 9 | import com.hcyacg.utils.ImageUtil 10 | import com.hcyacg.utils.RequestUtil 11 | import com.hcyacg.utils.logger 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.withContext 14 | import kotlinx.serialization.json.Json 15 | import kotlinx.serialization.json.JsonElement 16 | import kotlinx.serialization.json.decodeFromJsonElement 17 | import net.mamoe.mirai.event.events.GroupMessageEvent 18 | import net.mamoe.mirai.message.data.At 19 | import net.mamoe.mirai.message.data.Image 20 | import net.mamoe.mirai.message.data.QuoteReply 21 | import net.mamoe.mirai.utils.ExternalResource 22 | import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource 23 | import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage 24 | import okhttp3.Headers 25 | import okhttp3.RequestBody 26 | import org.jsoup.HttpStatusException 27 | import java.io.ByteArrayInputStream 28 | import java.io.IOException 29 | import java.net.ConnectException 30 | import java.net.SocketException 31 | import java.net.SocketTimeoutException 32 | import javax.net.ssl.SSLHandshakeException 33 | 34 | object LoliconCenter { 35 | private val requestBody: RequestBody? = null 36 | private var isChange: Boolean = false 37 | private val logger by logger() 38 | private val headers = Headers.Builder() 39 | 40 | suspend fun load(event: GroupMessageEvent) { 41 | val message = QuoteReply(event.message) 42 | if (!Setting.groups.contains(event.group.id.toString())) { 43 | event.subject.sendMessage("该群无权限查看涩图") 44 | return 45 | } 46 | 47 | if (!Config.enable.sexy.lolicon) { 48 | event.subject.sendMessage(message.plus("已关闭lolicon")) 49 | return 50 | } 51 | 52 | event.subject.sendMessage(At(event.sender).plus("正在获取中,请稍后")) 53 | val data: JsonElement? 54 | 55 | val temp = event.message.contentToString().replace("${Command.lolicon} ", "").split(" ") 56 | //https://api.lolicon.app/setu/v2?r18=2&proxy=i.acgmx.com&size=original&keyword=loli 57 | var r18 = 0 58 | val keyword: String 59 | var url = "https://api.lolicon.app/setu/v2?proxy=i.acgmx.com&size=${Config.loliconSize}" 60 | 61 | if (temp.isNotEmpty()) { 62 | keyword = temp[0] 63 | 64 | if (keyword.contentEquals("r18")){ 65 | r18 = 1 66 | }else{ 67 | val key = keyword.split("[\\&\\,\\@\\%\\$\\*\\,]".toRegex()) 68 | if (key.size <= 3) { 69 | key.forEach { 70 | url = url.plus("&tag=$it") 71 | } 72 | } else { 73 | event.subject.sendMessage(message.plus("关联tag最多三个")) 74 | return 75 | } 76 | } 77 | } 78 | 79 | 80 | 81 | if (temp.size == 2) { 82 | r18 = if (temp[1].contentEquals("r18")) { 83 | 1 84 | } else { 85 | 0 86 | } 87 | } 88 | url = url.plus("&r18=$r18") 89 | 90 | 91 | try { 92 | data = RequestUtil.request( 93 | RequestUtil.Companion.Method.GET, 94 | url, 95 | requestBody, 96 | headers.build() 97 | ) 98 | 99 | 100 | val lolicon = data?.let { Json.decodeFromJsonElement(it) } 101 | 102 | if (null == lolicon) { 103 | event.subject.sendMessage(message.plus("Lolicon数据为空")) 104 | return 105 | } 106 | 107 | if (lolicon.data.isNullOrEmpty()) { 108 | event.subject.sendMessage(message.plus("Lolicon数据为空")) 109 | return 110 | } 111 | 112 | 113 | if (null == lolicon.data[0].urls) { 114 | event.subject.sendMessage(message.plus("Lolicon数据为空")) 115 | return 116 | } 117 | val toExternalResource: ExternalResource 118 | if (Config.lowPoly){ 119 | val byte = ImageUtil.getImage(lolicon.data[0].urls?.original!!, CacheUtil.Type.LOLICON).toByteArray() 120 | 121 | /** 122 | * 生成low poly风格的图片 123 | * @param inputStream 源图片 124 | * @param accuracy 精度值,越小精度越高 125 | * @param scale 缩放,源图片和目标图片的尺寸比例 126 | * @param fill 是否填充颜色,为false时只绘制线条 127 | * @param format 输出图片格式 128 | * @param antiAliasing 是否抗锯齿 129 | * @param pointCount 随机点的数量 130 | */ 131 | toExternalResource = LowPoly.generate( 132 | ByteArrayInputStream(byte), 133 | 200, 134 | 1F, 135 | true, 136 | "png", 137 | false, 138 | 200 139 | ).toByteArray().toExternalResource() 140 | }else{ 141 | toExternalResource = ImageUtil.getImage(lolicon.data[0].urls?.original!!, CacheUtil.Type.LOLICON).toByteArray() 142 | .toExternalResource() 143 | } 144 | 145 | 146 | 147 | 148 | val imageId: String = toExternalResource.uploadAsImage(event.group).imageId 149 | withContext(Dispatchers.IO) { 150 | toExternalResource.close() 151 | } 152 | 153 | if (r18 == 1 && Config.recall != 0L) { 154 | event.subject.sendMessage( 155 | message.plus(Image(imageId)).plus("图片链接:\nhttps://www.pixiv.net/artworks/${lolicon.data[0].pid}") 156 | ).recallIn(Config.recall) 157 | } else { 158 | event.subject.sendMessage( 159 | message.plus(Image(imageId)).plus("图片链接:\nhttps://www.pixiv.net/artworks/${lolicon.data[0].pid}") 160 | ) 161 | } 162 | } catch (e: IOException) { 163 | logger.warn { "连接至Lolicon出现异常,请检查网络" } 164 | event.subject.sendMessage(message.plus("网络异常")) 165 | } catch (e: SSLHandshakeException) { 166 | logger.warn { "连接至Lolicon的网络超时,请检查网络" } 167 | event.subject.sendMessage(message.plus("网络异常")) 168 | } catch (e: HttpStatusException) { 169 | logger.warn { "连接至Lolicon的网络超时,请检查网络" } 170 | event.subject.sendMessage(message.plus("网络异常")) 171 | } catch (e: SocketTimeoutException) { 172 | logger.warn { "连接至Lolicon的网络超时,请检查网络" } 173 | event.subject.sendMessage(message.plus("网络异常")) 174 | } catch (e: ConnectException) { 175 | logger.warn { "连接至Lolicon的网络超时,请检查网络" } 176 | event.subject.sendMessage(message.plus("网络异常")) 177 | } catch (e: SocketException) { 178 | logger.warn { "连接至Lolicon的网络超时,请检查网络" } 179 | event.subject.sendMessage(message.plus("网络异常")) 180 | } catch (e: Exception) { 181 | logger.error { e.message } 182 | e.printStackTrace() 183 | event.subject.sendMessage(message.plus("服务错误")) 184 | } 185 | } 186 | 187 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/sexy/WarehouseCenter.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.sexy 2 | 3 | import com.hcyacg.utils.logger 4 | import net.mamoe.mirai.event.events.GroupMessageEvent 5 | import net.mamoe.mirai.message.data.QuoteReply 6 | 7 | /** 8 | * 从指定仓库发送图片 9 | */ 10 | object WarehouseCenter { 11 | private val logger by logger() 12 | 13 | suspend fun init(event: GroupMessageEvent) { 14 | 15 | try{ 16 | val message = QuoteReply(event.message) 17 | event.subject.sendMessage(message) 18 | 19 | }catch (e:Exception){ 20 | e.printStackTrace() 21 | } 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/CacheUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils 2 | 3 | import com.hcyacg.initial.Config 4 | import java.io.ByteArrayOutputStream 5 | import java.io.File 6 | import java.io.FileOutputStream 7 | 8 | object CacheUtil { 9 | private val logger by logger() 10 | 11 | enum class Type{ 12 | PIXIV,YANDE,LOLICON,KONACHAN,NONSUPPORT 13 | } 14 | 15 | fun saveToLocal(infoStream: ByteArrayOutputStream, type:Type,imageName:String){ 16 | try{ 17 | val temp:String? = when (type){ 18 | Type.PIXIV -> { 19 | "pixiv" 20 | } 21 | Type.YANDE -> { 22 | "yande" 23 | } 24 | Type.LOLICON -> { 25 | "lolicon" 26 | } 27 | Type.KONACHAN -> { 28 | "konachan" 29 | } 30 | Type.NONSUPPORT -> { 31 | null 32 | } 33 | } 34 | 35 | if (temp.isNullOrEmpty()){ 36 | return 37 | } 38 | 39 | val directory = File(Config.cache.directory) 40 | val imageDir = File(directory.path + File.separator + temp) 41 | if (!imageDir.exists()){ 42 | imageDir.mkdirs() 43 | } 44 | val out = FileOutputStream(directory.path + File.separator + temp+ File.separator +imageName) 45 | out.write(infoStream.toByteArray()) 46 | out.close() 47 | }catch (e:Exception){ 48 | logger.error { e.message } 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/DataUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils; 2 | 3 | import net.mamoe.mirai.message.data.* 4 | import net.mamoe.mirai.message.data.Image.Key.queryUrl 5 | import java.io.UnsupportedEncodingException 6 | import java.net.URLEncoder 7 | import java.text.NumberFormat 8 | 9 | /** 10 | * @Author Nekoer 11 | * @Date 2020/8/12 12:18 12 | * @Desc 对文本进行各种操作 13 | */ 14 | class DataUtil { 15 | companion object { 16 | private val logger by logger() 17 | 18 | suspend fun getImageLink(chain: MessageChain): String? { 19 | chain.forEach { 20 | if (it is Image) { 21 | return it.queryUrl() 22 | } 23 | } 24 | return null 25 | } 26 | 27 | fun urlEncode(url: String): String { 28 | return URLEncoder.encode(url, "UTF-8") 29 | } 30 | 31 | fun getSubString(text: String, left: String?, right: String?): String { 32 | val result: String 33 | var zLen: Int 34 | if (left == null || left.isEmpty()) { 35 | zLen = 0 36 | } else { 37 | zLen = text.indexOf(left) 38 | if (zLen > -1) { 39 | zLen += left.length 40 | } else { 41 | zLen = 0 42 | } 43 | } 44 | var yLen = text.indexOf(right!!, zLen) 45 | if (yLen < 0 || right.isEmpty()) { 46 | yLen = text.length 47 | } 48 | result = text.substring(zLen, yLen) 49 | return result 50 | } 51 | 52 | @Throws(UnsupportedEncodingException::class) 53 | fun getFileNameByURL(data: String): String { 54 | val resultURL = StringBuilder() 55 | //遍历字符串 56 | for (element in data) { 57 | //只对汉字处理 58 | val encode = URLEncoder.encode(element.toString() + "", "UTF-8") 59 | resultURL.append(encode) 60 | } 61 | return resultURL.toString() 62 | } 63 | 64 | 65 | fun getPercentFormat(d: Double, IntegerDigits: Int, FractionDigits: Int): String? { 66 | val nf = NumberFormat.getPercentInstance() 67 | nf.maximumIntegerDigits = IntegerDigits //小数点前保留几位 68 | nf.minimumFractionDigits = FractionDigits // 小数点后保留几位 69 | return nf.format(d) 70 | } 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/DateUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils 2 | 3 | import kotlin.math.floor 4 | 5 | class DateUtil { 6 | companion object { 7 | fun getTime(time: Long): String { 8 | val days: Double = (time / 1000 / 60 / 60 / 24).toDouble() 9 | val daysRound = floor(days) 10 | val hours = time / 1000 / 60 / 60 - 24 * daysRound 11 | val hoursRound = floor(hours) 12 | val minutes = time / 1000 / 60 - 24 * 60 * daysRound - 60 * hoursRound 13 | val minutesRound = floor(minutes) 14 | val seconds = time / 1000 - 24 * 60 * 60 * daysRound - 60 * 60 * hoursRound - 60 * minutesRound 15 | val secondsRound = floor(seconds) 16 | val timeRound = "${hoursRound.toString().split(".")[0]}:${minutesRound.toString().split(".")[0]}:${secondsRound.toString().split(".")[0]}" 17 | return timeRound 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/DownloadUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils 2 | 3 | 4 | import okhttp3.* 5 | import java.io.File 6 | import java.io.FileOutputStream 7 | import java.io.IOException 8 | import java.io.InputStream 9 | import java.util.concurrent.TimeUnit 10 | 11 | 12 | object DownloadUtil { 13 | private var okHttpClient = OkHttpClient().newBuilder().connectTimeout(60000, TimeUnit.MILLISECONDS) 14 | .readTimeout(60000, TimeUnit.MILLISECONDS) 15 | private val logger by logger() 16 | /** 17 | * @param url 下载连接 18 | * @param saveDir 储存下载文件的SDCard目录 19 | * @param listener 下载监听 20 | */ 21 | fun download(url: String, saveDir: String, listener: OnDownloadListener) { 22 | // val host = Config.proxy.host 23 | // val port = Config.proxy.port 24 | 25 | 26 | val request: Request = Request.Builder().url(url).build() 27 | // val client = if (host.isBlank() || port == -1){ 28 | // okHttpClient.build() 29 | // }else{ 30 | // val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port)) 31 | // okHttpClient.proxy(proxy).build() 32 | // } 33 | okHttpClient.build().newCall(request).enqueue(object : Callback { 34 | 35 | override fun onFailure(call: Call, e: IOException) { 36 | listener.onDownloadFailed() 37 | } 38 | 39 | override fun onResponse(call: Call, response: Response) { 40 | var `is`: InputStream? = null 41 | val buf = ByteArray(2048) 42 | var len: Int 43 | var fos: FileOutputStream? = null 44 | // 储存下载文件的目录 45 | val savePath = isExistDir(saveDir) 46 | try { 47 | `is` = response.body?.byteStream() 48 | val total: Long = response.body?.contentLength() ?: 0L 49 | val file = File(savePath, getNameFromUrl(url)) 50 | fos = FileOutputStream(file) 51 | var sum: Long = 0 52 | if (`is` != null) { 53 | while (`is`.read(buf).also { len = it } != -1) { 54 | fos.write(buf, 0, len) 55 | sum += len.toLong() 56 | val progress = (sum * 1.0f / total * 100).toInt() 57 | // 下载中 58 | listener.onDownloading(progress) 59 | } 60 | } 61 | fos.flush() 62 | // 下载完成 63 | listener.onDownloadSuccess() 64 | } catch (e: Exception) { 65 | listener.onDownloadFailed() 66 | } finally { 67 | try { 68 | `is`?.close() 69 | } catch (_: IOException) { 70 | } 71 | try { 72 | fos?.close() 73 | } catch (_: IOException) { 74 | } 75 | } 76 | } 77 | }) 78 | } 79 | 80 | /** 81 | * @param saveDir 82 | * @return 83 | * @throws IOException 84 | * 判断下载目录是否存在 85 | */ 86 | @Throws(IOException::class) 87 | private fun isExistDir(saveDir: String): String { 88 | // 下载位置 89 | val downloadFile = File(saveDir) 90 | if (!downloadFile.mkdirs()) { 91 | downloadFile.createNewFile() 92 | } 93 | return downloadFile.absolutePath 94 | } 95 | 96 | /** 97 | * @param url 98 | * @return 99 | * 从下载连接中解析出文件名 100 | */ 101 | private fun getNameFromUrl(url: String): String { 102 | return url.substring(url.lastIndexOf("/") + 1) 103 | } 104 | 105 | interface OnDownloadListener { 106 | /** 107 | * 下载成功 108 | */ 109 | fun onDownloadSuccess() 110 | 111 | /** 112 | * @param progress 113 | * 下载进度 114 | */ 115 | fun onDownloading(progress: Int) 116 | 117 | /** 118 | * 下载失败 119 | */ 120 | fun onDownloadFailed() 121 | } 122 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/ImageUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils 2 | 3 | import com.hcyacg.initial.Config 4 | import okhttp3.Headers 5 | import okhttp3.OkHttpClient 6 | import okhttp3.Request 7 | import okhttp3.Response 8 | import org.apache.commons.codec.binary.Base64 9 | import java.awt.Dimension 10 | import java.awt.Graphics2D 11 | import java.awt.Image 12 | import java.awt.Rectangle 13 | import java.awt.image.BufferedImage 14 | import java.io.ByteArrayOutputStream 15 | import java.io.FileNotFoundException 16 | import java.io.IOException 17 | import java.io.InputStream 18 | import java.net.InetSocketAddress 19 | import java.net.Proxy 20 | import java.util.concurrent.TimeUnit 21 | import javax.imageio.ImageIO 22 | import kotlin.math.abs 23 | import kotlin.math.cos 24 | import kotlin.math.sin 25 | 26 | 27 | class ImageUtil { 28 | companion object { 29 | private val client = OkHttpClient().newBuilder().connectTimeout(60000, TimeUnit.MILLISECONDS).readTimeout(60000, 30 | TimeUnit.MILLISECONDS) 31 | private val headers = Headers.Builder().add("referer", "https://i.acgmx.com") 32 | .add( 33 | "user-agent", 34 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 Edg/84.0.522.59" 35 | ) 36 | private val logger by logger() 37 | 38 | 39 | /** 40 | * 将图片链接读取到内存转换成ByteArrayOutputStream,方便操作 41 | */ 42 | fun getImage(imageUri: String, type: CacheUtil.Type): ByteArrayOutputStream { 43 | val infoStream = ByteArrayOutputStream() 44 | val host = Config.proxy.host 45 | val port = Config.proxy.port 46 | 47 | try{ 48 | 49 | val temp = if(imageUri.indexOf("?") > -1){ 50 | imageUri.substring(0, imageUri.indexOf("?")).split("/").last() 51 | }else{ 52 | imageUri.split("/").last() 53 | } 54 | 55 | // println("temp: $temp") 56 | // println("imageUri: $imageUri") 57 | val request = Request.Builder().url(imageUri).headers(headers.build()).get().build() 58 | val response: Response = if (host.isBlank() || port == -1){ 59 | client.build().newCall(request).execute() 60 | }else{ 61 | val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port)) 62 | client.proxy(proxy).build().newCall(request).execute() 63 | } 64 | 65 | val `in` = response.body?.byteStream() 66 | 67 | 68 | val buffer = ByteArray(2048) 69 | var len: Int 70 | if (`in` != null) { 71 | while (`in`.read(buffer).also { len = it } > 0) { 72 | infoStream.write(buffer, 0, len) 73 | } 74 | } 75 | infoStream.write((Math.random() * 100).toInt() + 1) 76 | infoStream.close() 77 | 78 | if (Config.cache.enable){ 79 | CacheUtil.saveToLocal(infoStream,type,temp) 80 | } 81 | 82 | return infoStream 83 | }catch (e:Exception){ 84 | logger.warn { "${imageUri}获取失败,请检查网络" } 85 | e.printStackTrace() 86 | return infoStream 87 | } 88 | } 89 | 90 | /** 91 | * 将图片链接读取到内存转换成ByteArrayOutputStream,方便操作 92 | */ 93 | fun getVideo(videoUri: String): InputStream? { 94 | 95 | // val request: Request = Request.Builder().url(imageUri.replace("i.pximg.net","i.pixiv.cat")).get().build() 96 | val request: Request = Request.Builder().url(videoUri).get().build() 97 | return client.build().newCall(request).execute().body?.byteStream() 98 | } 99 | 100 | 101 | /** 102 | * 旋转角度 103 | * @param src 源图片 104 | * @param angel 角度 105 | * @return 目标图片 106 | */ 107 | fun rotate(src: Image, angel:Int): ByteArrayOutputStream 108 | { 109 | val srcWidth: Int = src.getWidth(null) 110 | val srcHeight : Int = src.getHeight (null) 111 | val rectDes : Rectangle = calcRotatedSize ( Rectangle ( Dimension ( 112 | srcWidth, srcHeight)), angel) 113 | 114 | 115 | var res: BufferedImage? = null 116 | res = BufferedImage (rectDes.width, rectDes.height,BufferedImage.TYPE_INT_RGB) 117 | val g2: Graphics2D = res.createGraphics () 118 | // transform(这里先平移、再旋转比较方便处理;绘图时会采用这些变化,绘图默认从画布的左上顶点开始绘画,源图片的左上顶点与画布左上顶点对齐,然后开始绘画,修改坐标原点后,绘画对应的画布起始点改变,起到平移的效果;然后旋转图片即可) 119 | 120 | //平移(原理修改坐标系原点,绘图起点变了,起到了平移的效果,如果作用于旋转,则为旋转中心点) 121 | g2.translate((rectDes.width - srcWidth) / 2, (rectDes.height - srcHeight) / 2) 122 | 123 | 124 | //旋转(原理transalte(dx,dy)->rotate(radians)->transalte(-dx,-dy);修改坐标系原点后,旋转90度,然后再还原坐标系原点为(0,0),但是整个坐标系已经旋转了相应的度数 ) 125 | g2.rotate(Math.toRadians(angel.toDouble()), srcWidth.toDouble() / 2, srcHeight.toDouble() / 2) 126 | 127 | // //先旋转(以目标区域中心点为旋转中心点,源图片左上顶点对准目标区域中心点,然后旋转) 128 | // g2.translate(rect_des.width/2,rect_des.height/ 2); 129 | // g2.rotate(Math.toRadians(angel)); 130 | // //再平移(原点恢复到源图的左上顶点处(现在的右上顶点处),否则只能画出1/4) 131 | // g2.translate(-src_width/2,-src_height/2); 132 | 133 | 134 | g2.drawImage(src, null, null) 135 | return imageToBytes(res,"PNG") 136 | } 137 | 138 | 139 | /** 140 | * 转换BufferedImage 数据为byte数组 141 | * 142 | * Image对象 143 | * @param format 144 | * image格式字符串.如"gif","png" 145 | * @return byte数组 146 | */ 147 | private fun imageToBytes(bImage:BufferedImage, format:String):ByteArrayOutputStream { 148 | val out = ByteArrayOutputStream() 149 | 150 | try { 151 | ImageIO.write(bImage, format, out) 152 | } catch (e: IOException) { 153 | e.printStackTrace() 154 | } 155 | return out 156 | } 157 | 158 | /** 159 | * 计算转换后目标矩形的宽高 160 | * @param src 源矩形 161 | * @param angel 角度 162 | * @return 目标矩形 163 | */ 164 | private fun calcRotatedSize(src: Rectangle, angel: Int): Rectangle { 165 | val cos = abs(cos(Math.toRadians(angel.toDouble()))) 166 | val sin = abs(sin(Math.toRadians(angel.toDouble()))) 167 | val desWidth = (src.width * cos).toInt() + (src.height * sin).toInt() 168 | val desHeight = (src.height * cos).toInt() + (src.width * sin).toInt() 169 | return Rectangle(Dimension(desWidth, desHeight)) 170 | } 171 | 172 | fun generateImage(imgData: String): ByteArray? { // 对字节数组字符串进行Base64解码并生成图片 173 | var file = imgData 174 | try { 175 | // Base64解码 176 | if (file.contains("data:")) { 177 | val start: Int = file.indexOf(",") 178 | file = file.substring(start + 1) 179 | } 180 | 181 | file = file.replace("\r|\n", "") 182 | file = file.trim() 183 | return Base64.decodeBase64(file) 184 | } catch (e: FileNotFoundException) { 185 | e.printStackTrace() 186 | return null 187 | } catch (e: IOException) { 188 | e.printStackTrace() 189 | return null 190 | } 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/LogUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils 2 | 3 | import io.github.oshai.kotlinlogging.KLogger 4 | import io.github.oshai.kotlinlogging.KotlinLogging 5 | import kotlinx.serialization.json.Json 6 | import kotlin.reflect.full.companionObject 7 | 8 | fun T.logger(): Lazy { 9 | // 使logger的名字始终和最外层的类一致,即使是在companion object中初始化属性 10 | val ofClass = this.javaClass 11 | val clazz = ofClass.enclosingClass?.takeIf { 12 | ofClass.enclosingClass.kotlin.companionObject?.java == ofClass 13 | } ?: ofClass 14 | 15 | return lazy { KotlinLogging.logger(clazz.name) } 16 | } 17 | 18 | 19 | fun T.json(): Lazy { 20 | return lazy { 21 | Json { 22 | prettyPrint = true 23 | ignoreUnknownKeys = true 24 | encodeDefaults = false 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/RequestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils 2 | 3 | import com.hcyacg.initial.Config 4 | import kotlinx.serialization.json.Json 5 | import kotlinx.serialization.json.JsonElement 6 | 7 | import okhttp3.* 8 | import java.net.InetSocketAddress 9 | import java.net.Proxy 10 | import java.util.Collections 11 | import java.util.concurrent.TimeUnit 12 | 13 | /** 14 | * http请求 15 | */ 16 | class RequestUtil { 17 | companion object { 18 | private val client = OkHttpClient().newBuilder().connectTimeout(60000, TimeUnit.MILLISECONDS) 19 | .readTimeout(60000, TimeUnit.MILLISECONDS).protocols(Collections.singletonList(Protocol.HTTP_1_1)) 20 | private var response: Response? = null 21 | 22 | fun request( 23 | method: Method, 24 | uri: String, 25 | body: RequestBody?, 26 | headers: Headers 27 | ): JsonElement? { 28 | 29 | /** 30 | * 进行请求转发 31 | */ 32 | when (method) { 33 | Method.GET -> { 34 | return httpObject(Request.Builder().url(uri).headers(headers).get().build()) 35 | } 36 | Method.POST -> { 37 | return body?.let { Request.Builder().url(uri).headers(headers).post(it).build() } 38 | ?.let { httpObject(it) } 39 | } 40 | Method.PUT -> { 41 | return body?.let { Request.Builder().url(uri).headers(headers).put(it).build() } 42 | ?.let { httpObject(it) } 43 | } 44 | Method.DEL -> { 45 | return httpObject(Request.Builder().url(uri).headers(headers).delete(body).build()) 46 | } 47 | } 48 | } 49 | 50 | 51 | /** 52 | * 发送http请求,返回数据(其中根据proxy是否配置加入代理机制) 53 | */ 54 | private fun httpObject(request: Request): JsonElement? { 55 | val host = Config.proxy.host 56 | val port = Config.proxy.port 57 | 58 | 59 | response = if (host.isBlank() || port == -1) { 60 | client.build().newCall(request).execute() 61 | } else { 62 | val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port)) 63 | client.proxy(proxy).build().newCall(request).execute() 64 | } 65 | 66 | 67 | if (response!!.isSuccessful) { 68 | return response!!.body?.string().toString().let { 69 | Json.parseToJsonElement(it) 70 | } 71 | } 72 | 73 | response!!.close() 74 | return null 75 | } 76 | 77 | /** 78 | * http的请求方式 79 | */ 80 | enum class Method { 81 | GET, POST, PUT, DEL 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/utils/ZipUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.utils 2 | 3 | import java.io.* 4 | import java.util.zip.ZipEntry 5 | import java.util.zip.ZipFile 6 | import java.util.zip.ZipOutputStream 7 | 8 | 9 | object ZipUtil { 10 | // fun testZipFile() { 11 | // val f1 = File("./build/ziptest/中文.txt") 12 | // createFile(f1.path) 13 | // val f2 = File("./build/ziptest/2.txt") 14 | // createFile(f2.path) 15 | // val f3 = File("./build/ziptest/3.txt") 16 | // createFile(f3.path) 17 | // 18 | // zip(listOf(f1, f2, f3), "./build/ziptest.zip") 19 | // 20 | // val res = "./build/zipresult" 21 | // unzip("./build/ziptest.zip", res) 22 | // 23 | // } 24 | 25 | 26 | fun unzip(zipFile: String, descDir: String) { 27 | val buffer = ByteArray(1024) 28 | var outputStream: OutputStream? = null 29 | var inputStream: InputStream? = null 30 | try { 31 | val zf = ZipFile(zipFile) 32 | val entries = zf.entries() 33 | while (entries.hasMoreElements()) { 34 | val zipEntry: ZipEntry = entries.nextElement() as ZipEntry 35 | val zipEntryName: String = zipEntry.name 36 | 37 | inputStream = zf.getInputStream(zipEntry) 38 | val descFilePath: String = descDir + File.separator + zipEntryName 39 | val descFile: File = createFile(descFilePath) 40 | outputStream = FileOutputStream(descFile) 41 | 42 | var len: Int 43 | while (inputStream.read(buffer).also { len = it } > 0) { 44 | outputStream.write(buffer, 0, len) 45 | } 46 | inputStream.close() 47 | outputStream.close() 48 | } 49 | } finally { 50 | inputStream?.close() 51 | outputStream?.close() 52 | } 53 | } 54 | 55 | private fun createFile(filePath: String): File { 56 | val file = File(filePath) 57 | val parentFile = file.parentFile!! 58 | if (!parentFile.exists()) { 59 | parentFile.mkdirs() 60 | } 61 | if (!file.exists()) { 62 | file.createNewFile() 63 | } 64 | return file 65 | } 66 | 67 | fun zip(files: List, zipFilePath: String) { 68 | if (files.isEmpty()) return 69 | 70 | val zipFile = createFile(zipFilePath) 71 | val buffer = ByteArray(1024) 72 | var zipOutputStream: ZipOutputStream? = null 73 | var inputStream: FileInputStream? = null 74 | try { 75 | zipOutputStream = ZipOutputStream(FileOutputStream(zipFile)) 76 | for (file in files) { 77 | if (!file.exists()) continue 78 | zipOutputStream.putNextEntry(ZipEntry(file.name)) 79 | inputStream = FileInputStream(file) 80 | var len: Int 81 | while (inputStream.read(buffer).also { len = it } > 0) { 82 | zipOutputStream.write(buffer, 0, len) 83 | } 84 | zipOutputStream.closeEntry() 85 | } 86 | } finally { 87 | inputStream?.close() 88 | zipOutputStream?.close() 89 | } 90 | } 91 | 92 | fun zipByFolder(fileDir: String, zipFilePath: String) { 93 | val folder = File(fileDir) 94 | if (folder.exists() && folder.isDirectory) { 95 | val files = folder.listFiles() 96 | val filesList: List = files.toList() 97 | zip(filesList, zipFilePath) 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin: -------------------------------------------------------------------------------- 1 | com.hcyacg.Pixiv --------------------------------------------------------------------------------