├── .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 | [](https://mirai.mamoe.net/topic/461)
4 | 
5 | 
6 | [](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
--------------------------------------------------------------------------------