mirror of
https://github.com/jie65535/JChatGPT.git
synced 2026-09-15 02:56:10 +08:00
This commit is contained in:
@@ -68,6 +68,21 @@ chatFallbackModelAliases: []
|
||||
|
||||
修改配置后执行 `/jgpt reload`。其他模型、工具和实验功能按需配置,缺少依赖或凭据的可选工具不会启用。
|
||||
|
||||
### 群聊触发限制
|
||||
|
||||
默认开启 `requireOwnerInGroup`:只有配置的主人也在群内,JChatGPT 才允许触发群聊对话。请在 `Config.yml` 中填写主人的 QQ:
|
||||
|
||||
```yaml
|
||||
ownerId: 123456789 # 替换为你的 QQ 号
|
||||
requireOwnerInGroup: true
|
||||
```
|
||||
|
||||
此限制优先于聊天权限,覆盖 `@Bot`、引用回复、关键词以及连续会话触发。主人未配置、QQ 号无效、不在群内或成员查询失败时,均静默忽略,不发送拒绝提示。检查使用 Overflow 提供的当前群成员信息,不使用历史联系人快照;成员变动的生效时间取决于 Overflow 的同步。
|
||||
|
||||
私聊仍按原有逻辑处理,聊天记录继续保存,画像相关配置、管理命令和其他插件不受此开关影响。开关限制新的消息触发,不会强制取消已经开始的模型请求或工具调用。设为 `false` 可恢复原有群聊触发行为,修改后执行 `/jgpt reload`。
|
||||
|
||||
升级后此开关也默认开启;未配置 `ownerId` 时,所有群聊对话触发都会被忽略。
|
||||
|
||||
## 使用
|
||||
|
||||
- 群聊中 `@Bot`,或回复 Bot 的消息
|
||||
|
||||
@@ -32,6 +32,7 @@ import top.jie65535.mirai.config.ModelConfig
|
||||
import top.jie65535.mirai.config.ModelConfigMigration
|
||||
import top.jie65535.mirai.conversation.ConversationContext
|
||||
import top.jie65535.mirai.conversation.ConversationEngine
|
||||
import top.jie65535.mirai.conversation.allowsGroupChatTrigger
|
||||
import top.jie65535.mirai.data.ChatHistoryStore
|
||||
import top.jie65535.mirai.data.ChatMessageRecord
|
||||
import top.jie65535.mirai.data.ContactSnapshotRefresher
|
||||
@@ -175,6 +176,18 @@ object JChatGPT : KotlinPlugin(
|
||||
private suspend fun onMessage(event: MessageEvent) {
|
||||
if (LargeLanguageModels.chat == null) return
|
||||
|
||||
if (event is GroupMessageEvent && !allowsGroupChatTrigger(
|
||||
requireOwnerInGroup = PluginConfig.requireOwnerInGroup,
|
||||
ownerId = PluginConfig.ownerId,
|
||||
isMember = { ownerId -> event.group[ownerId] != null },
|
||||
onFailure = { cause ->
|
||||
logger.warning(
|
||||
"检查主人是否在群内失败,忽略群聊触发:bot=${event.bot.id}, group=${event.group.id}",
|
||||
cause,
|
||||
)
|
||||
},
|
||||
)) return
|
||||
|
||||
if (ConversationEngine.isExpectedUser(event)) {
|
||||
if (shouldIgnoreBecauseMuted(event)) return
|
||||
if (ConversationEngine.resumeObserved(event)) return
|
||||
|
||||
@@ -21,6 +21,9 @@ object PluginConfig : AutoSavePluginConfig("Config") {
|
||||
@ValueDescription("主人QQ,AI可以通过工具向主人发起请求,会等待一段时间")
|
||||
val ownerId: Long by value()
|
||||
|
||||
@ValueDescription("是否仅在主人(ownerId)也在的群内允许聊天触发,默认开启;主人未配置、不在群内或无法确认时静默忽略。仅限制群聊,不影响私聊和聊天记录保存")
|
||||
val requireOwnerInGroup: Boolean by value(true)
|
||||
|
||||
@ValueDescription("OpenAI API base url")
|
||||
val openAiApi: String by value("https://dashscope.aliyuncs.com/compatible-mode/v1/")
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package top.jie65535.mirai.conversation
|
||||
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
internal fun allowsGroupChatTrigger(
|
||||
requireOwnerInGroup: Boolean,
|
||||
ownerId: Long,
|
||||
isMember: (Long) -> Boolean,
|
||||
onFailure: (Exception) -> Unit,
|
||||
): Boolean {
|
||||
if (!requireOwnerInGroup) return true
|
||||
if (ownerId <= 0) return false
|
||||
return try {
|
||||
isMember(ownerId)
|
||||
} catch (cause: CancellationException) {
|
||||
throw cause
|
||||
} catch (cause: Exception) {
|
||||
onFailure(cause)
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package top.jie65535.mirai.conversation
|
||||
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class GroupChatTriggerPolicyTest {
|
||||
@Test
|
||||
fun disabledRestrictionDoesNotLookUpOwner() {
|
||||
assertTrue(allowsGroupChatTrigger(false, 0, { error("Unexpected lookup") }, { throw it }))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingOrInvalidOwnerBlocksWithoutLookup() {
|
||||
for (ownerId in listOf(0L, -1L)) {
|
||||
assertFalse(allowsGroupChatTrigger(true, ownerId, { error("Unexpected lookup") }, { throw it }))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun configuredOwnerMustBePresentAndMembershipIsCheckedAgainOnEachTrigger() {
|
||||
val members = mutableSetOf(123L, 456L)
|
||||
val isMember: (Long) -> Boolean = { ownerId ->
|
||||
assertEquals(123L, ownerId)
|
||||
ownerId in members
|
||||
}
|
||||
assertTrue(allowsGroupChatTrigger(true, 123L, isMember, { throw it }))
|
||||
members.remove(123L)
|
||||
assertFalse(allowsGroupChatTrigger(true, 123L, isMember, { throw it }))
|
||||
members.add(123L)
|
||||
assertTrue(allowsGroupChatTrigger(true, 123L, isMember, { throw it }))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unavailableMemberLookupBlocksAndReportsFailure() {
|
||||
val failure = UnsupportedOperationException("Member lookup unavailable")
|
||||
var reported: Exception? = null
|
||||
assertFalse(allowsGroupChatTrigger(true, 123L, { throw failure }, { reported = it }))
|
||||
assertSame(failure, reported)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cancellationPropagatesWithoutReportingLookupFailure() {
|
||||
val cancellation = CancellationException("Cancelled")
|
||||
assertSame(cancellation, assertFailsWith<CancellationException> {
|
||||
allowsGroupChatTrigger(true, 123L, { throw cancellation }, { error("Unexpected failure report") })
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user