├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ ├── config.yml
│ └── feature.md
├── pull_request_template.md
└── workflows
│ ├── dev.yml
│ ├── publish.yml
│ └── snapshot.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle.kts
├── buildSrc
├── LICENSE
├── README.md
├── build.gradle.kts
└── src
│ └── main
│ ├── groovy
│ └── moe
│ │ └── karla
│ │ └── maven
│ │ └── publishing
│ │ ├── MavenPublishingExtension.groovy
│ │ ├── MavenPublishingPlugin.groovy
│ │ └── PublishingStubsSetupPlugin.groovy
│ ├── java
│ └── moe
│ │ └── karla
│ │ └── maven
│ │ └── publishing
│ │ └── advtask
│ │ └── UploadToMavenCentral.java
│ └── kotlin
│ ├── Helper.kt
│ └── PublicationHelper.kt
├── docs
├── UserManual.md
├── configuration.md
├── contributing
│ └── README.md
├── dev
│ ├── README.md
│ └── progress.md
└── install
│ ├── MCL.md
│ ├── MCLOverflow.md
│ ├── MCLScript.md
│ ├── README.md
│ ├── Raw.md
│ ├── install_overflow_mcl.cmd
│ └── install_overflow_mcl.sh
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── overflow-core-all
└── build.gradle.kts
├── overflow-core-api
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── top
│ └── mrxiaom
│ └── overflow
│ ├── BotBuilder.kt
│ ├── OverflowAPI.kt
│ ├── action
│ └── ActionContext.kt
│ ├── contact
│ ├── RemoteBot.kt
│ ├── RemoteGroup.kt
│ ├── RemoteUser.kt
│ └── Updatable.kt
│ ├── event
│ ├── LegacyGuildMessageEvent.kt
│ ├── MemberEssenceNoticeEvent.kt
│ ├── MessageReactionEvent.kt
│ └── UnsolvedOnebotEvent.kt
│ ├── message
│ └── data
│ │ ├── ContactRecommend.kt
│ │ ├── FileMessageWithUrl.kt
│ │ ├── InlineKeyboard.kt
│ │ ├── Location.kt
│ │ └── Markdown.kt
│ └── spi
│ ├── ExtendedMessageSerializerService.kt
│ ├── FileService.kt
│ └── MediaURLService.kt
├── overflow-core
├── LICENSE
├── README.md
├── build.gradle.kts
├── runConsole.run.xml
└── src
│ ├── main
│ ├── kotlin
│ │ ├── cn
│ │ │ └── evolvefield
│ │ │ │ └── onebot
│ │ │ │ ├── client
│ │ │ │ ├── config
│ │ │ │ │ └── BotConfig.kt
│ │ │ │ ├── connection
│ │ │ │ │ ├── ConnectFactory.kt
│ │ │ │ │ ├── IAdapter.kt
│ │ │ │ │ ├── WSClient.kt
│ │ │ │ │ ├── WSServer.kt
│ │ │ │ │ └── platform-connection.kt
│ │ │ │ ├── core
│ │ │ │ │ └── Bot.kt
│ │ │ │ ├── handler
│ │ │ │ │ ├── ActionHandler.kt
│ │ │ │ │ ├── EventBus.kt
│ │ │ │ │ ├── EventHandler.kt
│ │ │ │ │ └── EventHolder.kt
│ │ │ │ └── util
│ │ │ │ │ ├── ActionSendRequest.kt
│ │ │ │ │ ├── Exceptions.kt
│ │ │ │ │ ├── Files.kt
│ │ │ │ │ └── ListenerUtils.kt
│ │ │ │ └── sdk
│ │ │ │ ├── action
│ │ │ │ ├── ActionPath.kt
│ │ │ │ └── ActionResults.kt
│ │ │ │ ├── entity
│ │ │ │ ├── Anonymous.kt
│ │ │ │ ├── File.kt
│ │ │ │ ├── GroupSender.kt
│ │ │ │ ├── MsgId.kt
│ │ │ │ ├── PrivateSender.kt
│ │ │ │ └── Sender.kt
│ │ │ │ ├── enums
│ │ │ │ └── ActionPathEnum.kt
│ │ │ │ ├── event
│ │ │ │ ├── Event.kt
│ │ │ │ ├── UnsolvedEvent.kt
│ │ │ │ ├── message
│ │ │ │ │ ├── GroupMessageEvent.kt
│ │ │ │ │ ├── MessageEvent.kt
│ │ │ │ │ └── PrivateMessageEvent.kt
│ │ │ │ ├── meta
│ │ │ │ │ ├── HeartbeatMetaEvent.kt
│ │ │ │ │ ├── LifecycleMetaEvent.kt
│ │ │ │ │ └── MetaEvent.kt
│ │ │ │ ├── notice
│ │ │ │ │ ├── NoticeEvent.kt
│ │ │ │ │ ├── NotifyNoticeEvent.kt
│ │ │ │ │ ├── friend
│ │ │ │ │ │ ├── FriendAddNoticeEvent.kt
│ │ │ │ │ │ └── PrivateMsgDeleteNoticeEvent.kt
│ │ │ │ │ ├── group
│ │ │ │ │ │ ├── GroupAdminNoticeEvent.kt
│ │ │ │ │ │ ├── GroupBanNoticeEvent.kt
│ │ │ │ │ │ ├── GroupCardChangeNoticeEvent.kt
│ │ │ │ │ │ ├── GroupDecreaseNoticeEvent.kt
│ │ │ │ │ │ ├── GroupEssenceNoticeEvent.kt
│ │ │ │ │ │ ├── GroupHonorChangeNoticeEvent.kt
│ │ │ │ │ │ ├── GroupIncreaseNoticeEvent.kt
│ │ │ │ │ │ ├── GroupLuckyKingNoticeEvent.kt
│ │ │ │ │ │ ├── GroupMsgDeleteNoticeEvent.kt
│ │ │ │ │ │ ├── GroupNameChangeNoticeEvent.kt
│ │ │ │ │ │ └── GroupUploadNoticeEvent.kt
│ │ │ │ │ └── misc
│ │ │ │ │ │ ├── GroupMsgEmojiLikeNotice.kt
│ │ │ │ │ │ ├── GroupReactionNotice.kt
│ │ │ │ │ │ └── ReceiveOfflineFilesNoticeEvent.kt
│ │ │ │ └── request
│ │ │ │ │ ├── FriendAddRequestEvent.kt
│ │ │ │ │ ├── GroupAddRequestEvent.kt
│ │ │ │ │ └── RequestEvent.kt
│ │ │ │ ├── response
│ │ │ │ ├── contact
│ │ │ │ │ ├── FriendInfoResp.kt
│ │ │ │ │ ├── LoginInfoResp.kt
│ │ │ │ │ └── StrangerInfoResp.kt
│ │ │ │ ├── ext
│ │ │ │ │ ├── CreateGroupFileFolderResp.kt
│ │ │ │ │ ├── GetFileResp.kt
│ │ │ │ │ ├── SetGroupReactionResp.kt
│ │ │ │ │ └── UploadGroupFileResp.kt
│ │ │ │ ├── group
│ │ │ │ │ ├── EssenceMsgResp.kt
│ │ │ │ │ ├── ForwardMsgResp.kt
│ │ │ │ │ ├── GetHistoryMsgResp.kt
│ │ │ │ │ ├── GetMsgResp.kt
│ │ │ │ │ ├── GroupAtAllRemainResp.kt
│ │ │ │ │ ├── GroupDataResp.kt
│ │ │ │ │ ├── GroupFileUrlResp.kt
│ │ │ │ │ ├── GroupFilesResp.kt
│ │ │ │ │ ├── GroupHonorInfoResp.kt
│ │ │ │ │ ├── GroupInfoResp.kt
│ │ │ │ │ ├── GroupMemberInfoResp.kt
│ │ │ │ │ └── GroupNoticeResp.kt
│ │ │ │ └── misc
│ │ │ │ │ ├── CSRFTokenResp.kt
│ │ │ │ │ ├── ClientsResp.kt
│ │ │ │ │ ├── CookiesResp.kt
│ │ │ │ │ └── CredentialsResp.kt
│ │ │ │ └── util
│ │ │ │ ├── CQCode.kt
│ │ │ │ ├── JsonDeserializerKt.kt
│ │ │ │ ├── JsonHelper.kt
│ │ │ │ └── json
│ │ │ │ ├── ClientsAdapter.kt
│ │ │ │ ├── ForwardMsgAdapter.kt
│ │ │ │ ├── GroupFilesAdapter.kt
│ │ │ │ ├── MessageEventAdapter.kt
│ │ │ │ └── MsgAdapter.kt
│ │ ├── net
│ │ │ └── mamoe
│ │ │ │ └── mirai
│ │ │ │ └── internal
│ │ │ │ ├── AbstractBot.kt
│ │ │ │ ├── QQAndroidBot.kt
│ │ │ │ ├── event
│ │ │ │ ├── EventChannelImpl.kt
│ │ │ │ ├── EventChannelToEventDispatcherAdapter.kt
│ │ │ │ ├── EventListeners.kt
│ │ │ │ ├── GlobalEventChannelProviderImpl.kt
│ │ │ │ ├── SafeListener.kt
│ │ │ │ └── package.kt
│ │ │ │ ├── message
│ │ │ │ ├── RefinableMessage.kt
│ │ │ │ └── data
│ │ │ │ │ └── MarketFaceImpl.kt
│ │ │ │ ├── network
│ │ │ │ ├── QQAndroidClient.kt
│ │ │ │ ├── components
│ │ │ │ │ ├── EventDispatcher.kt
│ │ │ │ │ └── SsoProcessor.kt
│ │ │ │ ├── keys.kt
│ │ │ │ └── protocol
│ │ │ │ │ └── data
│ │ │ │ │ └── proto
│ │ │ │ │ └── Msg.kt
│ │ │ │ ├── spi
│ │ │ │ └── EncryptService.kt
│ │ │ │ └── utils
│ │ │ │ └── MiraiProtocolInternal.kt
│ │ └── top
│ │ │ └── mrxiaom
│ │ │ └── overflow
│ │ │ └── internal
│ │ │ ├── BotFactoryImpl.kt
│ │ │ ├── Config.kt
│ │ │ ├── Overflow.kt
│ │ │ ├── cache
│ │ │ └── MessageCache.kt
│ │ │ ├── contact
│ │ │ ├── BotWrapper.kt
│ │ │ ├── FriendWrapper.kt
│ │ │ ├── GroupWrapper.kt
│ │ │ ├── MemberWrapper.kt
│ │ │ ├── OtherClientWrapper.kt
│ │ │ ├── StrangerWrapper.kt
│ │ │ └── data
│ │ │ │ ├── AnnouncementsWrapper.kt
│ │ │ │ ├── EssencesWrapper.kt
│ │ │ │ ├── FallbackFriendGroup.kt
│ │ │ │ ├── FallbackFriendGroups.kt
│ │ │ │ ├── GroupActiveProtocol.kt
│ │ │ │ ├── GroupActiveWrapper.kt
│ │ │ │ ├── GroupSettingsWrapper.kt
│ │ │ │ ├── MemberActiveWrapper.kt
│ │ │ │ └── RemoteFilesWrapper.kt
│ │ │ ├── data
│ │ │ ├── ContactInfoImpl.kt
│ │ │ └── UserProfileImpl.kt
│ │ │ ├── listener
│ │ │ ├── bot.kt
│ │ │ ├── friend.kt
│ │ │ └── group.kt
│ │ │ ├── message
│ │ │ ├── OnebotMessages.kt
│ │ │ └── data
│ │ │ │ ├── MessageSourceImpl.kt
│ │ │ │ ├── UnknownMessage.kt
│ │ │ │ ├── WrappedAudio.kt
│ │ │ │ ├── WrappedFileMessage.kt
│ │ │ │ ├── WrappedImageProtocol.kt
│ │ │ │ ├── WrappedMusicShare.kt
│ │ │ │ └── WrappedVideo.kt
│ │ │ ├── plugin
│ │ │ ├── BuiltInCommands.kt
│ │ │ └── OverflowCoreAsPlugin.kt
│ │ │ └── utils
│ │ │ ├── Base64FileService.kt
│ │ │ ├── BotUtils.kt
│ │ │ ├── LoggerUtils.kt
│ │ │ ├── RequestManager.kt
│ │ │ ├── ResourceUtils.kt
│ │ │ └── TransformerUtils.kt
│ └── resources
│ │ └── META-INF
│ │ └── services
│ │ ├── net.mamoe.mirai.IMirai
│ │ ├── net.mamoe.mirai.event.InternalGlobalEventChannelProvider
│ │ ├── net.mamoe.mirai.message.data.InternalImageProtocol
│ │ ├── net.mamoe.mirai.utils.InternalProtocolDataExchange
│ │ └── top.mrxiaom.overflow.spi.FileService
│ └── test
│ ├── kotlin
│ ├── ForwardMessageTest.kt
│ └── RunConsole.kt
│ └── resources
│ └── logback.xml
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: ["https://afdian.com/a/mrxiaom"]
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug 报告
2 | description: 提交一个 bug (若反馈漏洞不选择该模板,Issue 将被关闭)
3 | labels:
4 | - "question"
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | 感谢你来到这里
11 |
12 | 在反馈前, 请确认你已经做了下面这些事情
13 | - 阅读过 [「提问的智慧」](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
14 | - 阅读过 [「如何有效地报告 Bug」](https://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html)
15 | - 对照过 [Releases](https://github.com/MrXiaoM/Overflow/releases),相关问题未在近期更新中解决
16 | - 搜索了已有的 [issues](https://github.com/MrXiaoM/Overflow/issues?q=is%3Aissue) 列表中有没相关的信息
17 | - 阅读了 [Overflow 的相关文档](https://github.com/MrXiaoM/Overflow/tree/main/docs)
18 | - 已做过初步调查,确定该问题来自 Overflow,而不是来自 mirai、业务逻辑、插件,或 LLOnebot 等 Onebot 实现。
19 |
20 | - type: textarea
21 | id: issue-description
22 | attributes:
23 | label: 问题描述
24 | description: 在此详细描述你遇到的问题
25 | validations:
26 | required: true
27 |
28 | - type: textarea
29 | id: reproduce
30 | attributes:
31 | label: 复现
32 | description: 在这里简略说明如何让这个问题再次发生
33 | placeholder: |
34 | 在这里简略说明如何让这个问题再次发生
35 | 可使用 1. 2. 3. 的列表格式,或其他任意恰当的格式
36 | 如果你不确定如何复现, 请尽量描述发生当时的情景
37 |
38 | 建议提供相关代码
39 | validations:
40 | required: true
41 |
42 | - type: input
43 | id: version-overflow
44 | attributes:
45 | label: Overflow 版本
46 | description: |
47 | 开发版本的版本号为 short commit hash,即启动信息显示的 x.x.x.xxx- 后面的7位字符,示例: fffffff
48 | 正式版本的版本号为 x.x.x,如 1.0.0
49 | validations:
50 | required: true
51 |
52 | - type: textarea
53 | id: version-others
54 | attributes:
55 | label: 其他组件版本
56 | description: |
57 | 如 mirai-console 版本 (如果有)
58 | 如 LLOnebot 等 Onebot 实现的版本
59 |
60 | - type: textarea
61 | id: journal-system
62 | attributes:
63 | label: 系统日志
64 | description: |
65 | 请提供全面的相关日志. 请不要截图.
66 | 如果日志过大, 可以在 `补充信息` 上传文件.
67 | 如果你遇到的问题是 "消息收不到", "收消息报错" 等与协议有关的问题, 请一定提交日志. 若不提交日志, 你的问题可能会被直接关闭.
68 | render: 'text'
69 | validations:
70 | required: false
71 |
72 | - type: textarea
73 | id: journal-network
74 | attributes:
75 | label: 网络日志
76 | description: |
77 | 请提供全面的网络日志(如果需要). 请不要截图.
78 | 网络日志一般在 `logs/onebot/*.log`.
79 | 如果日志过大, 可以在 `补充信息` 上传文件.
80 | render: text
81 | validations:
82 | required: true
83 |
84 | - type: textarea
85 | id: additional
86 | attributes:
87 | label: 补充信息
88 | description: 如有必要,你可以在下文继续添加其他信息
89 |
90 | - type: markdown
91 | attributes:
92 | value: |
93 | ----
94 |
95 | 在发出 issue 前, 请确认
96 |
97 | - 全部信息已经填写完毕, 特别是「其他组件版本」、「**网络日志**」
98 | - 报告中没有令人反感的语言
99 | - 「复现」的描述是否足够详细准确
100 | - 若内容有你认为的敏感数据,请使用星号代替,但不要过度替换敏感数据造成阅读障碍
101 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 特性申请
3 | about: 申请 Overflow 添加新的特性
4 | title: ''
5 | labels: 'feature'
6 | assignees: ''
7 |
8 | ---
9 |
10 |
17 |
18 |
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | **在提出此拉取请求时,我确认了以下几点(请复选框):**
2 |
3 | - [ ] 我已阅读并理解[贡献文档](https://github.com/MrXiaoM/Overflow/tree/main/docs/contributing)。
4 | - [ ] 我已检查没有与此请求重复的 Pull Requests。
5 | - [ ] 我已经考虑过,并确认这份呈件对其他人很有价值。
6 | - [ ] 我接受此提交可能不会被使用,并根据维护人员的意愿关闭 Pull Requests。
7 |
8 |
9 |
10 |
11 |
12 | **填写PR内容:**
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.github/workflows/dev.yml:
--------------------------------------------------------------------------------
1 | name: Build Snapshots
2 | on:
3 | push:
4 | branches: [ "main" ]
5 | paths-ignore:
6 | - '*.md'
7 | pull_request:
8 | branches: [ "main" ]
9 | paths-ignore:
10 | - '*.md'
11 | permissions:
12 | contents: write
13 | jobs:
14 | build:
15 | permissions:
16 | contents: write
17 | packages: write
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v3
22 | with:
23 | fetch-depth: 0
24 | - name: Validate Gradle wrapper
25 | uses: gradle/actions/wrapper-validation@v4
26 | - name: Setup Gradle
27 | uses: gradle/actions/setup-gradle@v4
28 | - name: Setup Java 11
29 | uses: actions/setup-java@v3
30 | with:
31 | distribution: temurin
32 | java-version: 11
33 | - name: Get short SHA
34 | run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
35 | - name: Build Package
36 | run: ./gradlew :overflow-core-all:shadowJar
37 | env:
38 | # 加上 VERSION_OVERRIDE 将会设为 -SNAPSHOT 版本
39 | ORG_GRADLE_PROJECT_VERSION_OVERRIDE: ${{ env.SHORT_SHA }}
40 | - name: Upload Artifacts
41 | uses: actions/upload-artifact@v4
42 | with:
43 | name: overflow-core-all-${{ env.SHORT_SHA }}
44 | path: overflow-core-all/build/libs/*-all.jar
45 | if-no-files-found: error
46 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to Maven Central
2 | on:
3 | workflow_dispatch:
4 |
5 | jobs:
6 | publish:
7 | runs-on: ubuntu-latest
8 | if: github.repository == 'MrXiaoM/Overflow'
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v3
12 | with:
13 | fetch-depth: 0
14 | - name: Validate Gradle wrapper
15 | uses: gradle/actions/wrapper-validation@v4
16 | - name: Setup Gradle
17 | uses: gradle/actions/setup-gradle@v4
18 | - name: Set up Java
19 | uses: actions/setup-java@v3
20 | with:
21 | java-version: '11'
22 | distribution: 'adopt'
23 | - name: Publish Package to Stage Repository
24 | run: ./gradlew cleanMavenPublishingStage build publishAllPublicationsToMavenStageRepository
25 | env:
26 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
27 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
28 | - name: Upload Artifacts
29 | uses: actions/upload-artifact@v4
30 | with:
31 | name: tmp
32 | path: build/tmp/*
33 | if-no-files-found: error
34 | - name: Upload to Central Portal
35 | run: ./gradlew publishToMavenCentral
36 | env:
37 | MAVEN_PUBLISH_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PUBLISH_ACCOUNT }}
38 |
--------------------------------------------------------------------------------
/.github/workflows/snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Publish to Central Snapshots
2 | on:
3 | push:
4 | branches: [ "main" ]
5 | paths-ignore:
6 | - '*.md'
7 |
8 | permissions:
9 | contents: write
10 | jobs:
11 | build:
12 | permissions:
13 | contents: write
14 | packages: write
15 | runs-on: ubuntu-latest
16 | if: github.repository == 'MrXiaoM/Overflow'
17 | env:
18 | ORG_GRADLE_PROJECT_IS_SNAPSHOT: true
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v3
22 | with:
23 | fetch-depth: 0
24 | - name: Validate Gradle wrapper
25 | uses: gradle/actions/wrapper-validation@v4
26 | - name: Setup Gradle
27 | uses: gradle/actions/setup-gradle@v4
28 | - name: Setup Java 11
29 | uses: actions/setup-java@v3
30 | with:
31 | distribution: temurin
32 | java-version: 11
33 | # 检查是否存在 secrets,避免未设置 secrets 时运行发布
34 | - name: Check Secrets
35 | run: |
36 | echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
37 | echo "SECRETS_LENGTH=$(expr length "$SECRETS_TO_CHECK")" >> $GITHUB_ENV
38 | env:
39 | SECRETS_TO_CHECK: ${{ secrets.SIGNING_PRIVATE_KEY }}
40 | - name: Publish Package
41 | if: ${{ env.SECRETS_LENGTH > 0 }}
42 | run: ./gradlew build publishAllPublicationsToCentralSnapshotsRepository
43 | env:
44 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
45 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
46 | MAVEN_SNAPSHOTS_USERNAME: ${{ secrets.OSSRH_USERNAME }}
47 | MAVEN_SNAPSHOTS_TOKEN: ${{ secrets.OSSRH_TOKEN }}
48 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
Overflow
5 |
6 | 欢迎回到 mirai
7 |
8 | Overflow 是 mirai-core-api 的实现,对接 OneBot 11 标准,实现 mirai 的无缝迁移。
9 |
10 | 形象图由 [人间工作](https://www.pixiv.net/artworks/114225110) 绘制
11 |
12 | [](https://github.com/project-tRNA) [](https://github.com/MrXiaoM/Overflow/stargazers) [](https://github.com/mamoe/mirai) [](https://11.onebot.dev) [](https://mirai.mamoe.net/topic/2565)
13 |
14 |
15 | 当前 Overflow 基本稳定,欢迎各位使用。
16 |
17 | - **[用户手册: 快速开始](https://mirai.mrxiaom.top/docs/UserManual)**
18 | - **[开发文档](https://mirai.mrxiaom.top/docs/dev)**
19 | - **[开发进度/具体的接口支持情况](https://mirai.mrxiaom.top/docs/dev/progress)**
20 |
21 | ## 兼容性说明
22 |
23 | Overflow 支持且**仅支持**连接到大多数标准的 Onebot 或 go-cqhttp 协议,支持安装未使用 mirai 内部特性或 mirai 码的插件
24 | + [x] 使用正向(主动)或反向(被动) WebSocket 连接
25 | + [x] 在连接时使用 token 鉴权
26 | + [x] 在代码中调用自定义的 action
27 | + [x] 在消息中使用 CQ 码 *测试中*
28 | + [x] 将 mirai 消息段序列化/反序列化为 json
29 | + [ ] 使用 MiraiCode (Mirai 码) 处理消息 *不支持*
30 |
31 | 如果你的 Overflow 与 Onebot 实现 (如 LLOnebot、NapCat、Gensokyo 等) 可部署且已部署在了同一文件系统,推荐安装附属插件 [LocalFileService](https://github.com/MrXiaoM/LocalFileService)。
32 |
33 | # 鸣谢
34 |
35 | 本项目使用了 [onebot-client](https://github.com/cnlimiter/onebot-client) 进行快速开发。
36 | 感谢该项目为本项目的发起者节约了大量的阅读文档与设计接口时间。
37 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_MEMBER")
2 | import org.ajoberstar.grgit.Grgit
3 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
4 | import moe.karla.maven.publishing.MavenPublishingExtension.PublishingType
5 |
6 | plugins {
7 | // kotlin("jvm") moved to `buildSrc/build.gradle.kts`
8 | kotlin("plugin.serialization") version prop("kotlin.version") apply false
9 | id("org.jetbrains.dokka") version "1.8.10" apply false
10 | id("com.github.johnrengelman.shadow") version "7.0.0" apply false
11 | id("com.github.gmazzo.buildconfig") version "3.1.0" apply false
12 | id("me.him188.kotlin-jvm-blocking-bridge") version "3.0.0-180.1" apply false
13 | id("org.ajoberstar.grgit") version "5.2.2" apply false
14 |
15 | id("moe.karla.maven-publishing")
16 | }
17 |
18 | Helper.proj = rootProject
19 | group = "top.mrxiaom.mirai"
20 |
21 | val overflowVersion = "1.0.5".ext("overflowVersion")
22 | val miraiVersion = "2.16.0".ext("miraiVersion")
23 |
24 | var commitHash = "local"
25 | var commitCount = 0
26 | if (File(rootProject.projectDir, ".git").exists()) {
27 | Grgit.open(mapOf("currentDir" to rootProject.projectDir)).use { repo ->
28 | commitHash = repo.head().abbreviatedId
29 | val log = repo.log()
30 | commitCount = log.size
31 | }
32 | }
33 | commitHash.ext("commitHash")
34 | commitCount.ext("commitCount")
35 | val commit =
36 | if (commitHash == "local") "9999-local"
37 | else "$commitCount-${commitHash.substring(0, 7)}"
38 |
39 | version = overflowVersion
40 |
41 | if (findProperty("IS_SNAPSHOT") == "true") {
42 | version = "$version.$commit-SNAPSHOT"
43 | }
44 | findProperty("OVERRIDE_VERSION")?.also { version = it }
45 |
46 | println("Mirai version: $miraiVersion")
47 | println("Overflow version: $overflowVersion")
48 | println("Commit: $commit")
49 | println("Version: $version")
50 |
51 | mavenPublishing {
52 | publishingType = PublishingType.AUTOMATIC
53 | url = "https://github.com/MrXiaoM/Overflow"
54 | }
55 |
56 | allprojects {
57 | group = rootProject.group
58 | version = rootProject.version
59 |
60 | val javaVersion = "1.8"
61 | tasks {
62 | withType {
63 | kotlinOptions.jvmTarget = javaVersion
64 | }
65 | withType {
66 | options.encoding = "UTF-8"
67 | sourceCompatibility = javaVersion
68 | targetCompatibility = javaVersion
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/buildSrc/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Karlatemp
4 | Copyright (c) 2025 MrXiaoM
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/buildSrc/README.md:
--------------------------------------------------------------------------------
1 | ## 构建逻辑
2 |
3 | 发布工具使用 [Karlatemp/maven-central-publish](https://github.com/Karlatemp/maven-central-publish) - MIT License
4 |
5 | 进行了一点修改
6 | + 不强制要求 `rootProject` 拥有 clean 任务
7 | + 移除 signing-setup 插件,自己配置签名就足够了
8 | + 添加 SNAPSHOT 仓库支持
9 | + 由于可能与 kotlin 插件冲突,将使用 `JavaExec` 隔离进程的发布任务改为直接执行,虽然有潜在的安全风险,但只要不乱装其它插件一般不会出事
10 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.util.*
2 |
3 | plugins {
4 | java
5 | `groovy-gradle-plugin`
6 | `kotlin-dsl`
7 | id("com.github.gmazzo.buildconfig") version "3.1.0"
8 | }
9 | val gradleProperties = rootDir.parentFile
10 | .resolve("gradle.properties")
11 | val prop = Properties().apply {
12 | gradleProperties.reader().use(::load)
13 | }
14 | java {
15 | sourceCompatibility = JavaVersion.VERSION_1_8
16 | targetCompatibility = JavaVersion.VERSION_1_8
17 | }
18 | buildConfig {
19 | className("BuildConstants")
20 | useKotlinOutput()
21 | buildConfigField("String", "PROPERTIES_PATH", "\"" + gradleProperties.absolutePath.replace('\\', '/') + "\"")
22 | buildConfigField("java.io.File", "PROPERTIES_FILE", "java.io.File(PROPERTIES_PATH)")
23 | }
24 | repositories {
25 | if (Locale.getDefault().country == "CN") {
26 | prop["mirror.repo"]?.also(::maven)
27 | }
28 | mavenCentral()
29 | gradlePluginPortal()
30 | }
31 |
32 | dependencies {
33 | api(gradleApi())
34 | api(gradleKotlinDsl())
35 | api("com.google.code.gson:gson:2.10.1")
36 |
37 | api("org.apache.httpcomponents:httpclient:4.5.13")
38 | api("org.apache.httpcomponents:httpmime:4.5.13")
39 |
40 | api("org.jetbrains.kotlin", "kotlin-gradle-plugin", prop["kotlin.version"].toString()) {
41 | exclude("org.jetbrains.kotlin", "kotlin-stdlib")
42 | exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
43 | exclude("org.jetbrains.kotlin", "kotlin-reflect")
44 | }
45 | }
46 |
47 | gradlePlugin {
48 | website.set("https://github.com/Karlatemp/maven-central-publish")
49 | vcsUrl.set("https://github.com/Karlatemp/maven-central-publish")
50 |
51 | testSourceSets(sourceSets.test.get())
52 |
53 | plugins {
54 | register("maven-publishing") {
55 | id = "moe.karla.maven-publishing"
56 | implementationClass = "moe.karla.maven.publishing.MavenPublishingPlugin"
57 |
58 | displayName = "Maven Central Publishing"
59 | description = "Publishing your software to Maven Central"
60 | tags.set(listOf("signing", "publishing"))
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/moe/karla/maven/publishing/MavenPublishingExtension.groovy:
--------------------------------------------------------------------------------
1 | package moe.karla.maven.publishing
2 |
3 | class MavenPublishingExtension {
4 |
5 | ///////////////////////////
6 | // BASIC
7 | ///////////////////////////
8 |
9 | /**
10 | * The URL of this project.
11 | *
12 | * Leave blank or null for auto guest.
13 | */
14 | public String url
15 |
16 | static enum PublishingType {
17 | /**
18 | * (default) a deployment will go through validation and, if it passes, automatically proceed to publish to Maven Central
19 | */
20 | AUTOMATIC,
21 | /**
22 | * a deployment will go through validation and require the user to manually publish it via the Portal UI
23 | */
24 | USER_MANAGED,
25 | }
26 |
27 | public PublishingType publishingType = PublishingType.AUTOMATIC
28 |
29 | /**
30 | * Generate sources jar and stub javadoc jar automatic
31 | */
32 | public boolean automaticSourcesAndJavadoc = true
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/moe/karla/maven/publishing/PublishingStubsSetupPlugin.groovy:
--------------------------------------------------------------------------------
1 | package moe.karla.maven.publishing
2 |
3 | import org.gradle.api.NamedDomainObjectProvider
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 | import org.gradle.api.UnknownDomainObjectException
7 | import org.gradle.api.artifacts.Configuration
8 | import org.gradle.api.attributes.Bundling
9 | import org.gradle.api.attributes.Category
10 | import org.gradle.api.attributes.DocsType
11 | import org.gradle.api.attributes.Usage
12 | import org.gradle.api.component.AdhocComponentWithVariants
13 | import org.gradle.api.plugins.JavaPluginExtension
14 | import org.gradle.jvm.tasks.Jar
15 |
16 | class PublishingStubsSetupPlugin implements Plugin {
17 | private static final String STUB_JAVADOC_CONFIGURATION_NAME = 'mavenPublishingDummyStubJavadoc'
18 |
19 | static NamedDomainObjectProvider createStubJavadocConfiguration(Project root) {
20 | def configurations = root.configurations
21 | try {
22 | return configurations.named(STUB_JAVADOC_CONFIGURATION_NAME)
23 | } catch (UnknownDomainObjectException ignored) {
24 | }
25 |
26 | root.apply plugin: 'java-base'
27 | def stubTask = root.tasks.register('createMavenPublishingDummyStubJavadoc', Jar.class) { Jar task ->
28 | task.group = 'publishing'
29 | task.archiveClassifier.set('javadoc')
30 | task.archiveBaseName.set('maven-publishing-stub')
31 | task.archiveVersion.set('')
32 | }
33 |
34 | return configurations.register(STUB_JAVADOC_CONFIGURATION_NAME) { Configuration configuration ->
35 | configuration.outgoing {
36 | artifact(stubTask)
37 | }
38 | configuration.attributes {
39 | attribute(Usage.USAGE_ATTRIBUTE, root.objects.named(Usage.class, "java-runtime"))
40 | attribute(Category.CATEGORY_ATTRIBUTE, root.objects.named(Category.class, "documentation"))
41 | attribute(Bundling.BUNDLING_ATTRIBUTE, root.objects.named(Bundling.class, "external"))
42 | attribute(DocsType.DOCS_TYPE_ATTRIBUTE, root.objects.named(DocsType.class, DocsType.JAVADOC))
43 | }
44 | }
45 | }
46 |
47 | @Override
48 | void apply(Project target) {
49 | def stubConfig = createStubJavadocConfiguration(target.rootProject)
50 |
51 |
52 | target.pluginManager.withPlugin('java') {
53 | // setup sourcesJar & javadocs
54 | def javaExt = target.extensions.findByName('java') as JavaPluginExtension
55 | javaExt.withSourcesJar()
56 |
57 | AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) target.components.findByName("java")
58 | javaComponent.addVariantsFromConfiguration(stubConfig.get()) {
59 | // dependencies for this variant are considered runtime dependencies
60 | it.mapToMavenScope("runtime")
61 | // and also optional dependencies, because we don't want them to leak
62 | it.mapToOptional()
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/moe/karla/maven/publishing/advtask/UploadToMavenCentral.java:
--------------------------------------------------------------------------------
1 | package moe.karla.maven.publishing.advtask;
2 |
3 | import org.apache.http.HttpResponse;
4 | import org.apache.http.client.methods.HttpPost;
5 | import org.apache.http.entity.mime.MultipartEntityBuilder;
6 | import org.apache.http.impl.client.CloseableHttpClient;
7 | import org.apache.http.impl.client.HttpClientBuilder;
8 | import org.apache.http.util.EntityUtils;
9 |
10 | import java.io.File;
11 | import java.io.InputStream;
12 | import java.net.URLEncoder;
13 | import java.nio.charset.StandardCharsets;
14 | import java.util.Base64;
15 |
16 | public class UploadToMavenCentral {
17 | private static byte[] getUserPassword() {
18 | String mavenPublishPassword = System.getenv("MAVEN_PUBLISH_PASSWORD");
19 | if (mavenPublishPassword == null || mavenPublishPassword.isEmpty()) {
20 | throw new RuntimeException("Missing MAVEN_PUBLISH_PASSWORD");
21 | }
22 | if (mavenPublishPassword.contains(":")) {
23 | return mavenPublishPassword.getBytes(StandardCharsets.UTF_8);
24 | }
25 |
26 | String mavenPublishUser = System.getenv("MAVEN_PUBLISH_USER");
27 | if (mavenPublishUser == null || mavenPublishUser.isEmpty()) {
28 | throw new RuntimeException("Missing MAVEN_PUBLISH_USER and MAVEN_PUBLISH_PASSWORD not defined with user part");
29 | }
30 | return (mavenPublishUser + ":" + mavenPublishPassword).getBytes(StandardCharsets.UTF_8);
31 | }
32 |
33 | public static void execute(String publishingName, String publishingType, File bundle) throws Throwable {
34 |
35 | CloseableHttpClient httpclient = HttpClientBuilder.create().build();
36 |
37 | String url = "https://central.sonatype.com/api/v1/publisher/upload?publishingType=" + publishingType + "&name=" + URLEncoder.encode(publishingName, StandardCharsets.UTF_8);
38 | HttpPost httpPost = new HttpPost(url);
39 | System.out.println("POST " + url);
40 | httpPost.addHeader("Authorization", "Bearer " + Base64.getEncoder().encodeToString(
41 | getUserPassword()
42 | ));
43 |
44 | httpPost.setEntity(
45 | MultipartEntityBuilder.create()
46 | .addBinaryBody("bundle", bundle)
47 | .build()
48 | );
49 |
50 | HttpResponse response = httpclient.execute(httpPost);
51 | int statusCode = response.getStatusLine().getStatusCode();
52 | if (statusCode / 100 != 2) {
53 | System.err.println("HTTP Post Exited with " + statusCode);
54 | if (response.getEntity() != null) {
55 | InputStream content = response.getEntity().getContent();
56 | if (content != null) {
57 | byte[] buffer = new byte[1024];
58 | while (true) {
59 | int len = content.read(buffer);
60 | if (len == -1) break;
61 |
62 | System.err.write(buffer, 0, len);
63 | }
64 | }
65 | }
66 | throw new Exception("HTTP " + statusCode);
67 | } else {
68 | String deploymentId = EntityUtils.toString(response.getEntity());
69 | System.out.println("========================================");
70 | System.out.println("UPLOAD SUCCESS");
71 | System.out.println("Deployment ID: " + deploymentId);
72 | System.out.println("========================================");
73 | }
74 | httpclient.close();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Helper.kt:
--------------------------------------------------------------------------------
1 | import buildSrc.BuildConstants
2 | import org.gradle.api.Project
3 | import org.gradle.kotlin.dsl.*
4 | import org.jetbrains.kotlin.gradle.dsl.*
5 | import java.util.*
6 |
7 | fun T.ext(name: String): T {
8 | return also { Helper.rootProject.extra[name] = it }
9 | }
10 |
11 | inline fun Project.extra(name: String): T? {
12 | return rootProject.extra[name].run {
13 | if (this is T) this else null
14 | }
15 | }
16 | object Helper {
17 | @Suppress("MemberVisibilityCanBePrivate")
18 | internal lateinit var proj: Project
19 | val rootProject: Project
20 | get() = proj
21 | }
22 |
23 | val prop = Properties().apply {
24 | BuildConstants.PROPERTIES_FILE
25 | .reader().use(::load)
26 | }
27 |
28 | fun prop(name: String): String {
29 | return prop[name]?.toString() ?: ""
30 | }
31 |
32 | fun Project.optInForAllSourceSets(qualifiedClassname: String) {
33 | kotlinSourceSets!!.all {
34 | languageSettings {
35 | optIn(qualifiedClassname)
36 | }
37 | }
38 | }
39 | inline fun Any?.safeAs(): T? {
40 | return this as? T
41 | }
42 | val Project.kotlinSourceSets get() = extensions.findByName("kotlin").safeAs()?.sourceSets
43 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/PublicationHelper.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.Action
2 | import org.gradle.api.Project
3 | import org.gradle.api.publish.PublishingExtension
4 | import org.gradle.api.publish.maven.MavenPom
5 | import org.gradle.api.publish.maven.MavenPublication
6 | import org.gradle.kotlin.dsl.apply
7 | import org.gradle.kotlin.dsl.configure
8 | import org.gradle.kotlin.dsl.create
9 | import org.gradle.kotlin.dsl.getByType
10 | import org.gradle.plugins.signing.SigningExtension
11 |
12 | fun Project.setupMavenCentralPublication(artifactsBlock: MavenPublication.() -> Unit) {
13 | apply(plugin = "signing")
14 | apply(plugin = "maven-publish")
15 |
16 | extensions.configure(PublishingExtension::class) {
17 | publications {
18 | create("maven") {
19 | from(components.getByName("kotlin"))
20 | groupId = rootProject.group.toString()
21 | artifactId = project.name
22 | version = rootProject.version.toString()
23 |
24 | artifactsBlock()
25 | pom(mavenPom(artifactId))
26 | }
27 | }
28 | }
29 | extensions.configure(SigningExtension::class) {
30 | val signingKey = findProperty("signingKey")?.toString()
31 | val signingPassword = findProperty("signingPassword")?.toString()
32 | if (signingKey != null && signingPassword != null) {
33 | useInMemoryPgpKeys(signingKey, signingPassword)
34 | sign(extensions.getByType(PublishingExtension::class).publications.getByName("maven"))
35 | } else {
36 | logger.warn("子模块 ${project.name} 未找到签名配置")
37 | }
38 | }
39 | }
40 | fun mavenPom(artifactId: String): Action = action {
41 | name.set(artifactId)
42 | description.set("One of the Overflow project modules")
43 | url.set("https://github.com/MrXiaoM/Overflow")
44 | licenses {
45 | license {
46 | name.set("AGPL-3.0")
47 | url.set("https://github.com/MrXiaoM/Overflow/blob/master/LICENSE")
48 | }
49 | }
50 | developers {
51 | developer {
52 | name.set("MrXiaoM")
53 | email.set("mrxiaom@qq.com")
54 | }
55 | }
56 | scm {
57 | url.set("https://github.com/MrXiaoM/Overflow")
58 | connection.set("scm:git:https://github.com/MrXiaoM/Overflow.git")
59 | developerConnection.set("scm:git:https://github.com/MrXiaoM/Overflow.git")
60 | }
61 | }
62 |
63 | inline fun action(
64 | crossinline block: T.() -> Unit
65 | ): Action = Action { block() }
66 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # 配置文件说明
2 |
3 | 此处为各个配置文件的相关注释
4 |
5 | ## overflow.json
6 |
7 | | 键 | 默认值 | 注释 |
8 | |--------------------------------|-----------------------|------------------------------------------------------------------------------------------------------|
9 | | `ws_host` | `ws://127.0.0.1:3001` | 正向WebSocket连接地址 |
10 | | `reversed_ws_port` | `-1` | 反向WebSocket服务器端口,`-1` 为不开启反向WS。开启反向WS将会禁用正向WS连接 |
11 | | `token` | | 鉴权令牌,留空则不进行鉴权 |
12 | | `no_platform` | `false` | 是否禁止 Overflow 调用需要账号凭证的网络接口,如获取分享Key、群活跃等。在**非QQ平台**或**无法获取账号凭证**的平台 (如 Gensokyo) 使用 Overflow 请开启该选项 |
13 | | `use_cq_code` | `false` | 发送消息时,是否使用 CQ 码格式 |
14 | | `retry_times` | `5` | 正向WS断开连接后重连尝试次数,设为 `-1` 禁用自动重连 |
15 | | `retry_wait_mills` | `5000` | 每次重连之间的间隔时间 (毫秒) |
16 | | `retry_rest_mills` | `60000` | 重连次数用完后,等待多长时间 (毫秒) 后,重置次数再进行重连。设为 `-1` 禁用此功能 |
17 | | `heartbeat_check_seconds` | `60` | WebSocket 心跳策略检查时间 (秒),设为 `0` 关闭心跳策略 |
18 | | `resource_cache` | - | 媒体消息(图片、语音、短视频)自动下载缓存配置,更详细的信息请见下方 |
19 | | `drop_events_before_connected` | `true` | 是否抛弃所有在Bot成功连接之前传入的事件。比如 go-cqhttp 会在连接之后,在 Overflow 获取到 Bot 信息之前,推送以前未收到的消息。如果开启此项,则丢弃这些消息 |
20 |
21 | ## resource_cache
22 |
23 | | 键 | 默认值 | 注释 |
24 | |-----------------------|---------|--------------------------|
25 | | `enabled` | `false` | 是否启用媒体消息自动下载缓存功能 |
26 | | `keep_duration_hours` | `168` | 媒体缓存文件保留时间,超时自动删除,默认`7天` |
27 |
--------------------------------------------------------------------------------
/docs/contributing/README.md:
--------------------------------------------------------------------------------
1 | # 贡献文档
2 |
3 | 本文为贡献者指南,Overflow 欢迎并感谢一切形式的贡献。
4 |
5 | 本仓库包含以下模块
6 |
7 | | 模块 | 说明 |
8 | |-------------------|-----------------|
9 | | onebot | Onebot SDK 和客户端 |
10 | | overflow-core-api | overflow 核心 api |
11 | | overflow-core | overflow 核心实现 |
12 | | overflow-core-all | 上述模块的集合,用于生产环境 |
13 |
14 | 以下为开发者须知,阅读以更快地进入开发状态,避免错误。
15 |
16 | ## JDK要求
17 |
18 | Overflow 的编译目标为 Java 1.8,在以下环境可以正常编译。
19 |
20 | | 操作系统 | JDK | 架构 |
21 | |----------------|-----------------|-------|
22 | | Windows 11 | OpenJDK 11 | amd64 |
23 | | Ubuntu 22.04.3 | AdoptOpenJDK 11 | amd64 |
24 |
25 | 若无法正常编译,可尝试使用以上环境。
26 |
27 | ## 编译
28 |
29 | 执行以下命令即可编译 Overflow
30 | ```shell
31 | ./gradlew shadowJar
32 | ```
33 | 用于生产环境的包将会出现在 `overflow-core-all/build/libs/*-all.jar`
34 |
35 | ## 调试
36 |
37 | 使用 IntelliJ IDEA 打开此项目,在 Gradle 选项卡展开 `Overflow -> Tasks -> other`,双击执行 `runConsole` 即可运行 mirai-console。
38 |
39 | 或者在终端执行 `gradlew runConsole` 也可以达到同样的效果。
40 |
41 | 相应的 mirai 工作目录在 `./overflow-core/run`。
42 |
43 | ## mirai-console 兼容
44 |
45 | overflow-core 附有 mirai-console 兼容,在 IMirai 实现类 [Overflow](https://github.com/MrXiaoM/Overflow/blob/main/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/Overflow.kt) 的构造函数中,若检测到存在 mirai-console 相关类,将注入插件 [OverflowCoreAsPlugin](https://github.com/MrXiaoM/Overflow/blob/main/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/plugin/OverflowCoreAsPlugin.kt) 以帮助 Overflow 启动。
46 |
47 | ## 兼容其它 Onebot 实现
48 |
49 | 在连接成功后,Overflow 将会请求 `get_version_info`,并将 `app_name` 保存到 [RemoteBot](https://github.com/MrXiaoM/Overflow/blob/main/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/contact/RemoteBot.kt).appName 中,如果有原版 Onebot 标准中没有的额外接口实现,可以判断 appName 再调取该接口,以兼容尽可能多的 Onebot 实现。
50 |
51 | `bot.asOnebot.impl` 即可获取当前 mirai bot 的 [Bot (Onebot)](https://github.com/MrXiaoM/Overflow/blob/main/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/core/Bot.kt) 实现,在这里主动调取接口。
52 |
53 | 所有由服务端回复的结果,以及服务器发送的事件,均会在 onebot 模块转换为实体类,再传递给 overflow-core 模块使用,实体类均在 [cn.evolvefield.onebot.sdk](https://github.com/MrXiaoM/Overflow/tree/main/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk) 包中。按需创建或修改。
54 |
55 | ## 添加新消息类型/格式支持
56 |
57 | mirai 中不存在的消息类型,请添加到包 [top.mrxiaom.overflow.message.data](https://github.com/MrXiaoM/overflow/tree/main/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/message/data),参考包中原有的消息类型即可。
58 |
59 | 添加之后,到 [OnebotMessages](https://github.com/MrXiaoM/overflow/blob/dbd98d2b867356aa64b78d1a89e6cf8673337d0a/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/OnebotMessages.kt)
60 | + 在 `registerSerializers` 注册序列化器,以便该消息能够在 mirai 这边被序列化与反序列化
61 | + 在 `serializeToOneBotJsonArray` 添加该消息转换为 Onebot 消息段的实现
62 | + 在 `deserializeFromOneBotJson` 添加 Onebot 消息段转换为该消息的实现
63 | + 在 `Message.messageType` 添加该消息的 Onebot 消息类型名
64 |
65 | 正如上方步骤所述,`serializeToOneBotJsonArray` 和 `deserializeFromOneBotJson` 是 mirai 消息与 Onebot 消息段互相转换的重要方法。
66 | 方法开头获取了当前 Bot 的 Onebot 实现名称,如果某些 Onebot 实现增加了扩展消息格式,可以添加到这两个方法中。
67 |
68 | ## 提交高质量的 commit 以及 PR
69 |
70 | 另请参见 [mirai 贡献](https://github.com/mamoe/mirai/tree/dev/docs/contributing#%E6%8F%90%E4%BA%A4%E9%AB%98%E8%B4%A8%E9%87%8F%E7%9A%84-commit-%E4%BB%A5%E5%8F%8A-pr)
71 |
--------------------------------------------------------------------------------
/docs/install/MCL.md:
--------------------------------------------------------------------------------
1 | # 编辑 MCL 的 config.json
2 |
3 | > 开发版本需要 [该版本 MCL](https://github.com/iTXTech/mirai-console-loader/pull/192) 才可使用本方法。
4 |
5 | 首先,使用文本编辑器打开 `config.json` 大概是这样的
6 |
7 | ```json5
8 | {
9 | // ...
10 | "maven_repo": [
11 | "https://maven.aliyun.com/repository/public"
12 | ],
13 | // ...
14 | "packages": {
15 | // ...
16 | "net.mamoe:mirai-core-all": {
17 | "channel": "maven-stable",
18 | "version": "2.16.0",
19 | "type": "libs",
20 | "versionLocked": false
21 | },
22 | // ...
23 | }
24 | }
25 | ```
26 | ## 第一步. 加仓库
27 | 在 `maven_repo` 中加入快照仓库地址,添加完成后如下
28 |
29 | ```json5
30 | {
31 | // ...
32 | "maven_repo": [
33 | "https://maven.aliyun.com/repository/public",
34 | "https://s01.oss.sonatype.org/content/repositories/snapshots"
35 | ],
36 | // ...
37 | }
38 | ```
39 |
40 | ## 第二步. 加依赖
41 |
42 | - 将其中的 `"net.mamoe:mirai-core-all"` 改为 `"top.mrxiaom.mirai:overflow-core-all"`
43 | - 将其中的 `"maven-stable"` 改为 `"maven-snapshots"`
44 | - 将 `version` 的值 `2.16.0` 改为 Overflow 版本号
45 |
46 | 快照仓库中 Overflow 版本号的格式为 `major.minor.patch.commits-shortHash-SNAPSHOT`,
47 | 例如:`0.9.9.481-d59fa60-SNAPSHOT`,详见 #92
48 |
49 | 你可以在 [这里](https://s01.oss.sonatype.org/content/repositories/snapshots/top/mrxiaom/mirai/overflow-core/) 查询已发布到快照仓库的开发版本列表。
50 | 你可以在 [这里](https://central.sonatype.com/search?q=g%3Atop.mrxiaom.mirai) 查询所有正式版本列表。
51 |
--------------------------------------------------------------------------------
/docs/install/MCLOverflow.md:
--------------------------------------------------------------------------------
1 | # 通过 MCL 命令行安装
2 |
3 | # unix 安装教程
4 |
5 | 在 Linux/MacOS/Termux 下安装 Overflow。
6 |
7 | 在终端依次执行以下命令即可安装
8 |
9 | ```shell
10 | mkdir mcl
11 | cd mcl
12 | wget https://github.com/MrXiaoM/mirai-console-loader/releases/download/v2.1.2-patch1/with-overflow.zip
13 | unzip with-overflow.zip
14 | chmod +x mcl
15 | ./mcl -u --dry-run
16 | ```
17 |
18 | 若网络环境不佳,可将 `https://github.com` 改为 `https://mirror.ghproxy.com/github.com` 再试。
19 |
20 | 等待安装完成后,以后只需执行
21 |
22 | ```shell
23 | ./mcl
24 | ```
25 |
26 | 即可启动 Overflow。
27 |
28 | # Windows 安装教程
29 |
30 | 下载 [已修改的MCL](https://github.com/MrXiaoM/mirai-console-loader/releases/download/v2.1.2-patch1/with-overflow.zip) 并解压,若网络环境不佳,可将 https://github.com 改为 https://mirror.ghproxy.com/github.com 再试。
31 |
32 | 在解压的文件夹打开 CMD,执行
33 |
34 | ```shell
35 | mcl -u --dry-run
36 | ```
37 |
38 | 等待安装完成后,以后只需打开 `mcl.cmd` 即可启动 Overflow。
39 |
--------------------------------------------------------------------------------
/docs/install/MCLScript.md:
--------------------------------------------------------------------------------
1 | # MCL + 脚本安装
2 |
3 | > 开发版本需要 [该版本 MCL](https://github.com/iTXTech/mirai-console-loader/pull/192) 才可使用本方法。
4 |
5 | 如果需要开发版本,首先打开配置文件 config.json,在 `maven_repo` 中加入快照仓库地址,添加完成后如下
6 |
7 | > 如果不需要开发版本,直接打开下述的脚本即可。
8 |
9 | ```json5
10 | {
11 | // ...
12 | "maven_repo": [
13 | "https://maven.aliyun.com/repository/public",
14 | "https://s01.oss.sonatype.org/content/repositories/snapshots"
15 | ],
16 | // ...
17 | }
18 | ```
19 |
20 | 保存配置文件,然后
21 |
22 | - Windows 系统下载 [install_overflow_mcl.cmd](/docs/install/install_overflow_mcl.cmd)
23 | - Linux/MacOS 系统下载 [install_overflow_mcl.sh](/docs/install/install_overflow_mcl.sh)
24 |
25 | 将该脚本放到 MCL 所在目录,打开该脚本等待安装即可。
26 |
27 | 此脚本仅用于安装,升级请直接执行 `mcl -u`
28 |
--------------------------------------------------------------------------------
/docs/install/README.md:
--------------------------------------------------------------------------------
1 | # 安装教程
2 |
3 | 本目录存放 Overflow 的安装教程,不含 Onebot 实现的部署与连接教程。
4 |
5 | 关于部分 Onebot 实现的部署教程,另请参见 [数据消散Wiki](https://wiki.mrxiaom.top/overflow)。
6 |
--------------------------------------------------------------------------------
/docs/install/Raw.md:
--------------------------------------------------------------------------------
1 | # 替换 mirai-core 类库
2 |
3 | 需要准备:
4 | - overflow-core-all 的编译产物,可在 [Actions](https://github.com/MrXiaoM/Overflow/actions/workflows/dev.yml) 或 [快照仓库](https://s01.oss.sonatype.org/content/repositories/snapshots/top/mrxiaom/mirai/overflow-core-all/) 或 [Maven Central](https://repo.maven.apache.org/maven2/top/mrxiaom/mirai/overflow-core-all/) 下载。也可以拉取本项目运行 `./gradlew shadowJar` 后在 `overflow-core-all/build/libs` 中取得 (`-all.jar` 文件)
5 | - mirai-console 的编译产物,可[在此](https://mirrors.huaweicloud.com/repository/maven/net/mamoe/mirai-console/2.16.0/mirai-console-2.16.0-all.jar)下载
6 | - mirai-console-terminal 的编译产物,可[在此](https://mirrors.huaweicloud.com/repository/maven/net/mamoe/mirai-console-terminal/2.16.0/mirai-console-terminal-2.16.0-all.jar)下载
7 |
8 | 创建 `libs` 文件夹,将以上内容放入该文件夹。
9 |
10 | **注意,以上获取的文件均为 `-all.jar` 结尾的文件**
11 |
12 | 创建启动脚本,`start.cmd`(Windows) 或 `start.sh`(Linux/MacOS),按需写入以下内容。
13 |
14 | `start.cmd`(Windows) 如下
15 | ```shell
16 | java -cp ./libs/* net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader
17 | pause
18 | ```
19 | `start.sh`(Linux/MacOS) 如下
20 | ```shell
21 | java -cp "$CLASSPATH:./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader
22 | ```
23 |
24 | 打开启动脚本即可。
25 |
--------------------------------------------------------------------------------
/docs/install/install_overflow_mcl.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | cmd /c mcl --remove-package net.mamoe:mirai-core-all
3 | cmd /c mcl --update-package top.mrxiaom.mirai:overflow-core-all --channel maven-snapshots --type libs
4 | cmd /c mcl --update --dry-run
5 | pause
6 |
--------------------------------------------------------------------------------
/docs/install/install_overflow_mcl.sh:
--------------------------------------------------------------------------------
1 | ./mcl --remove-package net.mamoe:mirai-core-all
2 | ./mcl --update-package top.mrxiaom.mirai:overflow-core-all --channel maven-snapshots --type libs
3 | ./mcl --update --dry-run
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | org.gradle.jvmargs=-Xmx7000m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED
3 | org.gradle.parallel=true
4 | org.gradle.vfs.watch=true
5 | mirror.repo=https://mirrors.huaweicloud.com/repository/maven/
6 | kotlin.version=1.8.0
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrXiaoM/Overflow/5f559bfb1cf58dc889f8d548446fc8ca18a021ee/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/overflow-core-all/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | id("com.github.johnrengelman.shadow")
4 | }
5 |
6 | setupMavenCentralPublication {
7 | artifact(tasks.shadowJar)
8 | artifact(tasks.kotlinSourcesJar)
9 | }
10 |
11 | tasks.test {
12 | useJUnitPlatform()
13 | }
14 |
15 | dependencies {
16 | api(project(":overflow-core-api"))
17 | api(project(":overflow-core"))
18 | }
19 |
20 | tasks {
21 | shadowJar {
22 | mapOf(
23 | "com.google.gson" to "gson",
24 | "org.java_websocket" to "websocket",
25 | "io.netty" to "netty"
26 | ).forEach { (original, target) ->
27 | relocate(original, "top.mrxiaom.overflow.internal.deps.$target")
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/overflow-core-api/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | kotlin("plugin.serialization")
4 | id("org.jetbrains.dokka")
5 | id("com.github.gmazzo.buildconfig")
6 | id("me.him188.kotlin-jvm-blocking-bridge")
7 | }
8 |
9 | var commitHash = extra("commitHash") ?: "local"
10 | var commitCount: Int? = extra("commitCount")
11 | val miraiVersion = extra("miraiVersion") ?: "2.16.0"
12 |
13 | kotlin {
14 | optInForAllSourceSets("net.mamoe.mirai.utils.MiraiExperimentalApi")
15 | optInForAllSourceSets("net.mamoe.mirai.utils.MiraiInternalApi")
16 | }
17 |
18 | buildConfig {
19 | className("BuildConstants")
20 | packageName("top.mrxiaom.overflow")
21 | useKotlinOutput()
22 | buildConfigField("String", "VERSION", "\"${project.version}\"")
23 | buildConfigField("String", "MIRAI_VERSION", "\"$miraiVersion\"")
24 | buildConfigField("String", "COMMIT_HASH", "\"$commitHash\"")
25 | buildConfigField("kotlin.Int?", "COMMIT_COUNT", commitCount?.toString() ?: "null")
26 | buildConfigField("java.time.Instant", "BUILD_TIME", "java.time.Instant.ofEpochSecond(${System.currentTimeMillis() / 1000L}L)")
27 | }
28 |
29 | tasks.register("dokkaJavadocJar") {
30 | group = "documentation"
31 | dependsOn(tasks.dokkaJavadoc)
32 | from(tasks.dokkaJavadoc.flatMap { it.outputDirectory })
33 | archiveClassifier.set("javadoc")
34 | }
35 |
36 | setupMavenCentralPublication {
37 | artifact(tasks.kotlinSourcesJar)
38 | artifact(tasks.getByName("dokkaJavadocJar"))
39 | }
40 |
41 | dependencies {
42 | api("net.mamoe:mirai-core-api:$miraiVersion")
43 | api("net.mamoe:mirai-core-utils:$miraiVersion")
44 |
45 | api("me.him188:kotlin-jvm-blocking-bridge-runtime:3.0.0-180.1")
46 |
47 | api("org.slf4j:slf4j-api:2.0.5")
48 | }
49 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/OverflowAPI.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow
2 |
3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
4 | import net.mamoe.mirai.Bot
5 | import net.mamoe.mirai.Mirai
6 | import net.mamoe.mirai.message.data.*
7 | import net.mamoe.mirai.utils.MiraiLogger
8 | import top.mrxiaom.overflow.contact.RemoteBot
9 | import java.io.File
10 | import kotlin.time.Duration
11 |
12 | val Overflow: OverflowAPI
13 | get() = OverflowAPI.get()
14 | interface OverflowAPI {
15 | /**
16 | * Overflow 版本号
17 | */
18 | val version: String
19 | val botStarter: IBotStarter
20 | /**
21 | * 以 Onebot 格式新建图片消息
22 | *
23 | * 另请参见 [Onebot file 参数说明](https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E5%9B%BE%E7%89%87)
24 | */
25 | fun imageFromFile(file: String): Image
26 | /**
27 | * 以 Onebot 格式新建语音消息
28 | *
29 | * 另请参见 [Onebot file 参数说明](https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E5%9B%BE%E7%89%87)
30 | */
31 | fun audioFromFile(file: String): Audio
32 | /**
33 | * 以 Onebot 格式新建短视频消息
34 | *
35 | * 另请参见 [Onebot file 参数说明](https://github.com/botuniverse/onebot-11/blob/master/message/segment.md#%E5%9B%BE%E7%89%87)
36 | */
37 | fun videoFromFile(file: String): ShortVideo
38 |
39 | /**
40 | * 序列化 mirai 格式消息为 Onebot 格式 json 消息段
41 | */
42 | @Deprecated(
43 | message = "Please use serializeMessage(bot, message)",
44 | ReplaceWith("serializeMessage(bot, message)")
45 | )
46 | fun serializeMessage(message: Message): String = serializeMessage(null, message)
47 |
48 | /**
49 | * 序列化 mirai 格式消息为 Onebot 格式 json 消息段
50 | */
51 | fun serializeMessage(bot: RemoteBot?, message: Message): String
52 |
53 | /**
54 | * 序列化 Onebot 格式 json 消息段为 CQ 码
55 | */
56 | fun serializeJsonToCQCode(messageJson: String): String
57 |
58 | /**
59 | * 序列化 Onebot 格式 CQ 码 为 json 消息段
60 | */
61 | fun serializeCQCodeToJson(messageCQCode: String): String
62 |
63 | /**
64 | * 反序列化 Onebot 格式 json 消息段或 CQ 码,反序列化为 mirai 格式消息
65 | */
66 | @JvmBlockingBridge
67 | suspend fun deserializeMessage(bot: Bot, message: String): MessageChain
68 |
69 | /**
70 | * 反序列化 json 消息段为 mirai 格式消息
71 | */
72 | @JvmBlockingBridge
73 | suspend fun deserializeMessageFromJson(bot: Bot, message: String): MessageChain?
74 |
75 | /**
76 | * 反序列化 CQ 码 为 mirai 格式消息
77 | */
78 | @JvmBlockingBridge
79 | suspend fun deserializeMessageFromCQCode(bot: Bot, message: String): MessageChain?
80 |
81 | /**
82 | * 配置消息自动缓存选项,传入 null 的值不会被修改
83 | * @param enabled 是否启用媒体消息(图片、语音、视频)缓存。
84 | * @param saveDir 缓存文件夹路径。
85 | * @param keepDuration 缓存文件保存时间。在收到消息时,文件将会自动清理。
86 | */
87 | fun configureMessageCache(enabled: Boolean? = null, saveDir: File? = null, keepDuration: Duration? = null)
88 |
89 | companion object {
90 | public val logger = MiraiLogger.Factory.create(OverflowAPI::class, "Overflow")
91 | @JvmStatic
92 | fun get(): OverflowAPI = Mirai as OverflowAPI
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/action/ActionContext.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.action
2 |
3 | import kotlinx.serialization.Serializable
4 | import java.util.function.Consumer
5 |
6 | /**
7 | * Onebot 主动操作上下文
8 | */
9 | @Serializable
10 | data class ActionContext(
11 | val action: String,
12 | /**
13 | * 是否在调用失败时抛出异常。
14 | *
15 | * - 如果为 `false`(默认),则使用 warn 级别打印一条日志
16 | * - 如果为 `true`,则抛出异常
17 | * - 如果为 `null`,则使用 trace 级别打印日志,不抛出异常
18 | */
19 | var throwExceptions: Boolean? = false,
20 | /**
21 | * 是否将返回结果没有 `"status"` 的情况,当作调用成功。
22 | *
23 | * 若开启此选项,明确出现网络问题或者明确指定 `"status": "failed"` 才会当作调用失败。
24 | */
25 | var ignoreStatus: Boolean = false,
26 | ) {
27 | class Builder private constructor(action: String) {
28 | private val context = ActionContext(action)
29 |
30 | /**
31 | * @see ActionContext.throwExceptions
32 | */
33 | fun throwExceptions(flag: Boolean?) {
34 | context.throwExceptions = flag
35 | }
36 |
37 | /**
38 | * (旧版方法) 是否在报错时显示警告日志
39 | *
40 | * 迁移说明:
41 | * - `null` 的效果,与 `showWarning=false` 一致
42 | * - `false` 的效果,与 `showWarning=true` 一致
43 | * @see ActionContext.throwExceptions
44 | */
45 | fun showWarning(flag: Boolean) {
46 | if (flag) {
47 | context.throwExceptions = false
48 | } else {
49 | context.throwExceptions = null
50 | }
51 | }
52 |
53 | /**
54 | * @see ActionContext.ignoreStatus
55 | */
56 | fun ignoreStatus(flag: Boolean) {
57 | context.ignoreStatus = flag
58 | }
59 |
60 | /**
61 | * 构建一个新的 ActionContext
62 | */
63 | fun build(): ActionContext {
64 | return ActionContext(
65 | action = context.action,
66 | throwExceptions = context.throwExceptions,
67 | ignoreStatus = context.ignoreStatus
68 | )
69 | }
70 |
71 | companion object {
72 | fun create(action: String): Builder = Builder(action)
73 | }
74 | }
75 | companion object {
76 | @JvmStatic
77 | fun builder(action: String): Builder = Builder.create(action)
78 |
79 | fun builder(action: String, block: Builder.() -> Unit): Builder = builder(action).apply(block)
80 |
81 | fun build(action: String, block: Builder.() -> Unit): ActionContext = builder(action).apply(block).build()
82 |
83 | @JvmStatic
84 | fun builder(action: String, block: Consumer): Builder {
85 | val builder = builder(action)
86 | block.accept(builder)
87 | return builder
88 | }
89 |
90 | @JvmStatic
91 | fun build(action: String, block: Consumer): ActionContext {
92 | val builder = builder(action)
93 | block.accept(builder)
94 | return builder.build()
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/contact/RemoteBot.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.contact
2 |
3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
4 | import net.mamoe.mirai.Bot
5 | import net.mamoe.mirai.message.data.MessageChain
6 | import top.mrxiaom.overflow.action.ActionContext
7 | import kotlin.jvm.Throws
8 |
9 | interface RemoteBot {
10 | /**
11 | * Onebot 实现名称
12 | */
13 | val appName: String
14 |
15 | /**
16 | * Onebot 实现版本
17 | */
18 | val appVersion: String
19 |
20 | /**
21 | * 配置中是否已禁止 Overflow 调用网络接口
22 | */
23 | val noPlatform: Boolean
24 |
25 | /**
26 | * 执行自定义动作
27 | *
28 | * 详见 [Onebot 11 公开 API](https://github.com/botuniverse/onebot-11/blob/master/api/public.md)。
29 | * @param actionPath API请求路径
30 | * @param params 请求参数,可为空
31 | * @return 服务端返回信息,格式为 json
32 | */
33 | @JvmBlockingBridge
34 | suspend fun executeAction(actionPath: String, params: String?): String
35 |
36 | /**
37 | * 执行自定义动作
38 | *
39 | * 详见 [Onebot 11 公开 API](https://github.com/botuniverse/onebot-11/blob/master/api/public.md) 和 [go-cqhttp 公开 API](https://docs.go-cqhttp.org/api)。
40 | *
41 | * 用例:
42 | * ```kotlin
43 | * // kotlin
44 | * val result = onebot.executeAction(
45 | * context = ActionContext.build("get_guild_list") {
46 | * throwExceptions(true)
47 | * },
48 | * params = null
49 | * )
50 | * ```
51 | * ```java
52 | * // java
53 | * String result = onebot.executeAction(
54 | * ActionContext.build("get_guild_list", builder -> {
55 | * builder.throwExceptions(true);
56 | * }),
57 | * null
58 | * );
59 | * ```
60 | *
61 | * @param context 自定义请求上下文,可控制抛出异常、忽略标准格式等选项
62 | * @param params 请求参数,可为空
63 | * @return 服务端返回信息,格式为 json
64 | */
65 | @JvmBlockingBridge
66 | suspend fun executeAction(context: ActionContext, params: String?): String
67 |
68 | /**
69 | * 发送自定义 WebSocket 消息
70 | * @param message 消息内容
71 | */
72 | fun sendRawWebSocketMessage(message: String)
73 |
74 | /**
75 | * 发送自定义 WebSocket 消息
76 | * @param message 消息内容
77 | */
78 | fun sendRawWebSocketMessage(message: ByteArray)
79 |
80 | /**
81 | * 通过消息id获取消息,并反序列化为 mirai 格式消息
82 | * @param messageId 消息id
83 | */
84 | @JvmBlockingBridge
85 | suspend fun getMsg(messageId: Int): MessageChain?
86 |
87 | companion object {
88 | /**
89 | * 尝试将 mirai Bot 转换为 Overflow RemoteBot
90 | *
91 | * 当类型不正确时,或没有 Overflow 实现时,将会抛出异常
92 | */
93 | @JvmStatic
94 | @get:Throws(ClassNotFoundException::class, ClassCastException::class)
95 | val Bot.asRemoteBot
96 | get() = this as RemoteBot
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/contact/RemoteGroup.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.contact
2 |
3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
4 | import net.mamoe.mirai.contact.ContactList
5 | import net.mamoe.mirai.contact.Group
6 | import net.mamoe.mirai.contact.NormalMember
7 | import net.mamoe.mirai.contact.announcement.Announcements
8 |
9 | interface RemoteGroup {
10 | @JvmBlockingBridge
11 | suspend fun updateGroupMemberList(): ContactList
12 | @JvmBlockingBridge
13 | suspend fun updateAnnouncements(): Announcements
14 |
15 | /**
16 | * 设置群消息反应表情
17 | *
18 | * @param messageId 消息ID,可通过 `MessageSource.ids[0]` 取得
19 | * @param icon 图标ID,详见 Overflow 开发文档
20 | * @param enable 添加或取消反应表情
21 | */
22 | @JvmBlockingBridge
23 | suspend fun setMsgReaction(messageId: Int, icon: String, enable: Boolean)
24 |
25 | companion object {
26 | /**
27 | * 尝试将 mirai Group 转换为 Overflow RemoteGroup
28 | *
29 | * 当类型不正确时,或没有 Overflow 实现时,将会抛出异常
30 | */
31 | @JvmStatic
32 | @get:Throws(ClassNotFoundException::class, ClassCastException::class)
33 | val Group.asRemoteGroup
34 | get() = this as RemoteGroup
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/contact/RemoteUser.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.contact
2 |
3 | import net.mamoe.mirai.contact.User
4 |
5 | interface RemoteUser {
6 | val onebotData: String
7 |
8 | companion object {
9 | /**
10 | * 尝试将 mirai User 转换为 Overflow RemoteUser
11 | *
12 | * 当类型不正确时,或没有 Overflow 实现时,将会抛出异常
13 | */
14 | @JvmStatic
15 | @get:Throws(ClassNotFoundException::class, ClassCastException::class)
16 | val User.asRemoteUser
17 | get() = this as RemoteUser
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/contact/Updatable.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.contact
2 |
3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
4 |
5 | interface Updatable {
6 | /**
7 | * 请求更新信息
8 | */
9 | @JvmBlockingBridge
10 | suspend fun queryUpdate()
11 | }
12 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/event/LegacyGuildMessageEvent.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.event
2 |
3 | import net.mamoe.mirai.Bot
4 | import net.mamoe.mirai.contact.MemberPermission
5 | import net.mamoe.mirai.event.AbstractEvent
6 | import net.mamoe.mirai.message.data.MessageChain
7 |
8 | /**
9 | * 临时的频道消息事件
10 | *
11 | * 在正式确定频道接口之前,可使用此事件接收频道消息
12 | */
13 | @Deprecated("腾讯自己都不积极维护频道了,没必要支持了")
14 | public class LegacyGuildMessageEvent(
15 | /**
16 | * 接收消息的机器人
17 | */
18 | public val bot: Bot,
19 | /**
20 | * 频道ID
21 | */
22 | public val guildId: String,
23 | /**
24 | * 子频道ID
25 | */
26 | public val channelId: String,
27 | /**
28 | * 消息ID
29 | */
30 | public val messageId: String,
31 | /**
32 | * 收到的消息
33 | */
34 | public val message: MessageChain,
35 | /**
36 | * 消息发送者ID
37 | */
38 | public val senderId: Long,
39 | /**
40 | * 消息发送者TinyID
41 | */
42 | public val senderTinyId: String,
43 | /**
44 | * 消息发送者昵称
45 | */
46 | public val senderNick: String,
47 | /**
48 | * 消息发送者名片
49 | */
50 | public val senderNameCard: String,
51 | /**
52 | * 消息发送者头衔
53 | */
54 | public val senderTitle: String,
55 | /**
56 | * 消息发送者等级
57 | */
58 | public val senderLevel: String,
59 | /**
60 | * 消息发送者权限
61 | */
62 | public val senderRole: MemberPermission,
63 | /**
64 | * 消息发送时间
65 | */
66 | public val time: Int
67 | ): AbstractEvent() {
68 | public override fun toString(): String =
69 | "LegacyGuildMessageEvent(guildId=$guildId, channelId=$channelId, senderNameCard=$senderNameCard, sender=${senderId}, permission=${senderRole}, message=$message)"
70 | }
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/event/MemberEssenceNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.event
2 |
3 | import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge
4 | import net.mamoe.mirai.Bot
5 | import net.mamoe.mirai.contact.Group
6 | import net.mamoe.mirai.contact.NormalMember
7 | import net.mamoe.mirai.event.AbstractEvent
8 | import net.mamoe.mirai.message.data.MessageChain
9 | import top.mrxiaom.overflow.contact.RemoteBot.Companion.asRemoteBot
10 |
11 | public abstract class MemberEssenceNoticeEvent private constructor(
12 | /**
13 | * 机器人
14 | */
15 | public val bot: Bot,
16 | /**
17 | * 群聊
18 | */
19 | public val group: Group,
20 | /**
21 | * 消息发送者
22 | */
23 | public val member: NormalMember,
24 | /**
25 | * 精华消息添加者
26 | */
27 | public val operator: NormalMember,
28 | /**
29 | * 精华消息ID
30 | */
31 | public val messageId: Int
32 | ): AbstractEvent() {
33 |
34 | /**
35 | * 获取精华消息内容
36 | */
37 | @JvmBlockingBridge
38 | suspend fun queryMessage(): MessageChain? {
39 | return bot.asRemoteBot.getMsg(messageId)
40 | }
41 |
42 | protected open val type: String = ""
43 |
44 | public class Add(
45 | bot: Bot,
46 | group: Group,
47 | member: NormalMember,
48 | operator: NormalMember,
49 | messageId: Int
50 | ) : MemberEssenceNoticeEvent(bot, group, member, operator, messageId) {
51 | override val type: String = ".Add"
52 | }
53 |
54 | public class Delete(
55 | bot: Bot,
56 | group: Group,
57 | member: NormalMember,
58 | operator: NormalMember,
59 | messageId: Int
60 | ) : MemberEssenceNoticeEvent(bot, group, member, operator, messageId) {
61 | override val type: String = ".Delete"
62 | }
63 |
64 | public override fun toString(): String = "MemberEssenceNoticeEvent$type(bot=${bot.id}, group=${group.id}, operator=${operator.id}, member=${member.id}, messageId=$messageId)"
65 | }
66 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/event/MessageReactionEvent.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.event
2 |
3 | import net.mamoe.mirai.Bot
4 | import net.mamoe.mirai.contact.Group
5 | import net.mamoe.mirai.contact.NormalMember
6 | import net.mamoe.mirai.event.AbstractEvent
7 | import net.mamoe.mirai.event.events.BotEvent
8 |
9 | public class MessageReactionEvent(
10 | /**
11 | * 机器人
12 | */
13 | public override val bot: Bot,
14 | /**
15 | * 群聊
16 | */
17 | public val group: Group,
18 | /**
19 | * 操作人,null 时为机器人
20 | */
21 | public val operator: NormalMember?,
22 | /**
23 | * 被操作的消息ID
24 | */
25 | public val messageId: Int,
26 | /**
27 | * 表情ID,详见 Overflow 开发文档
28 | */
29 | public val reaction: String,
30 | /**
31 | * 进行此操作后的表情总数
32 | */
33 | public val count: Int,
34 | /**
35 | * 表情被添加还是移除
36 | *
37 | * `true` 为添加,`false` 为移除
38 | */
39 | public val operation: Boolean,
40 | ): BotEvent, AbstractEvent()
41 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/event/UnsolvedOnebotEvent.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.event
2 |
3 | import kotlinx.serialization.json.Json
4 | import kotlinx.serialization.json.JsonObject
5 | import kotlinx.serialization.json.jsonObject
6 | import net.mamoe.mirai.Bot
7 | import net.mamoe.mirai.event.AbstractEvent
8 | import net.mamoe.mirai.event.CancellableEvent
9 |
10 | public class UnsolvedOnebotEvent(
11 | /**
12 | * 机器人QQ号
13 | */
14 | public val botId: Long,
15 | /**
16 | * 收到的原事件内容
17 | */
18 | public val messageRaw: String,
19 | /**
20 | * 收到事件的时间
21 | */
22 | public val time: Long,
23 | ): AbstractEvent(), CancellableEvent {
24 | /**
25 | * 获取机器人实例,可能会失败
26 | */
27 | public val bot: Bot
28 | get() = Bot.getInstance(botId)
29 |
30 | /**
31 | * 获取机器人实例,可能会失败
32 | */
33 | public val botOrNull: Bot?
34 | get() = Bot.getInstanceOrNull(botId)
35 |
36 | /**
37 | * 读取事件内容为 json
38 | */
39 | public val json: JsonObject
40 | get() = Json.parseToJsonElement(messageRaw).jsonObject
41 |
42 | public override fun toString(): String = "UnsolvedOnebotEvent(bot=$botId, time=$time, message=$messageRaw)"
43 | }
44 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/message/data/ContactRecommend.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
2 | package top.mrxiaom.overflow.message.data
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 | import net.mamoe.mirai.message.code.CodableMessage
7 | import net.mamoe.mirai.message.data.AbstractPolymorphicMessageKey
8 | import net.mamoe.mirai.message.data.ConstrainSingle
9 | import net.mamoe.mirai.message.data.MessageContent
10 | import net.mamoe.mirai.message.data.MessageKey
11 | import net.mamoe.mirai.utils.safeCast
12 |
13 | /**
14 | * 推荐联系人
15 | */
16 | @Serializable
17 | @SerialName(ContactRecommend.SERIAL_NAME)
18 | public data class ContactRecommend(
19 | /**
20 | * 联系人类型
21 | */
22 | public val contactType: ContactType,
23 | public val id: Long
24 | ) : MessageContent, ConstrainSingle, CodableMessage {
25 | private val _contentValue: String by lazy(LazyThreadSafetyMode.NONE) {
26 | val typeString = when(contactType) {
27 | ContactType.Group -> "群聊"
28 | ContactType.Private -> "好友"
29 | }
30 | "[推荐$typeString]$id"
31 | }
32 | public override fun toString(): String = "[overflow:contact,$contactType,$id]"
33 | public override fun contentToString(): String = _contentValue
34 |
35 | override fun appendMiraiCodeTo(builder: StringBuilder) {
36 | builder.append("[mirai:contact:").append(contactType.name.lowercase()).append(",").append(id).append("]")
37 | }
38 |
39 | /**
40 | * 推荐联系人类型
41 | */
42 | public enum class ContactType {
43 | /**
44 | * 群聊
45 | */
46 | Group,
47 |
48 | /**
49 | * 私聊
50 | */
51 | Private
52 | }
53 |
54 | override val key: MessageKey get() = Key
55 | public companion object Key :
56 | AbstractPolymorphicMessageKey(MessageContent, { it.safeCast() }) {
57 | public const val SERIAL_NAME: String = "Contact"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/message/data/FileMessageWithUrl.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.message.data
2 |
3 | import net.mamoe.mirai.message.data.FileMessage
4 |
5 | /**
6 | * 带链接的文件消息
7 | *
8 | * 注意,这个消息类型提供的文件链接并不可靠,可能为空字符串。
9 | *
10 | * 基本上,仅在开启 use_group_upload_event_for_file_message 的情况下,从消息收到文件消息时可提供链接。
11 | */
12 | interface FileMessageWithUrl : FileMessage {
13 | val url: String
14 | }
15 |
16 | val FileMessage.withUrl: FileMessageWithUrl
17 | get() = this as FileMessageWithUrl
18 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/message/data/InlineKeyboard.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.message.data
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 | import net.mamoe.mirai.message.data.*
6 | import net.mamoe.mirai.utils.safeCast
7 |
8 | /**
9 | * 内联键盘消息. 常见于 Markdown 消息下方
10 | */
11 | @Serializable
12 | @SerialName(InlineKeyboard.SERIAL_NAME)
13 | public data class InlineKeyboard(
14 | /**
15 | * 官方机器人 appId
16 | */
17 | public val botAppId: Long,
18 | /**
19 | * 按键行列表
20 | */
21 | public val rows: List
22 | ) : MessageContent {
23 | private val string by lazy {
24 | "[overflow:inlinekeyboard,$botAppId,${rows.joinToString(",")}]"
25 | }
26 | private val content by lazy {
27 | "\n[内联键盘]\n" +
28 | rows.joinToString("\n") { row -> row.buttons.joinToString(", ") { it.label } }
29 | }
30 | public override fun toString(): String = string
31 | public override fun contentToString(): String = content
32 |
33 | public companion object Key :
34 | AbstractPolymorphicMessageKey(MessageContent, { it.safeCast() }) {
35 | public const val SERIAL_NAME: String = "InlineKeyboard"
36 | }
37 | }
38 |
39 | /**
40 | * 内联键盘按键行
41 | */
42 | @Serializable
43 | public data class InlineKeyboardRow(
44 | /**
45 | * 这一行的按钮列表
46 | */
47 | val buttons: List
48 | ) {
49 | private val stringContent by lazy {
50 | "{row:[${buttons.joinToString(",")}]}"
51 | }
52 | override fun toString(): String = stringContent
53 | }
54 | /**
55 | * 内联键盘按钮
56 | */
57 | @Serializable
58 | public data class InlineKeyboardButton(
59 | val id: String,
60 | val label: String,
61 | val visitedLabel: String,
62 | val style: Int,
63 | val type: Int,
64 | val clickLimit: Int,
65 | val unsupportTips: String,
66 | val data: String,
67 | val atBotShowChannelList: Boolean,
68 | val permissionType: Int,
69 | val specifyRoleIds: List,
70 | val specifyTinyIds: List
71 | ) {
72 | override fun toString(): String {
73 | return "[button:$label]"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/message/data/Location.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
2 | package top.mrxiaom.overflow.message.data
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 | import net.mamoe.mirai.message.code.CodableMessage
7 | import net.mamoe.mirai.message.data.AbstractPolymorphicMessageKey
8 | import net.mamoe.mirai.message.data.ConstrainSingle
9 | import net.mamoe.mirai.message.data.MessageContent
10 | import net.mamoe.mirai.message.data.MessageKey
11 | import net.mamoe.mirai.utils.safeCast
12 |
13 | /**
14 | * 定位分享
15 | */
16 | @Serializable
17 | @SerialName(Location.SERIAL_NAME)
18 | public data class Location(
19 | /**
20 | * 纬度
21 | */
22 | public val lat: Float,
23 | /**
24 | * 经度
25 | */
26 | public val lon: Float,
27 | /**
28 | * 定位消息标题 (可能为空),通常为地名
29 | */
30 | public val title: String = "",
31 |
32 | /**
33 | * 定位消息内容 (可能为空),通常为详细地址
34 | */
35 | public val content: String = ""
36 | ) : MessageContent, ConstrainSingle, CodableMessage {
37 | private val _contentValue: String by lazy(LazyThreadSafetyMode.NONE) {
38 | "[位置]$title $content(经度:$lon 纬度:$lat)"
39 | }
40 | public override fun toString(): String = "[overflow:location,lon=$lat,lat=$lon,title=$title,content=$content]"
41 | public override fun contentToString(): String = _contentValue
42 |
43 | override fun appendMiraiCodeTo(builder: StringBuilder) {
44 | builder.append("[mirai:location:").append(lat).append(",")
45 | .append(lon).append(",")
46 | .append(title).append(",")
47 | .append(content).append("]")
48 | }
49 |
50 | override val key: MessageKey get() = Key
51 | public companion object Key :
52 | AbstractPolymorphicMessageKey(MessageContent, { it.safeCast() }) {
53 | public const val SERIAL_NAME: String = "Location"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/message/data/Markdown.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
2 | package top.mrxiaom.overflow.message.data
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 | import net.mamoe.mirai.message.code.CodableMessage
7 | import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode
8 | import net.mamoe.mirai.message.data.*
9 | import net.mamoe.mirai.utils.safeCast
10 |
11 | /**
12 | * Markdown消息. 用于官方 Bot
13 | *
14 | * 使用时直接构造即可.
15 | */
16 | @Serializable
17 | @SerialName(Markdown.SERIAL_NAME)
18 | public data class Markdown(
19 | /**
20 | * Markdown 内容
21 | */
22 | public val content: String
23 | ) : MessageContent, CodableMessage {
24 | public override fun toString(): String = "[overflow:markdown,$content]"
25 | public override fun contentToString(): String = content
26 |
27 | override fun appendMiraiCodeTo(builder: StringBuilder) {
28 | builder.append("[mirai:markdown:").appendStringAsMiraiCode(content).append("]")
29 | }
30 |
31 | public companion object Key :
32 | AbstractPolymorphicMessageKey(MessageContent, { it.safeCast() }) {
33 | public const val SERIAL_NAME: String = "Markdown"
34 | }
35 | }
36 | /**
37 | * 构造 [Markdown]
38 | */
39 | @JvmSynthetic
40 | @Suppress("NOTHING_TO_INLINE")
41 | public inline fun String.toMarkdown(): Markdown = Markdown(this)
42 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/spi/ExtendedMessageSerializerService.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
2 | package top.mrxiaom.overflow.spi
3 |
4 | import kotlinx.serialization.json.JsonObject
5 | import net.mamoe.mirai.message.data.Message
6 | import net.mamoe.mirai.spi.BaseService
7 | import net.mamoe.mirai.spi.SpiServiceLoader
8 | import top.mrxiaom.overflow.contact.RemoteBot
9 |
10 | /**
11 | * 额外的消息序列化器,用于处理不同 Onebot 平台的特殊情况
12 | */
13 | interface ExtendedMessageSerializerService : BaseService {
14 |
15 | /**
16 | * 反序列化 Onebot 消息段为 Mirai 消息
17 | * CQ 码会先转为消息段再进行处理,无需担心CQ码兼容问题
18 | *
19 | * @param bot 机器人实例
20 | * @param type 消息类型
21 | * @param data 消息数据
22 | * @return Mirai 消息,返回 null 代表不进行额外处理,交给其它序列化器或 Overflow 自带序列化器处理
23 | */
24 | suspend fun deserialize(bot: RemoteBot, type: String, data: JsonObject): Message?
25 |
26 | /**
27 | * 序列化 Mirai 消息为 Onebot 消息段
28 | *
29 | * @param bot 机器人实例
30 | * @param message Mirai 消息
31 | * @return Onebot 消息段的类型与数据,返回 null 代表不进行额外处理,交给其它序列化器或 Overflow 自带序列化器处理
32 | */
33 | fun serialize(bot: RemoteBot?, message: Message): Pair?
34 |
35 | companion object {
36 | private val loader = SpiServiceLoader(ExtendedMessageSerializerService::class)
37 |
38 | internal val instances: List
39 | get() = loader.allServices.sortedBy { it.priority }
40 |
41 | suspend fun List.deserialize(
42 | bot: RemoteBot,
43 | type: String,
44 | data: JsonObject
45 | ): Message? {
46 | for (ext in this) {
47 | val message = ext.deserialize(bot, type, data) ?: continue
48 | return message
49 | }
50 | return null
51 | }
52 |
53 | fun List.serialize(
54 | bot: RemoteBot?,
55 | message: Message
56 | ): Pair? {
57 | for (ext in this) {
58 | val pair = ext.serialize(bot, message) ?: continue
59 | return pair
60 | }
61 | return null
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/spi/FileService.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
2 | package top.mrxiaom.overflow.spi
3 |
4 | import net.mamoe.mirai.spi.BaseService
5 | import net.mamoe.mirai.spi.SpiServiceLoader
6 | import net.mamoe.mirai.utils.ExternalResource
7 |
8 | /**
9 | * 文件服务,用于图片、语音、端视频文件上传
10 | */
11 | interface FileService : BaseService {
12 | /**
13 | * 上传文件并返回链接
14 | *
15 | * @return Onebot 格式链接,以 `file:///`、`http(s)://` 或 `base64://` 开头
16 | */
17 | suspend fun upload(res: ExternalResource): String
18 |
19 | companion object {
20 | private val loader = SpiServiceLoader(FileService::class)
21 |
22 | internal val instance: FileService?
23 | get() = loader.service
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/overflow-core-api/src/main/kotlin/top/mrxiaom/overflow/spi/MediaURLService.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
2 | package top.mrxiaom.overflow.spi
3 |
4 | import net.mamoe.mirai.message.data.Audio
5 | import net.mamoe.mirai.message.data.Image
6 | import net.mamoe.mirai.message.data.ShortVideo
7 | import net.mamoe.mirai.spi.BaseService
8 | import net.mamoe.mirai.spi.SpiServiceLoader
9 | import top.mrxiaom.overflow.contact.RemoteBot
10 |
11 | /**
12 | * 媒体消息下载链接获取服务
13 | */
14 | interface MediaURLService : BaseService {
15 |
16 | /**
17 | * 获取图片链接
18 | *
19 | * @return 图片链接,返回 null 代表不进行处理,交给其它服务或 Overflow 自带服务处理
20 | */
21 | suspend fun queryImageUrl(bot: RemoteBot, image: Image): String?
22 |
23 | /**
24 | * 获取语音链接
25 | *
26 | * @return 语音链接,返回 null 代表不进行处理,交给其它服务或 Overflow 自带服务处理
27 | */
28 | fun queryAudioUrl(audio: Audio): String?
29 |
30 | /**
31 | * 获取视频链接
32 | *
33 | * @return 视频链接,返回 null 代表不进行处理,交给其它服务或 Overflow 自带服务处理
34 | */
35 | fun queryVideoUrl(video: ShortVideo): String?
36 |
37 | companion object {
38 | private val loader = SpiServiceLoader(MediaURLService::class)
39 |
40 | internal val instances: List
41 | get() = loader.allServices.sortedBy { it.priority }
42 |
43 | suspend fun List.queryImageUrl(bot: RemoteBot, image: Image): String? {
44 | for (ext in this) {
45 | val url = ext.queryImageUrl(bot, image) ?: continue
46 | return url
47 | }
48 | return null
49 | }
50 | fun List.queryAudioUrl(audio: Audio): String? {
51 | for (ext in this) {
52 | val url = ext.queryAudioUrl(audio) ?: continue
53 | return url
54 | }
55 | return null
56 | }
57 | fun List.queryVideoUrl(video: ShortVideo): String? {
58 | for (ext in this) {
59 | val url = ext.queryVideoUrl(video) ?: continue
60 | return url
61 | }
62 | return null
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/overflow-core/README.md:
--------------------------------------------------------------------------------
1 | # Overflow-Core
2 |
3 | 本模块为 mirai-core-api 的实现,用于对接 mirai 的接口。
4 |
5 | # Onebot
6 |
7 | 本模块同时包含 [onebot-sdk](https://github.com/cnlimiter/onebot-sdk) 和 [onebot-client](https://github.com/cnlimiter/onebot-client) 的 fork,
8 | 适配 kotlin 协程特性,增加来自 go-cqhttp、LLOnebot、NapCatQQ 等实现的额外接口调用。
9 |
--------------------------------------------------------------------------------
/overflow-core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm")
3 | kotlin("plugin.serialization")
4 | id("org.jetbrains.dokka")
5 | id("me.him188.kotlin-jvm-blocking-bridge")
6 | id("org.ajoberstar.grgit")
7 | }
8 |
9 | kotlin {
10 | optInForAllSourceSets("net.mamoe.mirai.utils.MiraiExperimentalApi")
11 | optInForAllSourceSets("net.mamoe.mirai.utils.MiraiInternalApi")
12 | optInForAllSourceSets("net.mamoe.mirai.LowLevelApi")
13 | optInForAllSourceSets("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
14 | optInForAllSourceSets("net.mamoe.mirai.console.util.ConsoleExperimentalApi")
15 | }
16 |
17 | tasks.register("dokkaJavadocJar") {
18 | group = "documentation"
19 | dependsOn(tasks.dokkaJavadoc)
20 | from(tasks.dokkaJavadoc.flatMap { it.outputDirectory })
21 | archiveClassifier.set("javadoc")
22 | }
23 |
24 | setupMavenCentralPublication {
25 | artifact(tasks.kotlinSourcesJar)
26 | artifact(tasks.getByName("dokkaJavadocJar"))
27 | }
28 |
29 | val miraiVersion = extra("miraiVersion") ?: "2.16.0"
30 |
31 | dependencies {
32 | compileOnly("net.mamoe:mirai-console:$miraiVersion")
33 | api("net.mamoe:mirai-core-api:$miraiVersion")
34 | api("net.mamoe:mirai-core-utils:$miraiVersion")
35 | api("com.google.code.gson:gson:2.10.1")
36 | api("org.slf4j:slf4j-api:2.0.5")
37 | api("org.java-websocket:Java-WebSocket:1.5.7")
38 | api("com.google.code.gson:gson:2.10.1")
39 | api("me.him188:kotlin-jvm-blocking-bridge-runtime:3.0.0-180.1")
40 |
41 | fun netty(s: String): Dependency? = api("io.netty:netty-$s:4.1.90.Final")
42 | netty("codec-http")
43 | netty("codec-socks")
44 | netty("transport")
45 |
46 | api(project(":overflow-core-api"))
47 |
48 | testImplementation("com.google.code.gson:gson:2.10.1")
49 | testImplementation("org.java-websocket:Java-WebSocket:1.5.7")
50 | testImplementation("net.mamoe:mirai-console:$miraiVersion")
51 | testImplementation("net.mamoe:mirai-console-terminal:$miraiVersion")
52 | testImplementation("ch.qos.logback:logback-classic:1.4.14")
53 | testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
54 | testImplementation(kotlin("test"))
55 | }
56 |
57 | tasks.test {
58 | useJUnitPlatform()
59 | }
60 |
61 | tasks {
62 | register("runConsole") {
63 | mainClass.set("RunConsoleKt")
64 | workingDir = File(project.projectDir, "run")
65 | classpath = sourceSets.test.get().runtimeClasspath.filter {
66 | val path = it.absolutePath.replace("\\", "/")
67 | !path.contains("ch.qos.logback/logback-")
68 | }
69 | standardInput = System.`in`
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/overflow-core/runConsole.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
16 |
17 |
18 | true
19 | true
20 | false
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/config/BotConfig.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.config
2 |
3 | import kotlinx.coroutines.Job
4 |
5 | /**
6 | * Description:
7 | * Author: cnlimiter
8 | * Date: 2022/10/1 17:05
9 | * Version: 1.0
10 | */
11 | class BotConfig(
12 | /**
13 | * websocket地址
14 | */
15 | val url: String = "ws://127.0.0.1:8080",
16 | /**
17 | * 反向 websocket 端口
18 | */
19 | val reversedPort: Int = -1,
20 | /**
21 | * token或者verifyKey鉴权
22 | */
23 | val token: String = "",
24 | /**
25 | * 是否开启鉴权
26 | */
27 | val isAccessToken: Boolean = false,
28 | /**
29 | * 是否禁止 Overflow 通过 get_credentials 获取凭证调用网络接口
30 | *
31 | * 该选项无法禁止其它插件自行调用网络接口
32 | */
33 | val noPlatform: Boolean = false,
34 | /**
35 | * 发送消息时,是否使用 CQ 码
36 | */
37 | val useCQCode: Boolean = false,
38 | /**
39 | * 重连尝试次数
40 | */
41 | val retryTimes: Int = 5,
42 | /**
43 | * 重连间隔 (毫秒)
44 | */
45 | val retryWaitMills: Long = 5_000L,
46 | /**
47 | * 重连休息时间 (毫秒)
48 | */
49 | val retryRestMills: Long = 60_000L,
50 | /**
51 | * 心跳包检测时间 (秒),设为0关闭检测
52 | */
53 | val heartbeatCheckSeconds: Int = 60,
54 | /**
55 | * 是否不接收群聊的 file 消息,使用 group_upload 事件作为群文件消息
56 | */
57 | val useGroupUploadEventForFileMessage: Boolean = false,
58 | /**
59 | * 是否抛弃所有在Bot成功连接之前传入的事件。
60 | *
61 | * 比如 go-cqhttp 会在连接之后,在 Overflow 获取到 Bot 信息之前,推送以前未收到的消息。如果开启此项,则丢弃这些消息
62 | */
63 | val dropEventsBeforeConnected: Boolean = true,
64 | val parentJob: Job? = null
65 | ) {
66 | val isInReverseMode get() = reversedPort in 1..65535
67 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/connection/IAdapter.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.connection
2 |
3 | import cn.evolvefield.onebot.client.handler.ActionHandler
4 | import cn.evolvefield.onebot.client.handler.EventBus
5 | import cn.evolvefield.onebot.client.handler.EventHolder
6 | import cn.evolvefield.onebot.client.util.ActionSendRequest
7 | import cn.evolvefield.onebot.client.util.OnebotException
8 | import cn.evolvefield.onebot.sdk.util.ignorable
9 | import com.google.gson.JsonParser
10 | import com.google.gson.JsonSyntaxException
11 | import kotlinx.coroutines.CoroutineScope
12 | import kotlinx.coroutines.launch
13 | import kotlinx.coroutines.sync.Mutex
14 | import kotlinx.coroutines.sync.withLock
15 | import kotlinx.coroutines.withTimeoutOrNull
16 | import net.mamoe.mirai.Bot
17 | import org.slf4j.Logger
18 |
19 | internal interface IAdapter {
20 | val scope: CoroutineScope
21 | val actionHandler: ActionHandler
22 | val logger: Logger
23 | val eventsHolder: MutableMap>
24 |
25 | fun onReceiveMessage(message: String) {
26 | try {
27 | val json = JsonParser.parseString(message).asJsonObject
28 | if (json.ignorable(META_EVENT, "") != HEART_BEAT) { // 过滤心跳
29 | logger.debug("[Recv] <-- {}", json.toString())
30 |
31 | if (json.has(API_RESULT_KEY)) { // 接口回调
32 | actionHandler.onReceiveActionResp(json)
33 | } else {
34 | val holder = EventBus.matchHandlers(message)
35 | val botId = holder.event.selfId
36 | if (Bot.findInstance(botId) == null) {
37 | val list = eventsHolder[botId] ?: run {
38 | mutableListOf().also {
39 | eventsHolder[botId] = it
40 | }
41 | }
42 | list.add(holder)
43 | return
44 | } else scope.launch { // 处理事件
45 | handleEvent(holder)
46 | }
47 | }
48 | }
49 | } catch (e: JsonSyntaxException) {
50 | logger.error("Json语法错误: {}", message)
51 | } catch (e: OnebotException) {
52 | logger.error("解析异常: {}", e.info)
53 | }
54 | }
55 |
56 | fun afterLoginInfoFetch(bot: cn.evolvefield.onebot.client.core.Bot) {
57 | val events = eventsHolder.remove(bot.id) ?: return
58 | if (bot.config.dropEventsBeforeConnected) return
59 | for (holder in events) scope.launch {
60 | handleEvent(holder)
61 | }
62 | }
63 |
64 | private suspend fun handleEvent(holder: EventHolder) { // 处理事件
65 | mutex.withLock {
66 | withTimeoutOrNull(processTimeout) {
67 | runCatching {
68 | holder.onReceive()
69 | }.onFailure {
70 | logger.error("处理 Onebot 事件时出现异常: ", it)
71 | }
72 | } ?: throw IllegalStateException("事件处理超时: ${holder.event.json}")
73 | }
74 | }
75 |
76 | fun unlockMutex() {
77 | runCatching {
78 | if (mutex.isLocked) mutex.unlock()
79 | if (ActionSendRequest.mutex.isLocked) ActionSendRequest.mutex.unlock()
80 | }
81 | }
82 |
83 | companion object {
84 | private const val META_EVENT = "meta_event_type"
85 | private const val API_RESULT_KEY = "echo"
86 | private const val HEART_BEAT = "heartbeat"
87 |
88 | val processTimeout by lazy {
89 | System.getProperty("overflow.timeout-process")?.toLongOrNull() ?: 20000L
90 | }
91 | val mutex = Mutex()
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/connection/platform-connection.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.connection
2 |
3 | import cn.evolvefield.onebot.client.core.Bot
4 | import kotlinx.coroutines.withTimeout
5 | import kotlin.time.Duration
6 |
7 | internal interface OneBotProducer {
8 | fun invokeOnClose(block: () -> Unit)
9 | fun close()
10 | fun setBotConsumer(consumer: suspend (Bot) -> Unit)
11 | suspend fun awaitNewBotConnection(duration: Duration = Duration.INFINITE): Bot?
12 | }
13 |
14 | internal class PositiveOneBotProducer(
15 | private val client: WSClient
16 | ): OneBotProducer {
17 | override fun invokeOnClose(block: () -> Unit) = TODO("客户端暂不支持断线重连")
18 |
19 | override fun close() = client.close()
20 |
21 | override fun setBotConsumer(consumer: suspend (Bot) -> Unit) {
22 | client.botConsumer = consumer
23 | }
24 |
25 | override suspend fun awaitNewBotConnection(duration: Duration): Bot? {
26 | return kotlin.runCatching {
27 | withTimeout(duration) {
28 | if (client.connectSuspend()) client.createBot() else null
29 | }
30 | }.getOrNull()
31 | }
32 | }
33 |
34 | internal class ReversedOneBotProducer(
35 | private val server: WSServer
36 | ): OneBotProducer {
37 | override fun invokeOnClose(block: () -> Unit) {
38 | server.closeHandler.add(block)
39 | }
40 |
41 | override fun close() = server.stop()
42 |
43 | override fun setBotConsumer(consumer: suspend (Bot) -> Unit) {
44 | server.botConsumer = consumer
45 | }
46 |
47 | override suspend fun awaitNewBotConnection(duration: Duration): Bot? {
48 | return kotlin.runCatching {
49 | withTimeout(duration) {
50 | server.awaitNewBot()
51 | }
52 | }.getOrNull()
53 | }
54 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/handler/EventBus.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.handler
2 |
3 | import cn.evolvefield.onebot.client.util.ListenerUtils
4 | import cn.evolvefield.onebot.sdk.event.Event
5 | import cn.evolvefield.onebot.sdk.event.UnsolvedEvent
6 | import cn.evolvefield.onebot.sdk.util.JsonHelper.applyJson
7 | import cn.evolvefield.onebot.sdk.util.JsonHelper.gson
8 | import cn.evolvefield.onebot.sdk.util.ignorable
9 | import com.google.gson.JsonParser
10 | import net.mamoe.mirai.utils.currentTimeSeconds
11 | import org.slf4j.LoggerFactory
12 | import top.mrxiaom.overflow.internal.contact.BotWrapper
13 | import kotlin.reflect.KClass
14 | import kotlin.reflect.KProperty1
15 | import kotlin.reflect.full.declaredMemberProperties
16 |
17 | object EventBus {
18 | private val logger = LoggerFactory.getLogger(EventBus::class.java)
19 | private val handlers: MutableMap, MutableList>> = hashMapOf()
20 |
21 | internal fun addListener(handler: Handler) {
22 | val list = handlers.getOrPut(handler.type) { mutableListOf() }
23 | list.add(handler)
24 | }
25 | internal inline fun listen(noinline block: suspend BotWrapper.(T) -> Unit) {
26 | addListener(BotHandler(T::class, null, block))
27 | }
28 | internal inline fun listenNormally(noinline block: suspend (T) -> Unit) {
29 | addListener(NormalHandler(T::class, null, block))
30 | }
31 | internal inline fun listen(subType: String, noinline block: suspend BotWrapper.(T) -> Unit) {
32 | val field = T::class.property("subType") ?: return
33 | addListener(BotHandler(T::class, field.check(subType), block))
34 | }
35 | internal inline fun listenNormally(subType: String, noinline block: suspend (T) -> Unit) {
36 | val field = T::class.property("subType") ?: return
37 | addListener(NormalHandler(T::class, field.check(subType), block))
38 | }
39 | private fun KClass.property(name: String): KProperty1? {
40 | val field = declaredMemberProperties
41 | .firstOrNull { it.name == name }
42 | if (field == null) {
43 | logger.warn("无法注册 ${java.name}: 找不到 $name")
44 | return null
45 | }
46 | return field
47 | }
48 | private fun KProperty1.check(subType: String): (T) -> Boolean {
49 | return { get(it)?.toString() == subType }
50 | }
51 |
52 | fun clear() {
53 | handlers.clear()
54 | }
55 |
56 | internal fun matchHandlers(
57 | message: String
58 | ): EventHolder {
59 | val messageType = ListenerUtils[message] // 获取消息对应的实体类型
60 | val json = JsonParser.parseString(message).asJsonObject
61 | if (messageType != null) {
62 | val bean = gson.fromJson(message, messageType.java) // 将消息反序列化为对象
63 | bean.applyJson(json)
64 | logger.debug(String.format("接收到上报消息内容:%s", bean.toString()))
65 | val executes = handlers[messageType] ?: emptyList()
66 | if (executes.isNotEmpty()) {
67 | return EventHolder(bean, executes)
68 | }
69 | }
70 | // 如果该事件未被监听,或者未定义事件类,将其定为 UnsolvedEvent
71 | val bean = UnsolvedEvent().also { it.jsonString = message }
72 | val executes = handlers[UnsolvedEvent::class] ?: emptyList()
73 | bean.postType = json.ignorable("post_type", "")
74 | bean.time = json.ignorable("time", currentTimeSeconds())
75 | bean.selfId = json.ignorable("self_id", 0L)
76 | bean.applyJson(json)
77 | return EventHolder(bean, executes)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/handler/EventHandler.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.handler
2 |
3 | import cn.evolvefield.onebot.sdk.event.Event
4 | import top.mrxiaom.overflow.internal.contact.BotWrapper
5 | import top.mrxiaom.overflow.internal.utils.bot
6 | import kotlin.reflect.KClass
7 | import kotlin.reflect.safeCast
8 |
9 | internal abstract class Handler(
10 | val type: KClass,
11 | ) {
12 | suspend fun onReceive(event: Event) {
13 | val e = type.safeCast(event)
14 | if (e != null) {
15 | receive(e)
16 | }
17 | }
18 |
19 | abstract suspend fun receive(e: T)
20 | }
21 | internal class BotHandler(
22 | type: KClass,
23 | private val condition: ((T) -> Boolean)?,
24 | private val block: suspend BotWrapper.(T) -> Unit
25 | ): Handler(type) {
26 | override suspend fun receive(e: T) {
27 | val bot = e.bot ?: return
28 | if (condition?.invoke(e) != false) {
29 | block.invoke(bot, e)
30 | }
31 | }
32 | }
33 | internal class NormalHandler(
34 | type: KClass,
35 | private val condition: ((T) -> Boolean)?,
36 | private val block: suspend (T) -> Unit
37 | ): Handler(type) {
38 | override suspend fun receive(e: T) {
39 | if (condition?.invoke(e) != false) {
40 | block.invoke(e)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/handler/EventHolder.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.handler
2 |
3 | import cn.evolvefield.onebot.sdk.event.Event
4 |
5 | internal class EventHolder(
6 | val event: Event,
7 | val handlers: List>,
8 | ) {
9 | suspend fun onReceive() {
10 | for (handler in handlers) {
11 | handler.onReceive(event)
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/util/ActionSendRequest.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.util
2 |
3 | import cn.evolvefield.onebot.client.core.Bot
4 | import cn.evolvefield.onebot.sdk.util.ignorable
5 | import cn.evolvefield.onebot.sdk.util.ignorableArray
6 | import cn.evolvefield.onebot.sdk.util.ignorableObject
7 | import com.google.gson.JsonArray
8 | import com.google.gson.JsonObject
9 | import kotlinx.coroutines.CompletableDeferred
10 | import kotlinx.coroutines.Job
11 | import kotlinx.coroutines.TimeoutCancellationException
12 | import kotlinx.coroutines.sync.Mutex
13 | import kotlinx.coroutines.sync.withLock
14 | import kotlinx.coroutines.withTimeout
15 | import org.java_websocket.WebSocket
16 | import org.slf4j.Logger
17 | import top.mrxiaom.overflow.action.ActionContext
18 | import java.util.*
19 |
20 | /**
21 | * Description:
22 | * Author: cnlimiter
23 | * Date: 2022/9/14 15:06
24 | * Version: 1.0
25 | */
26 | /**
27 | * @param channel [WebSocket]
28 | * @param requestTimeout Request Timeout
29 | */
30 | internal class ActionSendRequest(
31 | private val bot: Bot,
32 | private val context: ActionContext,
33 | parent: Job?,
34 | private val logger: Logger,
35 | private val channel: WebSocket,
36 | private val requestTimeout: Long
37 | ) {
38 | private val resp = CompletableDeferred(parent)
39 | /**
40 | * @param req Request json data
41 | * @return Response json data
42 | */
43 | @Throws(TimeoutCancellationException::class, ActionFailedException::class)
44 | suspend fun send(req: JsonObject): JsonObject {
45 | val resp = mutex.withLock {
46 | kotlin.runCatching {
47 | withTimeout(requestTimeout) {
48 | logger.debug("[Send] --> {}", req.toString())
49 | channel.send(req.toString())
50 | resp.await()
51 | }
52 | }.onFailure { resp.cancel() }.getOrThrow()
53 | }
54 | if (resp.ignorable("status", if (context.ignoreStatus) "" else "failed") == "failed") {
55 | val extra = runCatching {
56 | val params = req.ignorableObject("params") { JsonObject() }
57 | val messages = params.ignorableArray("message") { JsonArray() }
58 | val extraFileTypes = messages.filter {
59 | listOf("image", "record", "video").contains(it.asJsonObject?.get("type")?.asString)
60 | && it.asJsonObject?.has("file") == true
61 | }.mapNotNull {
62 | val file = it.asJsonObject!!["file"].asString
63 | if (file.startsWith("base64://")) {
64 | val bytes = Base64.getDecoder().decode(file.replace("base64://", ""))
65 | "msgFileType=${bytes.fileType}"
66 | } else null
67 | }
68 | return@runCatching if (extraFileTypes.isNotEmpty()) {
69 | "; " + extraFileTypes.joinToString(", ")
70 | } else ""
71 | }.getOrElse { "" }
72 | throw ActionFailedException(
73 | action = context.action,
74 | app = "${bot.appName} v${bot.appVersion}",
75 | msg = "${resp.ignorable("message", "")}$extra",
76 | json = resp
77 | )
78 | }
79 | return resp
80 | }
81 |
82 | /**
83 | * @param resp Response json data
84 | */
85 | fun onCallback(resp: JsonObject) {
86 | this.resp.complete(resp)
87 | }
88 |
89 | companion object {
90 | val mutex = Mutex()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/util/Exceptions.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.client.util
2 |
3 | import com.google.gson.JsonObject
4 |
5 | class ActionFailedException(
6 | val action: String,
7 | val app: String,
8 | val msg: String,
9 | val json: JsonObject
10 | ): OnebotException("[$action] 请求失败: app=$app, message=$msg,\n retJson=$json")
11 |
12 | open class OnebotException(
13 | val info: String
14 | ): IllegalStateException(info)
15 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/client/util/Files.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2022 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.ink/mamoe/mirai/blob/dev/LICENSE
8 | */
9 |
10 | @file:JvmMultifileClass
11 | @file:JvmName("MiraiUtils")
12 |
13 | package cn.evolvefield.onebot.client.util
14 |
15 | private class FileType(
16 | signature: String,
17 | val requiredHeaderSize: Int,
18 | val formatName: String
19 | ) {
20 | val signatureRegex = Regex(signature, RegexOption.IGNORE_CASE)
21 | }
22 |
23 | /**
24 | * 文件头和文件类型列表
25 | */
26 | private val FILE_TYPES: List = listOf(
27 | FileType("^FFD8FF", 3, "jpg"),
28 | FileType("^89504E47", 4, "png"),
29 | FileType("^47494638", 4, "gif"),
30 | FileType("^424D", 3, "bmp"),
31 | FileType("^2321414D52", 5, "amr"),
32 | FileType("^02232153494C4B5F5633", 10, "silk"),
33 | FileType("^([a-zA-Z0-9]{8})66747970", 8, "mp4"),
34 |
35 | FileType("49492A00", 8, "tif"), // client doesn't support
36 | FileType("52494646", 8, "webp"), // pc client doesn't support
37 | FileType("57415645", 8, "wav"), // server doesn't support
38 | )
39 |
40 | /**
41 | * 在 [getFileType] 需要的 [ByteArray] 长度
42 | */
43 | val COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE: Int by lazy { FILE_TYPES.maxOf { it.requiredHeaderSize } }
44 |
45 | /*
46 |
47 | startsWith("FFD8") -> "jpg"
48 | startsWith("89504E47") -> "png"
49 | startsWith("47494638") -> "gif"
50 | startsWith("424D") -> "bmp"
51 | */
52 |
53 | val ByteArray.fileType: String?
54 | get() = getFileTypeByHeader(copyOf(COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE))
55 |
56 | /**
57 | * 根据文件头获取文件类型
58 | */
59 | fun getFileTypeByHeader(fileHeader: ByteArray): String? {
60 | val hex = fileHeader.toUHexString(
61 | "",
62 | length = COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE.coerceAtMost(fileHeader.size)
63 | )
64 | FILE_TYPES.forEach { t ->
65 | if (hex.contains(t.signatureRegex)) {
66 | return t.formatName
67 | }
68 | }
69 | return null
70 | }
71 |
72 | fun ByteArray.toUHexString(
73 | separator: String = " ",
74 | offset: Int = 0,
75 | length: Int = this.size - offset
76 | ): String {
77 | this.checkOffsetAndLength(offset, length)
78 | if (length == 0) {
79 | return ""
80 | }
81 | val lastIndex = offset + length
82 | return buildString(length * 2) {
83 | this@toUHexString.forEachIndexed { index, it ->
84 | if (index in offset until lastIndex) {
85 | val ret = it.toUByte().toString(16).uppercase()
86 | if (ret.length == 1) append('0')
87 | append(ret)
88 | if (index < lastIndex - 1) append(separator)
89 | }
90 | }
91 | }
92 | }
93 |
94 | fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {
95 | require(offset >= 0) { "offset shouldn't be negative: $offset" }
96 | require(length >= 0) { "length shouldn't be negative: $length" }
97 | require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" }
98 | }
99 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/action/ActionPath.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.action
2 |
3 | interface ActionPath {
4 | val path: String
5 | }
6 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/action/ActionResults.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.action
2 |
3 | import com.google.gson.JsonElement
4 | import com.google.gson.annotations.SerializedName
5 | import java.util.*
6 |
7 | open class JsonContainer {
8 | internal lateinit var element: JsonElement
9 | val json: JsonElement
10 | get() = element
11 | }
12 |
13 | data class ActionRaw(
14 | @SerializedName("status")
15 | val status: String,
16 | @SerializedName("retCode")
17 | val retCode: Int,
18 | @SerializedName("echo")
19 | val echo: String? = null,
20 | ): JsonContainer()
21 |
22 | data class ActionData (
23 | @SerializedName("status")
24 | val status: String,
25 | @SerializedName("retcode")
26 | val retCode: Int,
27 | @SerializedName("data")
28 | val data: T?,
29 | @SerializedName("echo")
30 | val echo: String? = null
31 | ): JsonContainer()
32 |
33 | data class ActionList(
34 | @SerializedName("status")
35 | val status: String,
36 | @SerializedName("retcode")
37 | val retCode: Int,
38 | @SerializedName("data")
39 | val data: LinkedList,
40 | @SerializedName("echo")
41 | val echo: String? = null
42 | ): JsonContainer()
43 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/entity/Anonymous.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.entity
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class Anonymous {
6 | @SerializedName("id")
7 | var id = 0L
8 | @SerializedName("name")
9 | var name: String = ""
10 | @SerializedName("flag")
11 | var flag: String = ""
12 | }
13 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/entity/File.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.entity
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /**
6 | * 文件实体
7 | */
8 | class File {
9 | @SerializedName("id")
10 | var id = ""
11 | @SerializedName("name")
12 | var name = ""
13 | @SerializedName("size")
14 | var size = 0L
15 | @SerializedName("busid")
16 | var busId = 0L
17 | @SerializedName("url")
18 | var url = ""
19 | }
20 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/entity/GroupSender.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.entity
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupSender {
6 | @SerializedName("user_id")
7 | var userId = ""
8 | @SerializedName("nickname")
9 | var nickname = ""
10 | @SerializedName("card")
11 | var card = ""
12 | @SerializedName("role")
13 | var role = "member"
14 | }
15 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/entity/MsgId.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.entity
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class MsgId {
6 | @SerializedName("message_id")
7 | var messageId: Int? = null
8 | }
9 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/entity/PrivateSender.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.entity
2 |
3 | import cn.evolvefield.onebot.sdk.util.json.MessageEventAdapter
4 | import com.google.gson.annotations.JsonAdapter
5 |
6 | @JsonAdapter(MessageEventAdapter.PrivateSenderAdapter::class)
7 | class PrivateSender {
8 | var userId = 0L
9 | var groupId = 0L // go-cqhttp
10 | var nickname = ""
11 | var sex = ""
12 | var age = 0
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/entity/Sender.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.entity
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class Sender {
6 | @SerializedName("user_id")
7 | var userId = ""
8 | @SerializedName("nickname")
9 | var nickname = ""
10 | @SerializedName("card")
11 | var card = ""
12 | @SerializedName("sex")
13 | var sex = ""
14 | @SerializedName("age")
15 | var age = 0
16 | @SerializedName("area")
17 | var area = ""
18 | @SerializedName("level")
19 | var level = ""
20 | @SerializedName("role")
21 | var role = ""
22 | @SerializedName("title")
23 | var title = ""
24 | }
25 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/Event.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event
2 |
3 | import cn.evolvefield.onebot.sdk.action.JsonContainer
4 | import com.google.gson.annotations.SerializedName
5 |
6 | /**
7 | * 事件上报
8 | *
9 | * @author cnlimiter
10 | */
11 | open class Event : JsonContainer() {
12 | @SerializedName("post_type")
13 | var postType: String = ""
14 | @SerializedName("time")
15 | var time: Long = 0
16 | @SerializedName("self_id")
17 | var selfId: Long = 0
18 |
19 | /**
20 | * 自动转换毫秒时间戳(如果是)为秒时间戳,
21 | * 仅支持2000年以后的毫秒时间戳,2000年以前的认定为秒时间戳
22 | */
23 | fun timeInSecond(): Long {
24 | return if (time < 946656000000L) time
25 | else time / 1000L
26 | }
27 |
28 | fun time(): Int = timeInSecond().toInt()
29 | }
30 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/UnsolvedEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event
2 |
3 | /**
4 | * 未成功解析的事件
5 | *
6 | * @author MrXiaoM
7 | */
8 | class UnsolvedEvent : Event() {
9 | var jsonString: String = "{}"
10 | }
11 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/message/GroupMessageEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.message
2 |
3 | import cn.evolvefield.onebot.sdk.entity.Anonymous
4 | import cn.evolvefield.onebot.sdk.entity.GroupSender
5 | import cn.evolvefield.onebot.sdk.util.json.MessageEventAdapter
6 | import com.google.gson.annotations.JsonAdapter
7 |
8 | @JsonAdapter(MessageEventAdapter::class)
9 | class GroupMessageEvent : MessageEvent() {
10 | var messageId: Int = 0
11 | var subType: String = ""
12 | var groupId = 0L
13 | var anonymous: Anonymous? = null
14 | var sender: GroupSender? = null
15 | }
16 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/message/MessageEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.message
2 |
3 | import cn.evolvefield.onebot.sdk.event.Event
4 |
5 | open class MessageEvent : Event() {
6 | var messageType = ""
7 | var userId = 0L
8 | var isJsonMessage = false
9 | var message = ""
10 | var rawMessage = ""
11 | var font = 0
12 | }
13 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/message/PrivateMessageEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.message
2 |
3 | import cn.evolvefield.onebot.sdk.entity.PrivateSender
4 | import cn.evolvefield.onebot.sdk.util.json.MessageEventAdapter
5 | import com.google.gson.annotations.JsonAdapter
6 |
7 | /**
8 | * 私聊消息
9 | *
10 | * @author cnlimiter
11 | */
12 | @JsonAdapter(MessageEventAdapter::class)
13 | class PrivateMessageEvent : MessageEvent() {
14 | var messageId = 0
15 | var subType = ""
16 | lateinit var sender: PrivateSender
17 | var groupId = 0L // OpenShamrock
18 |
19 | /**
20 | * go-cqhttp: [docs](https://docs.go-cqhttp.org/reference/data_struct.html#post-message-tempsource)
21 | * Group(0),
22 | * Consultation(1),
23 | * Seek(2),
24 | * QQMovie(3),
25 | * HotChat(4),
26 | * VerifyMsg(6),
27 | * Discussion(7),
28 | * Dating(8),
29 | * Contact(9),
30 | * Unknown(-1)
31 | */
32 | var tempSource = 0
33 | var fromNick = "" // OpenShamrock
34 | }
35 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/meta/HeartbeatMetaEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.meta
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class HeartbeatMetaEvent : MetaEvent() {
6 | init {
7 | metaEventType = "heartbeat"
8 | }
9 | @SerializedName("status")
10 | val status: Any = ""
11 | @SerializedName("interval")
12 | val interval: Long = 0
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/meta/LifecycleMetaEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.meta
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class LifecycleMetaEvent : MetaEvent() {
6 | init {
7 | metaEventType = "lifecycle"
8 | }
9 | @SerializedName("sub_type")
10 | var subType = "" // enable、disable、connect
11 | }
12 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/meta/MetaEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.meta
2 |
3 | import cn.evolvefield.onebot.sdk.event.Event
4 | import com.google.gson.annotations.SerializedName
5 |
6 | open class MetaEvent : Event() {
7 | init {
8 | postType = "meta_event"
9 | }
10 | @SerializedName("meta_event_type")
11 | var metaEventType = ""
12 | }
13 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/NoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice
2 |
3 | import cn.evolvefield.onebot.sdk.event.Event
4 | import com.google.gson.annotations.SerializedName
5 |
6 | open class NoticeEvent : Event() {
7 | @SerializedName("notice_type")
8 | var noticeType = ""
9 | @SerializedName("user_id")
10 | var userId = 0L
11 | }
12 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/NotifyNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class NotifyNoticeEvent : NoticeEvent() {
6 | @SerializedName("sub_type")
7 | var subType = ""
8 | @SerializedName("operator_id")
9 | var operatorId = 0L
10 | @SerializedName("target_id")
11 | var targetId = 0L
12 | @SerializedName("group_id")
13 | var groupId = 0L
14 | @SerializedName("title")
15 | val title = ""
16 | val realOperatorId: Long
17 | get() = operatorId.takeIf { it > 0 } ?: userId
18 | }
19 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/friend/FriendAddNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.friend
2 |
3 | import cn.evolvefield.onebot.sdk.event.request.RequestEvent
4 |
5 | class FriendAddNoticeEvent : RequestEvent()
6 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/friend/PrivateMsgDeleteNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.friend
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class PrivateMsgDeleteNoticeEvent : NoticeEvent() {
7 | @SerializedName("operator_id")
8 | var operatorId = 0L
9 | @SerializedName("message_id")
10 | var msgId = 0L
11 | }
12 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupAdminNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupAdminNoticeEvent : NoticeEvent() {
7 | /**
8 | * set、unset
9 | * 事件子类型, 分别表示设置和取消管理
10 | */
11 | @SerializedName( "sub_type")
12 | var subType = ""
13 |
14 | /**
15 | * 群号
16 | */
17 | @SerializedName( "group_id")
18 | var groupId = 0L
19 |
20 | // 管理员QQ号 userId 已在 NoticeEvent 中实现
21 | }
22 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupBanNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupBanNoticeEvent : NoticeEvent() {
7 | @SerializedName("sub_type")
8 | var subType = ""
9 | @SerializedName("group_id")
10 | var groupId = 0L
11 | @SerializedName("operator_id")
12 | var operatorId = 0L
13 | @SerializedName("duration")
14 | var duration = 0L
15 | }
16 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupCardChangeNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupCardChangeNoticeEvent : NoticeEvent() {
7 | @SerializedName("card_new")
8 | var cardNew = ""
9 | @SerializedName("card_old")
10 | var cardOld = ""
11 | @SerializedName("group_id")
12 | var groupId = 0L
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupDecreaseNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupDecreaseNoticeEvent : NoticeEvent() {
7 | @SerializedName("sub_type")
8 | var subType = ""
9 | @SerializedName("group_id")
10 | var groupId = 0L
11 | @SerializedName("operator_id")
12 | var operatorId = 0L
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupEssenceNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupEssenceNoticeEvent : NoticeEvent() {
7 | /**
8 | * add,delete 添加为add,移出为delete
9 | */
10 | @SerializedName("sub_type")
11 | var subType = ""
12 |
13 | /**
14 | * 消息发送者ID
15 | */
16 | @SerializedName("sender_id")
17 | var senderId: Long = 0L
18 |
19 | /**
20 | * 操作者ID
21 | */
22 | @SerializedName("operator_id")
23 | var operatorId: Long = 0L
24 |
25 | /**
26 | * 消息ID
27 | */
28 | @SerializedName("message_id")
29 | var messageId: Int = 0
30 |
31 | /**
32 | * 群号
33 | */
34 | @SerializedName( "group_id")
35 | var groupId = 0L
36 | }
37 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupHonorChangeNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupHonorChangeNoticeEvent : NoticeEvent() {
7 | @SerializedName("sub_type")
8 | var subType = ""
9 | @SerializedName("group_id")
10 | var groupId = 0L
11 | @SerializedName("honor_type")
12 | var honorType = ""
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupIncreaseNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupIncreaseNoticeEvent : NoticeEvent() {
7 | @SerializedName("sub_type")
8 | var subType = ""
9 | @SerializedName("group_id")
10 | var groupId = 0L
11 | @SerializedName("operator_id")
12 | var operatorId = 0L
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupLuckyKingNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupLuckyKingNoticeEvent : NoticeEvent() {
7 | @SerializedName("sub_type")
8 | var subType = ""
9 | @SerializedName("group_id")
10 | var groupId = 0L
11 | @SerializedName("target_id")
12 | var targetId = 0L
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupMsgDeleteNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupMsgDeleteNoticeEvent : NoticeEvent() {
7 | @SerializedName("group_id")
8 | var groupId = 0L
9 | @SerializedName("operator_id")
10 | var operatorId = 0L
11 | @SerializedName("message_id")
12 | var msgId = 0L
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupNameChangeNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupNameChangeNoticeEvent : NoticeEvent() {
7 | @SerializedName("name")
8 | var name = ""
9 | @SerializedName("group_id")
10 | var groupId = 0L
11 | @SerializedName("operator_id")
12 | var operatorId = 0L
13 | }
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/group/GroupUploadNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.group
2 |
3 | import cn.evolvefield.onebot.sdk.entity.File
4 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
5 | import com.google.gson.annotations.SerializedName
6 |
7 | class GroupUploadNoticeEvent : NoticeEvent() {
8 | @SerializedName("group_id")
9 | var groupId = 0L
10 | @SerializedName("file")
11 | var file: File? = null
12 | }
13 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/misc/GroupMsgEmojiLikeNotice.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.misc
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 | import java.util.*
6 |
7 | class GroupMsgEmojiLikeNotice : NoticeEvent() {
8 | @SerializedName("group_id")
9 | var groupId = 0L
10 | @SerializedName("message_id")
11 | var messageId = 0
12 | @SerializedName("likes")
13 | var likes = LinkedList()
14 |
15 | class Like {
16 | @SerializedName("emoji_id")
17 | var emojiId = ""
18 | @SerializedName("count")
19 | var count = 0
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/misc/GroupReactionNotice.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.misc
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class GroupReactionNotice : NoticeEvent() {
7 | @SerializedName("sub_type") // add, remove
8 | var subType = ""
9 | @SerializedName("group_id")
10 | var groupId = 0L
11 | @SerializedName("message_id")
12 | var messageId = 0
13 | @SerializedName("operator_id")
14 | var operatorId = 0L
15 | @SerializedName("code", alternate = ["icon", "icon_id"])
16 | var code = ""
17 | @SerializedName("count")
18 | var count = 0
19 | }
20 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/notice/misc/ReceiveOfflineFilesNoticeEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.notice.misc
2 |
3 | import cn.evolvefield.onebot.sdk.event.notice.NoticeEvent
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class ReceiveOfflineFilesNoticeEvent : NoticeEvent() {
7 | @SerializedName("file")
8 | var file: File? = null
9 |
10 | /**
11 | * 文件对象
12 | */
13 | class File {
14 | @SerializedName("name")
15 | var name = ""
16 | @SerializedName("size")
17 | var size = 0L
18 | @SerializedName("url")
19 | var url = ""
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/request/FriendAddRequestEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.request
2 |
3 | class FriendAddRequestEvent : RequestEvent()
4 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/request/GroupAddRequestEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.request
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupAddRequestEvent : RequestEvent() {
6 | @SerializedName("sub_type")
7 | var subType = ""
8 | @SerializedName("group_id")
9 | var groupId = 0L
10 | @SerializedName("invitor_id")
11 | var invitorId = 0L
12 | }
13 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/event/request/RequestEvent.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.event.request
2 |
3 | import cn.evolvefield.onebot.sdk.event.Event
4 | import com.google.gson.annotations.SerializedName
5 |
6 | open class RequestEvent : Event() {
7 | @SerializedName("request_type")
8 | var requestType = ""
9 | @SerializedName("user_id")
10 | var userId = 0L
11 | @SerializedName(value = "comment", alternate = ["message"])
12 | var comment = ""
13 | @SerializedName("flag")
14 | var flag = ""
15 | }
16 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/contact/FriendInfoResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.contact
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class FriendInfoResp {
6 | @SerializedName("user_id")
7 | var userId = 0L
8 | @SerializedName(value = "nickname", alternate = ["user_name"])
9 | var nickname = ""
10 | @SerializedName(value = "remark", alternate = ["user_remark"])
11 | var remark = ""
12 | @SerializedName("sex")
13 | var sex = "unknown"
14 | @SerializedName("age")
15 | var age = 0
16 | @SerializedName("email")
17 | var email = ""
18 | @SerializedName("level")
19 | var level = 0
20 | }
21 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/contact/LoginInfoResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.contact
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class LoginInfoResp {
6 | @SerializedName("user_id")
7 | var userId = 0L
8 | @SerializedName("nickname")
9 | var nickname = ""
10 | }
11 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/contact/StrangerInfoResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.contact
2 |
3 | import com.google.gson.JsonObject
4 | import com.google.gson.annotations.SerializedName
5 |
6 | class StrangerInfoResp {
7 | /**
8 | * QQ 号
9 | */
10 | @SerializedName("user_id")
11 | var userId = 0L
12 | /**
13 | * 昵称
14 | */
15 | @SerializedName("nickname")
16 | var nickname = ""
17 | /**
18 | * 性别 male 或 female 或 unknown
19 | */
20 | @SerializedName("sex")
21 | var sex = ""
22 | /**
23 | * 年龄
24 | */
25 | @SerializedName("age")
26 | var age = 0
27 | /**
28 | * qid 身份卡
29 | */
30 | @SerializedName("qid")
31 | var qid = ""
32 | /**
33 | * 等级
34 | */
35 | @SerializedName("level", alternate = ["qqLevel", "qq_level"])
36 | var level = 0
37 | /**
38 | * 在线天数?我猜的(
39 | */
40 | @SerializedName("login_days")
41 | var loginDays = 0
42 |
43 | /**
44 | * 好友分组ID(NapCat)
45 | */
46 | @SerializedName("categoryId")
47 | var friendGroupId = 0
48 | /**
49 | * 个性签名(NapCat、LLOnebot)
50 | */
51 | @SerializedName("longNick", alternate = ["long_nick"])
52 | var sign = ""
53 | /**
54 | * 电子邮箱(NapCat)
55 | */
56 | @SerializedName("eMail")
57 | var email = ""
58 |
59 | @SerializedName("ext")
60 | var ext = JsonObject()
61 | }
62 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/ext/CreateGroupFileFolderResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.ext
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /**
6 | * LLOnebot
7 | */
8 | class CreateGroupFileFolderResp {
9 | @SerializedName("folder_id")
10 | var folderId = ""
11 | }
12 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/ext/GetFileResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.ext
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GetFileResp {
6 | @SerializedName("file")
7 | var file: String = ""
8 | @SerializedName("file_name")
9 | var fileName: String = ""
10 | @SerializedName("file_size")
11 | var fileSize: Long = 0
12 | @SerializedName("base64")
13 | var base64: String? = null
14 | }
15 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/ext/SetGroupReactionResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.ext
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /**
6 | * AstralGocq
7 | */
8 | class SetGroupReactionResp {
9 | @SerializedName("message_id")
10 | var messageId = 0L
11 | @SerializedName("group_id")
12 | var groupId = 0L
13 | @SerializedName("message_seq")
14 | var messageSequence = 0L
15 | }
16 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/ext/UploadGroupFileResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.ext
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | /**
6 | * LLOnebot
7 | */
8 | class UploadGroupFileResp {
9 | @SerializedName("file_id")
10 | var fileId = ""
11 | }
12 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/EssenceMsgResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class EssenceMsgResp {
6 | @SerializedName("sender_id")
7 | var senderId = 0L
8 | @SerializedName("sender_nick")
9 | var senderNick = ""
10 | @SerializedName("sender_time")
11 | var senderTime = 0L
12 | @SerializedName("operator_id")
13 | var operatorId = 0L
14 | @SerializedName("operator_nick")
15 | var operatorNick = ""
16 | @SerializedName("operator_time")
17 | var operatorTime = ""
18 | @SerializedName("message_id")
19 | var messageId = 0
20 | }
21 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/ForwardMsgResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import cn.evolvefield.onebot.sdk.util.json.ForwardMsgAdapter
4 | import com.google.gson.annotations.JsonAdapter
5 | import com.google.gson.annotations.SerializedName
6 |
7 | /**
8 | * @author MrXiaoM
9 | */
10 | @JsonAdapter(ForwardMsgAdapter::class)
11 | class ForwardMsgResp {
12 | /**
13 | * 消息节点列表
14 | */
15 | var message: List = mutableListOf()
16 | /**
17 | * 消息节点
18 | */
19 | class Node {
20 | var time = 0
21 | var messageType = ""
22 | var messageId = 0
23 | var realId = 0
24 | var peerId = 0L
25 | var targetId = 0L
26 | var sender: Sender? = null
27 | var message = ""
28 | }
29 |
30 | /**
31 | * 消息发送者
32 | */
33 | class Sender {
34 | @SerializedName("user_id")
35 | var userId = 0L
36 | @SerializedName("nickname")
37 | var nickname = ""
38 | @SerializedName("sex")
39 | var sex = ""
40 | @SerializedName("age")
41 | var age = 0
42 | @SerializedName("uid")
43 | var uid = ""
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GetHistoryMsgResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GetHistoryMsgResp {
6 | /**
7 | * 发送时间
8 | */
9 | @SerializedName("time")
10 | var time = 0
11 | /**
12 | * 消息类型
13 | */
14 | @SerializedName("message_type")
15 | var messageType = 0
16 | /**
17 | * 消息id
18 | */
19 | @SerializedName("message_id")
20 | var messageId = 0
21 | /**
22 | * 消息真实id
23 | */
24 | @SerializedName("real_id")
25 | var realId = 0
26 | /**
27 | * 发送者
28 | */
29 | @SerializedName("sender")
30 | var sender: Sender? = null
31 | /**
32 | * 消息内容
33 | */
34 | @SerializedName("message")
35 | var message = ""
36 | /**
37 | * 群号
38 | */
39 | @SerializedName("group_id")
40 | var groupId = 0L
41 | /**
42 | * 消息目标(私聊)
43 | */
44 | @SerializedName("target_id")
45 | var targetId = 0L
46 | /**
47 | * 消息接收者,群聊是群号,私聊时是目标QQ
48 | */
49 | @SerializedName("peer_id")
50 | var peerId = 0L
51 |
52 | /**
53 | * sender信息
54 | */
55 | class Sender {
56 | @SerializedName("user_id")
57 | var userId = 0L
58 | @SerializedName("nickname")
59 | var nickname = ""
60 | @SerializedName("sex")
61 | var sex = ""
62 | @SerializedName("age")
63 | var age = 0
64 | @SerializedName("uid")
65 | var uid = ""
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GetMsgResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import cn.evolvefield.onebot.sdk.entity.Sender
4 | import cn.evolvefield.onebot.sdk.util.json.MsgAdapter
5 | import com.google.gson.annotations.JsonAdapter
6 |
7 | @JsonAdapter(MsgAdapter::class)
8 | class GetMsgResp {
9 | var messageId = 0
10 | var realId = 0
11 | lateinit var sender: Sender
12 | var time = 0
13 | var isJsonMessage = false
14 | var message = ""
15 | var rawMessage = ""
16 | var peerId = 0L
17 | var groupId = 0L
18 | var targetId = 0L
19 | }
20 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupAtAllRemainResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupAtAllRemainResp {
6 | /**
7 | * 是否可以 @全体成员
8 | */
9 | @SerializedName("can_at_all")
10 | var canAtAll = false
11 | /**
12 | * 群内所有管理当天剩余 @全体成员 次数
13 | */
14 | @SerializedName("remain_at_all_count_for_group")
15 | var remainAtAllCountForGroup = 0
16 | /**
17 | * Bot 当天剩余 @全体成员 次数
18 | */
19 | @SerializedName("remain_at_all_count_for_uin")
20 | var remainAtAllCountForUin = 0
21 | }
22 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupDataResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | open class GroupDataResp {
6 | @SerializedName("group_id")
7 | var groupId = 0L
8 | @SerializedName("group_name")
9 | var groupName = ""
10 | }
11 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupFileUrlResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupFileUrlResp {
6 | @SerializedName("url")
7 | var url = ""
8 | }
9 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupFilesResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import cn.evolvefield.onebot.sdk.util.json.GroupFilesAdapter
4 | import com.google.gson.annotations.JsonAdapter
5 | import com.google.gson.annotations.SerializedName
6 |
7 | class GroupFilesResp {
8 |
9 | var files: List? = null
10 |
11 | var folders: List? = null
12 |
13 | /**
14 | * 群文件
15 | */
16 | @JsonAdapter(GroupFilesAdapter.Files::class)
17 | class Files {
18 | var fileId = ""
19 | var fileName = ""
20 | var busid = 0
21 | var fileSize = 0L
22 | var uploadTime = 0L
23 | var deadTime = 0L
24 | var modifyTime = 0L
25 | var downloadTimes = 0
26 | var uploader = 0L
27 | var uploaderName = ""
28 | var md5: String? = null
29 | var sha1: String? = null
30 | var sha3: String? = null
31 | }
32 |
33 | /**
34 | * 群文件夹
35 | */
36 | class Folders {
37 | @SerializedName("folder_id")
38 | var folderId = ""
39 | @SerializedName("folder_name")
40 | var folderName = ""
41 | @SerializedName("create_time")
42 | var createTime = 0L
43 | @SerializedName("creator")
44 | var creator = 0L
45 | @SerializedName("creator_name")
46 | var creatorName = ""
47 | @SerializedName("total_file_count")
48 | var totalFileCount = 0
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupHonorInfoResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupHonorInfoResp {
6 | @SerializedName("group_id")
7 | var groupId = 0L
8 | @SerializedName("current_talkative")
9 | private val currentTalkative: CurrentTalkative? = null
10 | @SerializedName("talkative_list")
11 | var talkativeList: List = mutableListOf()
12 | @SerializedName("performer_list")
13 | var performerList: List = mutableListOf()
14 | @SerializedName("legend_list")
15 | var legendList: List = mutableListOf()
16 | @SerializedName("strong_newbie_list")
17 | var strongNewbieList: List = mutableListOf()
18 | @SerializedName("emotion_list")
19 | var emotionList: List = mutableListOf()
20 |
21 | /**
22 | * 活跃天数
23 | */
24 | class CurrentTalkative {
25 | @SerializedName("user_id")
26 | var userId = 0L
27 |
28 | @SerializedName("nickname")
29 | var nickname = ""
30 |
31 | @SerializedName("avatar")
32 | var avatar = ""
33 |
34 | @SerializedName("day_count")
35 | var dayCount = 0
36 | }
37 |
38 | /**
39 | * 其它荣耀
40 | */
41 | class OtherHonor {
42 | @SerializedName("user_id")
43 | var userId = 0L
44 |
45 | @SerializedName("nickname")
46 | var nickname = ""
47 |
48 | @SerializedName("avatar")
49 | var avatar = ""
50 |
51 | @SerializedName("description")
52 | var description = ""
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupInfoResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupInfoResp : GroupDataResp() {
6 | @SerializedName("group_all_shut") // NapCat,全员禁言,-1 开启,0 关闭
7 | var groupAllShut = 0
8 | @SerializedName("group_memo")
9 | var groupMemo = ""
10 | @SerializedName("group_create_time")
11 | var groupCreateTime = 0
12 | @SerializedName("group_level")
13 | var groupLevel = 0
14 | @SerializedName("member_count")
15 | var memberCount: Int? = null
16 | @SerializedName("max_member_count")
17 | var maxMemberCount: Int? = null
18 | }
19 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupMemberInfoResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupMemberInfoResp {
6 | @SerializedName("group_id")
7 | var groupId = 0L
8 | @SerializedName("user_id")
9 | var userId = 0L
10 | @SerializedName("nickname")
11 | var nickname = ""
12 | @SerializedName("card")
13 | var card = ""
14 | @SerializedName("sex")
15 | var sex = ""
16 | @SerializedName("age")
17 | var age = 0
18 | @SerializedName("area")
19 | var area = ""
20 | @SerializedName("join_time")
21 | var joinTime = 0
22 | @SerializedName("last_sent_time")
23 | var lastSentTime = 0
24 | @SerializedName("level")
25 | var level = "0"
26 | @SerializedName("qq_level")
27 | var qqLevel = 0
28 | @SerializedName("role")
29 | var role = "member"
30 | @SerializedName("unfriendly")
31 | var unfriendly = false
32 | @SerializedName("title")
33 | var title = ""
34 | @SerializedName("title_expire_time")
35 | var titleExpireTime = 0L
36 | @SerializedName("card_changeable")
37 | var cardChangeable = true
38 | @SerializedName("shut_up_timestamp")
39 | var shutUpTimestamp = 0L
40 |
41 | val levelInt: Int
42 | get() = level.toIntOrNull() ?: 0
43 | }
44 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/group/GroupNoticeResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.group
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class GroupNoticeResp {
6 | @SerializedName("sender_id")
7 | var senderId = 0L
8 |
9 | @SerializedName("publish_time")
10 | var publishTime = 0L
11 |
12 | @SerializedName("message")
13 | var message: Message? = null
14 |
15 | class Message {
16 | @SerializedName("text")
17 | var text: String = ""
18 |
19 | @SerializedName("images")
20 | var images: List = mutableListOf()
21 | }
22 |
23 | class Image {
24 | @SerializedName("id")
25 | var id = ""
26 |
27 | @SerializedName("width")
28 | var width = "0"
29 |
30 | @SerializedName("height")
31 | var height = "0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/misc/CSRFTokenResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.misc
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class CSRFTokenResp {
6 | @SerializedName("token")
7 | var token = ""
8 | }
9 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/misc/ClientsResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.misc
2 |
3 | import cn.evolvefield.onebot.sdk.util.json.ClientsAdapter
4 | import com.google.gson.annotations.JsonAdapter
5 | import com.google.gson.annotations.SerializedName
6 |
7 | class ClientsResp {
8 | @SerializedName("clients")
9 | var clients: List = mutableListOf()
10 |
11 | @JsonAdapter(ClientsAdapter::class)
12 | class Clients {
13 | var appId = 0L
14 | var deviceName = ""
15 | var deviceKind = ""
16 | var loginTime = 0L
17 | var loginPlatform = 0L
18 | var location = ""
19 | var guid = ""
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/misc/CookiesResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.misc
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class CookiesResp {
6 | @SerializedName("cookies")
7 | var cookies = ""
8 | }
9 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/response/misc/CredentialsResp.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.response.misc
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class CredentialsResp {
6 | @SerializedName("cookies")
7 | var cookies = ""
8 | @SerializedName("csrf_token", alternate = ["token"])
9 | var token = ""
10 | }
11 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/util/CQCode.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.util
2 |
3 | import com.google.gson.JsonArray
4 | import com.google.gson.JsonObject
5 | import com.google.gson.JsonPrimitive
6 |
7 | object CQCode {
8 | fun fromJson(json: JsonArray): String {
9 | return buildString {
10 | for (ele in json) {
11 | val obj = ele.asJsonObject
12 | val type = obj["type"].asString
13 | val data = obj["data"]
14 | if (data is JsonObject) {
15 | // 纯文本
16 | if (type == "text") {
17 | append(data["text"].asString
18 | .replace("&", "&")
19 | .replace("[", "[")
20 | .replace("]", "]"))
21 | continue
22 | }
23 | append("[CQ:$type")
24 | for ((key, value) in data.asMap()) {
25 | // 约定: key 不会出现需要转义的字符
26 | append(",$key=")
27 | append(
28 | if (value is JsonPrimitive) {
29 | value.asString
30 | } else {
31 | value.toString()
32 | }.replace("&", "&")
33 | .replace("[", "[")
34 | .replace("]", "]")
35 | .replace(",", ",")
36 | )
37 | }
38 | }
39 | append("]")
40 | }
41 | }
42 | }
43 |
44 | fun toJson(s: String): JsonArray {
45 | val array = JsonArray()
46 | fun add(type: String, data: JsonObject.() -> Unit) {
47 | array.add(JsonObject().also {
48 | it.addProperty("type", type)
49 | it.add("data", JsonObject().apply(data))
50 | })
51 | }
52 | fun String.decode(): String {
53 | return replace("&", "&")
54 | .replace("[", "[")
55 | .replace("]", "]")
56 | }
57 | fun String.decodeValue(): String {
58 | return decode()
59 | .replace(",", ",")
60 | }
61 | fun StringBuilder.decode(): String {
62 | return toString()
63 | .decode()
64 | .also { clear() }
65 | }
66 | val temp = StringBuilder()
67 | var flag = false
68 | for (c in s) {
69 | if (c == '[' && temp.isNotEmpty()) {
70 | add("text") {
71 | addProperty("text", temp.decode())
72 | }
73 | }
74 | temp.append(c)
75 | if (!flag && temp.startsWith("[CQ:")) flag = true
76 | if (flag && c == ']') {
77 | val cq = temp.substring(4, temp.length - 1)
78 | if (cq.contains(',')) {
79 | val split = cq.split(',')
80 | add(split[0]) {
81 | for (s1 in split.drop(1)) {
82 | if (!s1.contains('=')) continue
83 | val (key, value) = s1
84 | .split('=', limit = 2)
85 | .run { this[0] to this[1].decodeValue() }
86 | addProperty(key, value)
87 | }
88 | }
89 | } else add(cq) { }
90 | temp.clear()
91 | flag = false
92 | }
93 | }
94 | if (temp.isNotEmpty()) {
95 | add("text") {
96 | addProperty("text", temp.decode())
97 | }
98 | }
99 | return array
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/util/JsonDeserializerKt.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.util
2 |
3 | import com.google.gson.JsonDeserializationContext
4 | import com.google.gson.JsonDeserializer
5 | import com.google.gson.JsonElement
6 | import com.google.gson.JsonParseException
7 | import java.lang.reflect.Type
8 |
9 | interface JsonDeserializerKt : JsonDeserializer {
10 | override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): T? {
11 | return runCatching {
12 | deserializeFromJson(json, typeOfT, context)
13 | }.getOrElse {
14 | throw JsonParseException("An error has occurred while parsing json $json to ${typeOfT.typeName}", it)
15 | }
16 | }
17 |
18 | fun deserializeFromJson(json: JsonElement, type: Type, ctx: JsonDeserializationContext): T?
19 | }
20 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/util/json/ClientsAdapter.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.util.json
2 |
3 | import cn.evolvefield.onebot.sdk.response.misc.ClientsResp
4 | import cn.evolvefield.onebot.sdk.util.JsonDeserializerKt
5 | import cn.evolvefield.onebot.sdk.util.ignorable
6 | import cn.evolvefield.onebot.sdk.util.long
7 | import cn.evolvefield.onebot.sdk.util.string
8 | import com.google.gson.JsonDeserializationContext
9 | import com.google.gson.JsonElement
10 | import java.lang.reflect.Type
11 |
12 | class ClientsAdapter : JsonDeserializerKt {
13 | override fun deserializeFromJson(
14 | json: JsonElement,
15 | type: Type,
16 | ctx: JsonDeserializationContext
17 | ): ClientsResp.Clients {
18 | val obj = json.asJsonObject
19 | return ClientsResp.Clients().apply {
20 | appId = obj.long("app_id")
21 | deviceName = obj.string("device_name")
22 | deviceKind = obj.string("device_kind")
23 | loginTime = obj.ignorable("login_time", 0L)
24 | loginPlatform = obj.ignorable("login_platform", 0L)
25 | location = obj.ignorable("location", "")
26 | guid = obj.ignorable("guid", "")
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/util/json/ForwardMsgAdapter.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.util.json
2 |
3 | import cn.evolvefield.onebot.sdk.response.group.ForwardMsgResp
4 | import cn.evolvefield.onebot.sdk.util.JsonDeserializerKt
5 | import cn.evolvefield.onebot.sdk.util.forceString
6 | import cn.evolvefield.onebot.sdk.util.fromJson
7 | import cn.evolvefield.onebot.sdk.util.ignorable
8 | import com.google.gson.JsonDeserializationContext
9 | import com.google.gson.JsonElement
10 | import java.lang.reflect.Type
11 |
12 | class ForwardMsgAdapter : JsonDeserializerKt {
13 | override fun deserializeFromJson(
14 | json: JsonElement,
15 | type: Type,
16 | ctx: JsonDeserializationContext
17 | ): ForwardMsgResp {
18 | val nodes = mutableListOf()
19 | val jsonObj = json.asJsonObject
20 | val msgArray = when {
21 | jsonObj.has("messages") -> jsonObj.get("messages") //Lagrange
22 | jsonObj.has("message") -> jsonObj.get("message") // go-cqhttp
23 | jsonObj.has("content") -> jsonObj.get("content") //old nap-cat
24 | else -> throw IllegalArgumentException("不受支持的合并转发消息格式!请携带日志前往对应实现反馈。")
25 | }
26 | for (element in msgArray.asJsonArray) {
27 | val obj = element.asJsonObject.run {
28 | if (has("data")) get("data").asJsonObject
29 | else this
30 | }
31 | nodes.add(ForwardMsgResp.Node().apply {
32 | time = obj.ignorable("time", 0) // OpenShamrock
33 | messageType = obj.ignorable("message_type", "") // OpenShamrock
34 | realId = obj.ignorable("real_id", 0) // OpenShamrock
35 | sender = obj.fromJson("sender") // OpenShamrock
36 | if (sender == null) { // Lagrange
37 | sender = ForwardMsgResp.Sender().apply {
38 | userId = obj.ignorable("user_id", 0L)
39 | nickname = obj.ignorable("nickname", "")
40 | sex = "unknown"
41 | }
42 | }
43 | message = if (obj.has("content")) {
44 | obj.forceString("content") // Lagrange
45 | } else {
46 | obj.forceString("message") // go-cqhttp, OpenShamrock
47 | }
48 | peerId = obj.ignorable("peer_id", 0L) // OpenShamrock
49 | targetId = obj.ignorable("target_id", 0L) // OpenShamrock
50 | })
51 | }
52 | return ForwardMsgResp().apply { message = nodes }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/util/json/GroupFilesAdapter.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.util.json
2 |
3 | import cn.evolvefield.onebot.sdk.response.group.GroupFilesResp
4 | import cn.evolvefield.onebot.sdk.util.*
5 | import com.google.gson.JsonDeserializationContext
6 | import com.google.gson.JsonElement
7 | import java.lang.reflect.Type
8 |
9 | class GroupFilesAdapter {
10 | class Files : JsonDeserializerKt {
11 | override fun deserializeFromJson(
12 | json: JsonElement,
13 | type: Type,
14 | ctx: JsonDeserializationContext
15 | ): GroupFilesResp.Files {
16 | val obj = json.asJsonObject
17 | return GroupFilesResp.Files().apply {
18 | fileId = obj.string("file_id")
19 | fileName = obj.string("file_name")
20 | busid = obj.int("busid")
21 | fileSize = obj.long("file_size", "size") // NapCat: size
22 | uploadTime = obj.long("upload_time")
23 | deadTime = obj.long("dead_time")
24 | modifyTime = obj.long("modify_time")
25 | downloadTimes = obj.int("download_times")
26 | uploader = obj.long("uploader")
27 | uploaderName = obj.string("uploader_name")
28 | md5 = obj.nullableString("md5", null)
29 | sha1 = obj.nullableString("sha1", null)
30 | sha3 = obj.nullableString("sha3", null)
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/util/json/MessageEventAdapter.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.util.json
2 |
3 | import cn.evolvefield.onebot.sdk.entity.PrivateSender
4 | import cn.evolvefield.onebot.sdk.event.message.GroupMessageEvent
5 | import cn.evolvefield.onebot.sdk.event.message.MessageEvent
6 | import cn.evolvefield.onebot.sdk.event.message.PrivateMessageEvent
7 | import cn.evolvefield.onebot.sdk.util.*
8 | import cn.evolvefield.onebot.sdk.util.json.MsgAdapter.Companion.messageId
9 | import com.google.gson.JsonDeserializationContext
10 | import com.google.gson.JsonDeserializer
11 | import com.google.gson.JsonElement
12 | import com.google.gson.JsonObject
13 | import java.lang.reflect.Type
14 |
15 | class MessageEventAdapter : JsonDeserializerKt {
16 | override fun deserializeFromJson(
17 | json: JsonElement,
18 | type: Type,
19 | ctx: JsonDeserializationContext
20 | ): MessageEvent? {
21 | val obj = json.asJsonObject
22 | val subType = obj.string("sub_type")
23 | val messageType = obj.string("message_type")
24 | return when (messageType) {
25 | "group" -> obj.groupMessage(subType)
26 | "private" -> obj.privateMessage(subType)
27 | else -> null
28 | }?.apply {
29 | postType = obj.string("post_type")
30 | time = obj.long("time")
31 | selfId = obj.long("self_id")
32 | userId = obj.long("user_id")
33 | isJsonMessage = obj.has("message") && obj["message"].isJsonArray
34 | message = obj.forceString("message")
35 | rawMessage = obj.ignorable("raw_message", "")
36 | font = obj.ignorable("font", 0)
37 | }
38 | }
39 |
40 | private fun JsonObject.groupMessage(subType: String): MessageEvent = GroupMessageEvent().apply {
41 | this.subType = subType
42 | messageId = messageId()
43 | groupId = long("group_id")
44 | anonymous = fromJson("anonymous")
45 | sender = fromJson("sender")
46 | }
47 | private fun JsonObject.privateMessage(subType: String): MessageEvent = PrivateMessageEvent().apply {
48 | this.subType = subType
49 | messageId = messageId()
50 | tempSource = ignorable("temp_source", Int.MIN_VALUE)
51 | sender = fromJson("sender")!!
52 | groupId = ignorable("group_id", sender.groupId)
53 | fromNick = ignorable("from_nick", sender.nickname)
54 | }
55 |
56 | class PrivateSenderAdapter : JsonDeserializer {
57 | override fun deserialize(
58 | json: JsonElement,
59 | typeOfT: Type,
60 | context: JsonDeserializationContext
61 | ): PrivateSender {
62 | val obj = json.asJsonObject
63 | return PrivateSender().apply {
64 | userId = obj["user_id"].asLong
65 | groupId = obj.ignorable("group_id", 0L)
66 | nickname = obj["nickname"].asString
67 | sex = obj.ignorable("sex", "unknown")
68 | age = obj.ignorable("age", 0)
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/cn/evolvefield/onebot/sdk/util/json/MsgAdapter.kt:
--------------------------------------------------------------------------------
1 | package cn.evolvefield.onebot.sdk.util.json
2 |
3 | import cn.evolvefield.onebot.sdk.response.group.GetMsgResp
4 | import cn.evolvefield.onebot.sdk.util.*
5 | import com.google.gson.JsonDeserializationContext
6 | import com.google.gson.JsonElement
7 | import com.google.gson.JsonObject
8 | import java.lang.reflect.Type
9 |
10 | class MsgAdapter : JsonDeserializerKt {
11 | override fun deserializeFromJson(
12 | json: JsonElement,
13 | type: Type,
14 | ctx: JsonDeserializationContext
15 | ): GetMsgResp {
16 | val obj = json.asJsonObject
17 | return GetMsgResp().apply {
18 | messageId = obj.messageId()
19 | realId = obj.ignorable("real_id", 0)
20 | sender = obj.fromJson("sender")!!
21 | time = obj.int("time")
22 | isJsonMessage = obj.has("message") && obj["message"].isJsonArray
23 | message = obj.forceString("message")
24 | rawMessage = obj.ignorable("raw_message", "")
25 | peerId = obj.ignorable("peer_id", 0L)
26 | groupId = obj.ignorable("group_id", 0L)
27 | targetId = obj.ignorable("target_id", 0L)
28 | }
29 | }
30 |
31 | companion object {
32 | fun JsonObject.messageId(): Int {
33 | val msgId: String = this["message_id"]?.asString ?: throw IllegalStateException("""
34 | 收到消息ID为空的消息事件。type=${this["message_type"]}, sub_type=${this["sub_type"]}
35 | 请向你所使用的 Onebot 实现维护者报告该问题,
36 | 不要将该问题反馈到 Overflow。
37 | """.trimIndent()
38 | )
39 | try {
40 | return msgId.toInt()
41 | } catch (ignored: NumberFormatException) {
42 | throw NumberFormatException(
43 | """
44 | 无法将消息ID $msgId 的类型转换为 int32,
45 | 请向你所使用的 Onebot 实现维护者报告该问题,
46 | 不要将该问题反馈到 Overflow。
47 | """.trimIndent()
48 | )
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/AbstractBot.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/master/LICENSE
8 | */
9 | package net.mamoe.mirai.internal
10 |
11 | import kotlinx.coroutines.CoroutineScope
12 | import net.mamoe.mirai.Bot
13 |
14 | abstract class AbstractBot : Bot, CoroutineScope
15 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/QQAndroidBot.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/master/LICENSE
8 | */
9 | package net.mamoe.mirai.internal
10 |
11 | import net.mamoe.mirai.Bot
12 | import net.mamoe.mirai.internal.network.QQAndroidClient
13 | import kotlin.contracts.ExperimentalContracts
14 | import kotlin.contracts.contract
15 |
16 | @OptIn(ExperimentalContracts::class)
17 | internal fun Bot.asQQAndroidBot(): QQAndroidBot {
18 | contract {
19 | returns() implies (this@asQQAndroidBot is QQAndroidBot)
20 | }
21 |
22 | return this as QQAndroidBot
23 | }
24 |
25 | internal abstract class QQAndroidBot : AbstractBot() {
26 | abstract val implGetter: () -> cn.evolvefield.onebot.client.core.Bot
27 | val client: QQAndroidClient by lazy {
28 | QQAndroidClient(implGetter)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/event/EventChannelToEventDispatcherAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2022 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/dev/LICENSE
8 | */
9 |
10 | package net.mamoe.mirai.internal.event
11 |
12 | import net.mamoe.mirai.event.Event
13 | import kotlin.coroutines.CoroutineContext
14 | import kotlin.coroutines.EmptyCoroutineContext
15 | import kotlin.reflect.KClass
16 |
17 | internal class EventChannelToEventDispatcherAdapter private constructor(
18 | baseEventClass: KClass, defaultCoroutineContext: CoroutineContext = EmptyCoroutineContext
19 | ) : EventChannelImpl(baseEventClass, defaultCoroutineContext) {
20 | companion object {
21 | @InternalEventMechanism
22 | val instance by lazy { EventChannelToEventDispatcherAdapter(Event::class, EmptyCoroutineContext) }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/event/GlobalEventChannelProviderImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2022 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/dev/LICENSE
8 | */
9 |
10 | package net.mamoe.mirai.internal.event
11 |
12 | import net.mamoe.mirai.event.Event
13 | import net.mamoe.mirai.event.EventChannel
14 | import net.mamoe.mirai.event.InternalGlobalEventChannelProvider
15 | import net.mamoe.mirai.utils.MiraiInternalApi
16 |
17 | @OptIn(MiraiInternalApi::class)
18 | @InternalEventMechanism
19 | internal class GlobalEventChannelProviderImpl : InternalGlobalEventChannelProvider {
20 | @InternalEventMechanism
21 | val instance = EventChannelToEventDispatcherAdapter.instance
22 |
23 | @OptIn(InternalEventMechanism::class)
24 | override fun getInstance(): EventChannel = instance
25 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/event/package.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2022 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/dev/LICENSE
8 | */
9 |
10 | package net.mamoe.mirai.internal.event
11 |
12 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/RefinableMessage.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2022 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/dev/LICENSE
8 | */
9 | package net.mamoe.mirai.internal.message
10 |
11 | import net.mamoe.mirai.message.data.*
12 |
13 | internal sealed class MessageRefiner
14 |
15 | /**
16 | * 执行不需要 `suspend` 的 refine. 用于 [MessageSource.originalMessage].
17 | *
18 | * 兼容 cssxsh/mirai-hibernate-plugin
19 | *
20 | * https://github.com/cssxsh/mirai-hibernate-plugin/blob/8f425db01629ff84900b53b1bf86c741ef7f81be/src/main/kotlin/xyz/cssxsh/mirai/hibernate/MiraiHibernateRecorder.kt
21 | */
22 | @Suppress("unused")
23 | internal object LightMessageRefiner : MessageRefiner() {
24 | /**
25 | * 去除 [MessageChain] 携带的内部标识
26 | *
27 | * 用于 [createMessageReceipt] <- `RemoteFile.uploadAndSend` (文件操作API v1)
28 | */
29 | fun MessageChain.dropMiraiInternalFlags(): MessageChain = this
30 | }
31 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/message/data/MarketFaceImpl.kt:
--------------------------------------------------------------------------------
1 | package net.mamoe.mirai.internal.message.data
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 | import kotlinx.serialization.Transient
6 | import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
7 | import net.mamoe.mirai.message.data.MarketFace
8 | import net.mamoe.mirai.utils.MiraiExperimentalApi
9 |
10 | @SerialName(MarketFaceImpl.SERIAL_NAME)
11 | @Serializable
12 | internal data class MarketFaceImpl internal constructor(
13 | internal val delegate: ImMsgBody.MarketFace,
14 | ) : MarketFace {
15 |
16 | override val name: String get() = delegate.faceName.decodeToString()
17 |
18 | @Transient
19 | @MiraiExperimentalApi
20 | override val id: Int = delegate.tabId
21 |
22 | @OptIn(MiraiExperimentalApi::class)
23 | override fun toString() = "[mirai:marketface:$id,$name]"
24 |
25 | companion object {
26 | const val SERIAL_NAME = MarketFace.SERIAL_NAME
27 | }
28 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/QQAndroidClient.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/master/LICENSE
8 | */
9 | package net.mamoe.mirai.internal.network
10 |
11 | import net.mamoe.mirai.internal.network.components.SsoSession
12 |
13 | internal open class QQAndroidClient(
14 | implGetter: () -> cn.evolvefield.onebot.client.core.Bot
15 | ) : SsoSession {
16 | override var loginState: Int = 0
17 | override var wLoginSigInfo: WLoginSigInfo = WLoginSigInfo(implGetter)
18 | }
19 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/components/SsoProcessor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/master/LICENSE
8 | */
9 | package net.mamoe.mirai.internal.network.components
10 |
11 | import net.mamoe.mirai.internal.network.WLoginSigInfo
12 |
13 | /**
14 | * Contains secrets for encryption and decryption during a session created by [SsoProcessor] and [PacketCodec].
15 | *
16 | * @see AccountSecrets
17 | */
18 | internal interface SsoSession {
19 | //var outgoingPacketSessionId: ByteArray
20 |
21 | /**
22 | * always 0 for now.
23 | */
24 | var loginState: Int
25 |
26 | // also present in AccountSecrets
27 | var wLoginSigInfo: WLoginSigInfo
28 | //val randomKey: ByteArray
29 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/keys.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/master/LICENSE
8 | */
9 | @file:Suppress("unused")
10 | package net.mamoe.mirai.internal.network
11 |
12 | import kotlinx.coroutines.runBlocking
13 | import net.mamoe.mirai.internal.AbstractBot
14 | import net.mamoe.mirai.internal.QQAndroidBot
15 |
16 | /**
17 | * 兼容 cssxsh/meme-helper 中请求网络接口所需的 bkn、sKey、psKey 等参数
18 | *
19 | * https://github.com/cssxsh/meme-helper/blob/61fbfd967a5e45dd9923c470622cd90d5bce1ce4/src/main/kotlin/face/MarketFaceHelper.kt
20 | */
21 | internal data class WLoginSigInfo(
22 | val impl: () -> cn.evolvefield.onebot.client.core.Bot
23 | ) {
24 | val bkn: Int
25 | get() = sKey.encodeToByteArray()
26 | .fold(5381) { acc: Int, b: Byte -> acc + acc.shl(5) + b.toInt() }
27 | .and(Int.MAX_VALUE)
28 |
29 | val sKey: String
30 | get() = runBlocking {
31 | val data = impl().getCredentials("qun.qq.com").data ?: throw IllegalStateException("Onebot 获取 Credentials (sKey) 失败")
32 | val matches = Regex("[^_]skey=([^;]+);?").find(data.cookies) ?: throw IllegalStateException("Onebot 获取 Credentials 返回的 cookie 中没有 skey")
33 | return@runBlocking matches.groupValues[1]
34 | }
35 |
36 | fun getPsKey(name: String): String {
37 | return runBlocking {
38 | val data = impl().getCredentials(name).data ?: throw IllegalStateException("Onebot 获取 Credentials (psKey) 失败")
39 | val matches = Regex("p_skey=([^;]+);?").find(data.cookies) ?: throw IllegalStateException("Onebot 获取 Credentials 返回的 cookie 中没有 p_skey")
40 | return@runBlocking matches.groupValues[1]
41 | }
42 | }
43 | }
44 |
45 | internal val AbstractBot.sKey get() = client.wLoginSigInfo.sKey
46 | internal fun AbstractBot.psKey(name: String) = client.wLoginSigInfo.getPsKey(name)
47 | internal val AbstractBot.client get() = (this as QQAndroidBot).client
48 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/net/mamoe/mirai/internal/network/protocol/data/proto/Msg.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019-2022 Mamoe Technologies and contributors.
3 | *
4 | * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
5 | * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
6 | *
7 | * https://github.com/mamoe/mirai/blob/dev/LICENSE
8 | */
9 | package net.mamoe.mirai.internal.network.protocol.data.proto
10 |
11 | import kotlinx.serialization.Serializable
12 | import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
13 | import net.mamoe.mirai.utils.isSameType
14 |
15 | @Serializable
16 | internal class ImMsgBody /*: ProtoBuf*/ {
17 |
18 | @Serializable
19 | internal data class MarketFace(
20 | /*@ProtoNumber(1) */@JvmField var faceName: ByteArray = EMPTY_BYTE_ARRAY,
21 | /*@ProtoNumber(2) */@JvmField val itemType: Int = 0,
22 | /*@ProtoNumber(3) */@JvmField val faceInfo: Int = 0,
23 | /*@ProtoNumber(4) */@JvmField val faceId: ByteArray = EMPTY_BYTE_ARRAY,
24 | /*@ProtoNumber(5) */@JvmField val tabId: Int = 0,
25 | /*@ProtoNumber(6) */@JvmField val subType: Int = 0,
26 | /*@ProtoNumber(7) */@JvmField val key: ByteArray = EMPTY_BYTE_ARRAY,
27 | /*@ProtoNumber(8) */@JvmField val param: ByteArray = EMPTY_BYTE_ARRAY,
28 | /*@ProtoNumber(9) */@JvmField val mediaType: Int = 0,
29 | /*@ProtoNumber(10) */@JvmField val imageWidth: Int = 0,
30 | /*@ProtoNumber(11) */@JvmField val imageHeight: Int = 0,
31 | /*@ProtoNumber(12) */@JvmField val mobileParam: ByteArray = EMPTY_BYTE_ARRAY,
32 | /*@ProtoNumber(13) */@JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY,
33 | ) /*: ProtoBuf*/ {
34 | @Suppress("DuplicatedCode")
35 | override fun equals(other: Any?): Boolean {
36 | if (this === other) return true
37 | if (!isSameType(this, other)) return false
38 |
39 | if (!faceName.contentEquals(other.faceName)) return false
40 | if (itemType != other.itemType) return false
41 | if (faceInfo != other.faceInfo) return false
42 | if (!faceId.contentEquals(other.faceId)) return false
43 | if (tabId != other.tabId) return false
44 | if (subType != other.subType) return false
45 | if (!key.contentEquals(other.key)) return false
46 | if (!param.contentEquals(other.param)) return false
47 | if (mediaType != other.mediaType) return false
48 | if (imageWidth != other.imageWidth) return false
49 | if (imageHeight != other.imageHeight) return false
50 | if (!mobileParam.contentEquals(other.mobileParam)) return false
51 | if (!pbReserve.contentEquals(other.pbReserve)) return false
52 |
53 | return true
54 | }
55 |
56 | override fun hashCode(): Int {
57 | var result = faceName.contentHashCode()
58 | result = 31 * result + itemType
59 | result = 31 * result + faceInfo
60 | result = 31 * result + faceId.contentHashCode()
61 | result = 31 * result + tabId
62 | result = 31 * result + subType
63 | result = 31 * result + key.contentHashCode()
64 | result = 31 * result + param.contentHashCode()
65 | result = 31 * result + mediaType
66 | result = 31 * result + imageWidth
67 | result = 31 * result + imageHeight
68 | result = 31 * result + mobileParam.contentHashCode()
69 | result = 31 * result + pbReserve.contentHashCode()
70 | return result
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/BotFactoryImpl.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal
2 |
3 | import net.mamoe.mirai.Bot
4 | import net.mamoe.mirai.BotFactory
5 | import net.mamoe.mirai.auth.BotAuthorization
6 | import net.mamoe.mirai.utils.BotConfiguration
7 |
8 | internal object BotFactoryImpl : BotFactory {
9 | override fun newBot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot = end(qq)
10 |
11 | override fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot = end(qq)
12 |
13 | override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot = end(qq)
14 |
15 | private fun end(qq: Long): Bot {
16 | Bot.getInstanceOrNull(qq)?.also { return it }
17 | throw UnsupportedOperationException("溢出核心已委托远程实现接管了账户管理,mirai 框架端没有登录机器人的职责")
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/Config.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Config(
8 | @SerialName("no_log___DO_NOT_REPORT_IF_YOU_SWITCH_THIS_ON___开启此选项时不接受漏洞反馈")
9 | var noLogDoNotReportIfYouSwitchThisOn: Boolean = false,
10 | @SerialName("ws_host")
11 | var wsHost: String = "ws://127.0.0.1:3001",
12 | @SerialName("reversed_ws_port")
13 | var reversedWSPort: Int = -1,
14 | @SerialName("token")
15 | var token: String = "",
16 | @SerialName("no_platform")
17 | val noPlatform: Boolean = false,
18 | @SerialName("use_cq_code")
19 | val useCQCode: Boolean = false,
20 | @SerialName("retry_times")
21 | var retryTimes: Int = 5,
22 | @SerialName("retry_wait_mills")
23 | var retryWaitMills: Long = 5_000L,
24 | @SerialName("retry_rest_mills")
25 | var retryRestMills: Long = 60_000L,
26 | @SerialName("heartbeat_check_seconds")
27 | var heartbeatCheckSeconds: Int = 60,
28 | @SerialName("use_group_upload_event_for_file_message")
29 | var useGroupUploadEventForFileMessage: Boolean = false,
30 | @SerialName("resource_cache")
31 | var resourceCache: CacheConfig = CacheConfig(),
32 | @SerialName("drop_events_before_connected")
33 | var dropEventsBeforeConnected: Boolean = true,
34 | )
35 | @Serializable
36 | data class CacheConfig (
37 | @SerialName("enabled")
38 | var enabled: Boolean = false,
39 | @SerialName("keep_duration_hours")
40 | var keepDurationHours: Long = 168L,
41 | )
42 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/OtherClientWrapper.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
2 | package top.mrxiaom.overflow.internal.contact
3 |
4 | import kotlinx.coroutines.CoroutineName
5 | import net.mamoe.mirai.contact.OtherClient
6 | import net.mamoe.mirai.contact.OtherClientInfo
7 | import net.mamoe.mirai.message.data.ShortVideo
8 | import net.mamoe.mirai.utils.ExternalResource
9 | import top.mrxiaom.overflow.internal.message.OnebotMessages
10 | import top.mrxiaom.overflow.spi.FileService
11 | import kotlin.coroutines.CoroutineContext
12 |
13 | internal class OtherClientWrapper(
14 | override val bot: BotWrapper,
15 | override var info: OtherClientInfo,
16 | ) : OtherClient {
17 | override val coroutineContext: CoroutineContext = CoroutineName("(Bot/${bot.id})OtherClient/${info.deviceKind}")
18 |
19 | override suspend fun uploadShortVideo(
20 | thumbnail: ExternalResource,
21 | video: ExternalResource,
22 | fileName: String?
23 | ): ShortVideo {
24 | return OnebotMessages.videoFromFile(FileService.instance!!.upload(video))
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/data/AnnouncementsWrapper.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
2 | package top.mrxiaom.overflow.internal.contact.data
3 |
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.asFlow
6 | import net.mamoe.mirai.contact.Group
7 | import net.mamoe.mirai.contact.NormalMember
8 | import net.mamoe.mirai.contact.announcement.*
9 | import net.mamoe.mirai.utils.ExternalResource
10 | import net.mamoe.mirai.utils.currentTimeSeconds
11 | import top.mrxiaom.overflow.internal.contact.GroupWrapper
12 | import top.mrxiaom.overflow.internal.utils.FastImageInfo
13 | import top.mrxiaom.overflow.spi.FileService
14 |
15 | internal class AnnouncementsWrapper(
16 | val impl: GroupWrapper,
17 | internal var list: List
18 | ) : Announcements {
19 | override fun asFlow(): Flow = list.asFlow()
20 |
21 | override suspend fun delete(fid: String): Boolean {
22 | TODO("Not yet implemented")
23 | }
24 |
25 | override suspend fun get(fid: String): OnlineAnnouncement? {
26 | TODO("Not yet implemented")
27 | }
28 |
29 | override suspend fun members(fid: String, confirmed: Boolean): List {
30 | TODO("Not yet implemented")
31 | }
32 |
33 | override suspend fun remind(fid: String) {
34 | TODO("Not yet implemented")
35 | }
36 |
37 | override suspend fun publish(announcement: Announcement): OnlineAnnouncement {
38 | impl.bot.impl.sendGroupNotice(
39 | impl.id,
40 | announcement.content,
41 | announcement.parameters.image?.file
42 | )
43 | return OnlineAnnouncementWrapper(
44 | content = announcement.content,
45 | group = impl,
46 | senderId = impl.bot.id,
47 | parameters = announcement.parameters
48 | )
49 | }
50 |
51 | override suspend fun uploadImage(resource: ExternalResource): AnnouncementImage {
52 | val size = FastImageInfo(resource.inputStream())
53 | return AnnouncementImage.create("\n${FileService.instance!!.upload(resource)}\n", size?.height ?: 0, size?.width ?: 0)
54 | }
55 |
56 | suspend fun update() {
57 | list = impl.fetchAnnouncements().list
58 | }
59 |
60 | companion object {
61 | suspend fun GroupWrapper.fetchAnnouncements(): AnnouncementsWrapper {
62 | val list = bot.impl.getGroupNotice(id).data.map {
63 | val message = it.message ?: return@map null
64 | OnlineAnnouncementWrapper(
65 | content = message.text,
66 | group = this,
67 | senderId = it.senderId,
68 | publicationTime = it.publishTime,
69 | parameters = AnnouncementParametersBuilder().apply {
70 | for (image in message.images) {
71 | image(AnnouncementImage.create(
72 | image.id, image.height.toIntOrNull() ?: 0, image.width.toIntOrNull() ?: 0
73 | ))
74 | }
75 | }.build()
76 | )
77 | }
78 | return AnnouncementsWrapper(this, list.filterNotNull())
79 | }
80 | }
81 | }
82 |
83 | internal class OnlineAnnouncementWrapper(
84 | override val content: String,
85 | override val group: Group,
86 | override val senderId: Long,
87 | override val parameters: AnnouncementParameters = AnnouncementParameters.DEFAULT,
88 | override val allConfirmed: Boolean = false,
89 | override val confirmedMembersCount: Int = 0,
90 | override val fid: String = "",
91 | override val publicationTime: Long = currentTimeSeconds(),
92 | override val sender: NormalMember? = group[senderId],
93 | ) : OnlineAnnouncement
94 |
95 | internal val AnnouncementImage.file: String
96 | get() = if (url.contains("\n")) url.split("\n")[1] else url
97 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/data/EssencesWrapper.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.contact.data
2 |
3 | import kotlinx.coroutines.currentCoroutineContext
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.flow
6 | import kotlinx.coroutines.isActive
7 | import net.mamoe.mirai.contact.MemberPermission
8 | import net.mamoe.mirai.contact.checkBotPermission
9 | import net.mamoe.mirai.contact.essence.EssenceMessageRecord
10 | import net.mamoe.mirai.contact.essence.Essences
11 | import net.mamoe.mirai.message.data.MessageSource
12 | import net.mamoe.mirai.message.data.MessageSourceBuilder
13 | import net.mamoe.mirai.message.data.MessageSourceKind
14 | import net.mamoe.mirai.message.data.messageChainOf
15 | import top.mrxiaom.overflow.internal.contact.GroupWrapper
16 | import top.mrxiaom.overflow.internal.utils.shareDigest
17 |
18 | internal class EssencesWrapper(
19 | val impl: GroupWrapper,
20 | private var list: List
21 | ) : Essences {
22 | override fun asFlow(): Flow {
23 | return flow {
24 | var offset = 0
25 | while (currentCoroutineContext().isActive) {
26 | val list = impl.fetchEssencesList(offset)
27 | for (message in list) {
28 | emit(message)
29 | }
30 | if (list.isEmpty() || list.size < 20) break
31 | offset++
32 | }
33 | }
34 | }
35 |
36 | override suspend fun getPage(start: Int, limit: Int): List {
37 | if (impl.bot.noPlatform) return listOf()
38 | return impl.fetchEssencesList().also { list = it }
39 | }
40 |
41 | override suspend fun remove(source: MessageSource) {
42 | impl.checkBotPermission(MemberPermission.ADMINISTRATOR)
43 | impl.bot.impl.deleteEssenceMsg(source.ids[0])
44 | }
45 |
46 | override suspend fun share(source: MessageSource): String {
47 | if (impl.bot.noPlatform) return ""
48 | val shareKey = impl.bot.shareDigest(
49 | groupCode = impl.id,
50 | msgSeq = source.ids.first().toLong().and(0xFFFF_FFFF),
51 | msgRandom = source.internalIds.first().toLong().and(0xFFFF_FFFF),
52 | targetGroupCode = 0
53 | )
54 | return "https://qun.qq.com/essence/share?_wv=3&_wwv=128&_wvx=2&sharekey=$shareKey"
55 | }
56 |
57 | companion object {
58 | internal suspend fun GroupWrapper.fetchEssencesList(page: Int = 0): List {
59 | if (bot.noPlatform) return listOf()
60 | return bot.impl.getEssenceMsgList(id, page).data.map {
61 | EssenceMessageRecord(
62 | this, queryMember(it.senderId), it.senderId, it.senderNick, it.senderTime.toInt(),
63 | queryMember(it.operatorId), it.operatorId, it.operatorNick, it.operatorTime.toInt()
64 | ) { parse ->
65 | MessageSourceBuilder().apply {
66 | id(it.messageId)
67 | internalId(it.messageId)
68 | time(it.senderTime.toInt())
69 | target(this@fetchEssencesList)
70 | sender(it.senderId)
71 | if (parse) {
72 | bot.getMsg(it.messageId)?.also { msg -> messageChainOf(msg) }
73 | }
74 | }.build(bot.id, MessageSourceKind.GROUP)
75 | }
76 | }
77 | }
78 | internal suspend fun GroupWrapper.fetchEssences(): EssencesWrapper {
79 | return EssencesWrapper(this, fetchEssencesList())
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/data/FallbackFriendGroup.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.contact.data
2 |
3 | import net.mamoe.mirai.Bot
4 | import net.mamoe.mirai.contact.Friend
5 | import net.mamoe.mirai.contact.friendgroup.FriendGroup
6 |
7 | class FallbackFriendGroup(
8 | val bot: Bot
9 | ): FriendGroup {
10 | override val count: Int
11 | get() = friends.size
12 | override val friends: Collection
13 | get() = bot.friends
14 | override val id: Int = 0
15 | override val name: String = "我的好友"
16 |
17 | override suspend fun delete(): Boolean = throw IllegalStateException("Onebot 未提供好友分组接口")
18 | override suspend fun moveIn(friend: Friend): Boolean = throw IllegalStateException("Onebot 未提供好友分组接口")
19 | override suspend fun renameTo(newName: String): Boolean = throw IllegalStateException("Onebot 未提供好友分组接口")
20 | }
21 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/data/FallbackFriendGroups.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.contact.data
2 |
3 | import net.mamoe.mirai.Bot
4 | import net.mamoe.mirai.contact.friendgroup.FriendGroup
5 | import net.mamoe.mirai.contact.friendgroup.FriendGroups
6 |
7 | class FallbackFriendGroups(
8 | val bot: Bot
9 | ) : FriendGroups {
10 |
11 | internal val fallbackFriendGroup = FallbackFriendGroup(bot)
12 |
13 | override fun asCollection(): Collection = listOf(fallbackFriendGroup)
14 | override suspend fun create(name: String): FriendGroup = fallbackFriendGroup
15 | override fun get(id: Int): FriendGroup? = fallbackFriendGroup
16 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/data/GroupSettingsWrapper.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.contact.data
2 |
3 | import kotlinx.coroutines.runBlocking
4 | import net.mamoe.mirai.contact.GroupSettings
5 | import net.mamoe.mirai.contact.MemberPermission
6 | import net.mamoe.mirai.contact.checkBotPermission
7 | import top.mrxiaom.overflow.internal.contact.GroupWrapper
8 |
9 | internal class GroupSettingsWrapper(
10 | val group: GroupWrapper
11 | ) : GroupSettings {
12 | @Deprecated(
13 | "group.announcements.asFlow().filter { it.parameters.sendToNewMember }.firstOrNull()",
14 | level = DeprecationLevel.HIDDEN
15 | )
16 | override var entranceAnnouncement: String
17 | get() = group.impl.groupMemo
18 | set(_) {}
19 |
20 | override var isAllowMemberInvite: Boolean
21 | get() = false // TODO: Not yet implemented
22 | set(_) {}
23 |
24 | override var isAnonymousChatEnabled: Boolean
25 | get() = false // TODO: Not yet implemented
26 | set(value) = runBlocking {
27 | group.bot.impl.setGroupAnonymous(group.id, value)
28 | }
29 |
30 | override val isAutoApproveEnabled: Boolean
31 | get() = false // TODO: Not yet implemented
32 |
33 | internal var muteAll: Boolean
34 | get() = group.impl.groupAllShut == -1
35 | set(value) {
36 | group.impl.groupAllShut = if (value) -1 else 0
37 | }
38 | override var isMuteAll: Boolean
39 | get() = muteAll
40 | set(value) = runBlocking {
41 | group.checkBotPermission(MemberPermission.ADMINISTRATOR)
42 | muteAll = value
43 | group.bot.impl.setGroupWholeBan(group.id, value)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/contact/data/MemberActiveWrapper.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.contact.data
2 |
3 | import net.mamoe.mirai.contact.active.MemberActive
4 | import net.mamoe.mirai.contact.active.MemberMedalInfo
5 | import net.mamoe.mirai.contact.active.MemberMedalType
6 | import net.mamoe.mirai.data.GroupHonorType
7 | import top.mrxiaom.overflow.internal.contact.MemberWrapper
8 |
9 | internal class MemberActiveWrapper(
10 | val member: MemberWrapper
11 | ) : MemberActive {
12 | internal var pointInternal = 0
13 | internal var rankInternal = 1
14 | internal val honorsInternal: HashSet = hashSetOf()
15 | override val honors: Set
16 | get() {
17 | member.group.active
18 | return honorsInternal
19 | }
20 | override val point: Int
21 | get() {
22 | member.group.active
23 | return pointInternal
24 | }
25 | override val rank: Int
26 | get() {
27 | member.group.active
28 | return rankInternal
29 | }
30 | override val temperature: Int
31 | get() = member.impl.levelInt
32 | override suspend fun queryMedal(): MemberMedalInfo {
33 | return member.group.active.queryMemberMedal(uid = member.id)
34 | }
35 | }
36 | internal object EmptyMemberActive : MemberActive {
37 | override val honors: Set = setOf()
38 | override val point: Int = 0
39 | override val rank: Int = 1
40 | override val temperature: Int = 0
41 | override suspend fun queryMedal(): MemberMedalInfo = MemberMedalInfo("", "", MemberMedalType.ACTIVE, setOf())
42 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/data/ContactInfoImpl.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(LowLevelApi::class)
2 | package top.mrxiaom.overflow.internal.data
3 |
4 | import cn.evolvefield.onebot.sdk.response.contact.FriendInfoResp
5 | import cn.evolvefield.onebot.sdk.response.contact.StrangerInfoResp
6 | import cn.evolvefield.onebot.sdk.response.group.GroupMemberInfoResp
7 | import cn.evolvefield.onebot.sdk.util.ignorable
8 | import com.google.gson.JsonObject
9 | import net.mamoe.mirai.LowLevelApi
10 | import net.mamoe.mirai.contact.MemberPermission
11 | import net.mamoe.mirai.data.FriendInfo
12 | import net.mamoe.mirai.data.GroupHonorType
13 | import net.mamoe.mirai.data.MemberInfo
14 | import net.mamoe.mirai.data.StrangerInfo
15 |
16 | internal class FriendInfoImpl(
17 | override val uin: Long,
18 | override val nick: String,
19 | override var remark: String,
20 | override val friendGroupId: Int = 0
21 | ) : FriendInfo
22 |
23 | internal class StrangerInfoImpl(
24 | override val uin: Long,
25 | override val nick: String,
26 | override val fromGroup: Long = 0,
27 | override val remark: String = "",
28 | ): StrangerInfo
29 |
30 | internal class MemberInfoImpl(
31 | override val honors: Set,
32 | override val isOfficialBot: Boolean,
33 | override val joinTimestamp: Int,
34 | override val lastSpeakTimestamp: Int,
35 | override val muteTimestamp: Int,
36 | override val nameCard: String,
37 | override val permission: MemberPermission,
38 | override val point: Int,
39 | override val rank: Int,
40 | override val specialTitle: String,
41 | override val temperature: Int,
42 | override val nick: String,
43 | override val remark: String,
44 | override val uin: Long
45 | ): MemberInfo
46 |
47 | internal val FriendInfo.asOnebot: FriendInfoResp
48 | get() = FriendInfoResp().apply {
49 | userId = uin
50 | nickname = nick
51 | remark = this@asOnebot.remark
52 | }
53 | internal val StrangerInfo.asOnebot: StrangerInfoResp
54 | get() = StrangerInfoResp().apply {
55 | userId = uin
56 | nickname = nick
57 | ext = JsonObject().also {
58 | if (fromGroup > 0) it.addProperty("add_src_id", fromGroup)
59 | }
60 | }
61 | internal val StrangerInfoResp.asMirai: StrangerInfo
62 | get() = StrangerInfoImpl(
63 | uin = userId,
64 | nick = nickname,
65 | fromGroup = ext.ignorable("add_src_id", 0L),
66 | )
67 |
68 | internal val GroupMemberInfoResp.asMirai: MemberInfoImpl
69 | get() = MemberInfoImpl(setOf(), false, joinTime, lastSentTime, 0, card,
70 | when(role) {
71 | "owner" -> MemberPermission.OWNER
72 | "admin" -> MemberPermission.ADMINISTRATOR
73 | else -> MemberPermission.MEMBER
74 | }, 0, 0, title, 0, nickname, "", userId)
75 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/data/UserProfileImpl.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.data
2 |
3 | import net.mamoe.mirai.data.UserProfile
4 |
5 | internal class UserProfileImpl(
6 | override val age: Int,
7 | override val email: String,
8 | override val friendGroupId: Int,
9 | override val nickname: String,
10 | override val qLevel: Int,
11 | override val sex: UserProfile.Sex,
12 | override val sign: String
13 | ) : UserProfile
14 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/listener/bot.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.listener
2 |
3 | import cn.evolvefield.onebot.client.handler.EventBus.listen
4 | import cn.evolvefield.onebot.client.handler.EventBus.listenNormally
5 | import cn.evolvefield.onebot.sdk.event.UnsolvedEvent
6 | import cn.evolvefield.onebot.sdk.event.notice.NotifyNoticeEvent
7 | import kotlinx.coroutines.launch
8 | import net.mamoe.mirai.event.broadcast
9 | import net.mamoe.mirai.event.events.NudgeEvent
10 | import top.mrxiaom.overflow.Overflow
11 | import top.mrxiaom.overflow.event.UnsolvedOnebotEvent
12 | import top.mrxiaom.overflow.internal.contact.BotWrapper
13 | import top.mrxiaom.overflow.internal.scope
14 | import top.mrxiaom.overflow.internal.utils.group
15 |
16 | internal fun addBotListeners() {
17 | listen("poke") { e ->
18 | // 戳一戳通知
19 | val operatorId = e.realOperatorId
20 | if (checkId(operatorId, "%onebot 返回了异常的数值 operator_id=%value")) return@listen
21 | // Lagrange: notice -> notify -> poke 不仅仅适用于群聊
22 | if (e.groupId == 0L) {
23 | val operator = bot.getFriend(operatorId)
24 | ?: throw IllegalStateException("好友 $operatorId 戳一戳事件 操作者不在好友列表内")
25 | val target = bot.asFriend // 有的 Onebot 实现不一定提供 target_id,所以直接使用机器人实例
26 | // TODO: 戳一戳无法获取被戳一方的动作、后缀信息
27 | bot.eventDispatcher.broadcastAsync(NudgeEvent(operator, target, operator, "拍了拍", ""))
28 | } else {
29 | val group = bot.group(e.groupId)
30 | val operator = group.queryMember(operatorId)
31 | ?: throw IllegalStateException("群 ${group.id} 戳一戳事件 无法获取操作者")
32 | val target = group.queryMember(e.targetId)
33 | ?: throw IllegalStateException("群 ${group.id} 戳一戳事件 无法获取目标")
34 | // TODO: 戳一戳无法获取被戳一方的动作、后缀信息
35 | bot.eventDispatcher.broadcastAsync(NudgeEvent(operator, target, group, "拍了拍", ""))
36 | }
37 | }
38 | listenNormally { e ->
39 | // 未处理 Onebot 事件
40 | Overflow.scope.launch {
41 | UnsolvedOnebotEvent(e.selfId, e.jsonString, e.timeInSecond()).broadcast()
42 | }
43 | }
44 | }
45 | internal fun BotWrapper.checkId(id: Long, msg: String): Boolean {
46 | return (id <= 0).also {
47 | if (it) logger.warning(msg
48 | .replace("%onebot", "${impl.appName} v${impl.appVersion}")
49 | .replace("%value", id.toString())
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/data/UnknownMessage.kt:
--------------------------------------------------------------------------------
1 | package top.mrxiaom.overflow.internal.message.data
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 | import net.mamoe.mirai.message.data.AbstractPolymorphicMessageKey
6 | import net.mamoe.mirai.message.data.MessageContent
7 | import net.mamoe.mirai.utils.createFileIfNotExists
8 | import net.mamoe.mirai.utils.safeCast
9 | import java.io.File
10 | import java.text.SimpleDateFormat
11 | import java.util.*
12 |
13 | private fun currentTime() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())
14 |
15 | @Serializable
16 | @SerialName(UnknownMessage.SERIAL_NAME)
17 | internal data class UnknownMessage(
18 | val msgType: String,
19 | val data: String
20 | ) : MessageContent {
21 | fun printLog(): UnknownMessage = apply {
22 | val logFile = File("logs/unknown_messages.log")
23 | logFile.createFileIfNotExists()
24 | logFile.appendText("${currentTime()} I/UnknownMessage: ${contentToString()}\n")
25 | }
26 |
27 | override fun contentToString(): String {
28 | return "[overflow,unknown:$msgType,$data}]"
29 | }
30 |
31 | override fun toString(): String = contentToString()
32 |
33 | public companion object Key :
34 | AbstractPolymorphicMessageKey(MessageContent, { it.safeCast() }) {
35 | public const val SERIAL_NAME: String = "UnknownMessage"
36 | }
37 | }
--------------------------------------------------------------------------------
/overflow-core/src/main/kotlin/top/mrxiaom/overflow/internal/message/data/WrappedAudio.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
2 | package top.mrxiaom.overflow.internal.message.data
3 |
4 | import cn.evolvefield.onebot.client.util.fileType
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 | import net.mamoe.mirai.message.data.*
8 | import net.mamoe.mirai.utils.safeCast
9 | import top.mrxiaom.overflow.internal.utils.base64Length
10 | import top.mrxiaom.overflow.internal.utils.lengthToString
11 | import top.mrxiaom.overflow.spi.MediaURLService
12 | import top.mrxiaom.overflow.spi.MediaURLService.Companion.queryAudioUrl
13 | import java.util.*
14 |
15 | @Serializable
16 | @SerialName(WrappedAudio.SERIAL_NAME)
17 | internal data class WrappedAudio(
18 | var url: String,
19 | override val length: Long,
20 | ): OnlineAudio, OfflineAudio {
21 | private val _stringValue: String by lazy(LazyThreadSafetyMode.NONE) {
22 | val fileString = if (file.startsWith("base64://") && file.length > 32) {
23 | val s = file.replace("base64://", "")
24 | val len = base64Length(s)
25 | val type = Base64.getDecoder().decode(s).fileType ?: "*"
26 | "${file.substring(0, 32)}... (${if (type == "*") "" else "$type, "}about ${lengthToString(len)})"
27 | } else file
28 | "[overflow:audio,file=$fileString]"
29 | }
30 | override val codec: AudioCodec = AudioCodec.AMR
31 | override val extraData: ByteArray? = null
32 | override val fileMd5: ByteArray = ByteArray(16)
33 | override val fileSize: Long by lazy {
34 | length.takeIf { it > 0 } ?: if (!file.startsWith("base64://")) 0
35 | else base64Length(file.replace("base64://", ""))
36 | }
37 | override val urlForDownload: String
38 | get() {
39 | val extUrl = MediaURLService.instances.queryAudioUrl(this)
40 | return extUrl ?: url
41 | }
42 | override val filename: String = urlForDownload.substringAfterLast("/")
43 | val file: String = urlForDownload
44 |
45 | override fun toString(): String = _stringValue
46 |
47 | override val key: MessageKey get() = Key
48 | public companion object Key :
49 | AbstractPolymorphicMessageKey