-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): implements new chat processor
- Loading branch information
1 parent
ece6e00
commit 2d627e7
Showing
6 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
core/src/main/java/net/okocraft/okochat/core/chat/context/ChannelChatContexts.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package net.okocraft.okochat.core.chat.context; | ||
|
||
import net.kyori.adventure.text.Component; | ||
import net.okocraft.okochat.api.channel.Channel; | ||
import net.okocraft.okochat.api.chat.context.ChannelChatContext; | ||
import net.okocraft.okochat.api.sender.Sender; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.Collection; | ||
|
||
public final class ChannelChatContexts { | ||
|
||
public record Preparing(@NotNull Sender sender, @NotNull String message, | ||
@NotNull Channel channel) implements ChannelChatContext { | ||
} | ||
|
||
public record Formatted(@NotNull Sender sender, @NotNull String message, @NotNull Channel channel, | ||
@NotNull Component formattedMessage) implements ChannelChatContext { | ||
} | ||
|
||
public record Ready(@NotNull Sender sender, @NotNull String message, @NotNull Channel channel, | ||
@NotNull Component formattedMessage, | ||
@NotNull Collection<Sender> recipients) implements ChannelChatContext { | ||
} | ||
|
||
private ChannelChatContexts() { | ||
throw new UnsupportedOperationException(); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
core/src/main/java/net/okocraft/okochat/core/chat/context/PrivateChatContexts.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package net.okocraft.okochat.core.chat.context; | ||
|
||
import net.kyori.adventure.text.Component; | ||
import net.okocraft.okochat.api.chat.context.PrivateChatContext; | ||
import net.okocraft.okochat.api.sender.Sender; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
public final class PrivateChatContexts { | ||
|
||
public record Preparing(@NotNull Sender sender, @NotNull String message, | ||
@NotNull Sender target) implements PrivateChatContext { | ||
} | ||
|
||
public record Ready(@NotNull Sender sender, @NotNull String message, @NotNull Sender target, | ||
@NotNull Component formattedMessage) implements PrivateChatContext { | ||
} | ||
|
||
private PrivateChatContexts() { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
|
||
} |
180 changes: 180 additions & 0 deletions
180
core/src/main/java/net/okocraft/okochat/core/chat/processor/ChannelChatProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package net.okocraft.okochat.core.chat.processor; | ||
|
||
import com.github.siroshun09.event4j.caller.EventCaller; | ||
import net.okocraft.okochat.api.channel.Channel; | ||
import net.okocraft.okochat.api.channel.member.status.MemberStatus; | ||
import net.okocraft.okochat.api.chat.context.ChannelChatContext; | ||
import net.okocraft.okochat.api.chat.converter.ChatConverter; | ||
import net.okocraft.okochat.api.chat.ngword.DetectedNGWord; | ||
import net.okocraft.okochat.api.chat.ngword.NGWord; | ||
import net.okocraft.okochat.api.chat.result.ChannelChatResult; | ||
import net.okocraft.okochat.api.event.LunaChatEvent; | ||
import net.okocraft.okochat.api.event.chat.channelchat.ChannelFindEvent; | ||
import net.okocraft.okochat.api.event.chat.channelchat.PostChannelChatEvent; | ||
import net.okocraft.okochat.api.event.chat.channelchat.PreChannelChatEvent; | ||
import net.okocraft.okochat.api.identity.Identified; | ||
import net.okocraft.okochat.api.sender.Sender; | ||
import net.okocraft.okochat.api.util.either.Either; | ||
import net.okocraft.okochat.core.chat.context.ChannelChatContexts; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.function.Function; | ||
import java.util.regex.Pattern; | ||
|
||
public record ChannelChatProcessor( | ||
@Nullable String globalChannelMarker, | ||
@Nullable Channel globalChannel, | ||
@Nullable Pattern quickChatSeparatorPattern, | ||
@NotNull Function<String, Channel> channelAccessor, | ||
@NotNull Function<Sender, Channel> defaultChannelProvider, | ||
@NotNull EventCaller<? super LunaChatEvent> eventCaller, | ||
@NotNull Function<Sender, ChatConverter> converterAccessor, | ||
@NotNull String disableChatConvertMarker, | ||
@NotNull Collection<NGWord> ngWords | ||
) implements ChatProcessor<ProcessContext.ChannelChat, ChannelChatContext, ChannelChatResult> { | ||
|
||
@Override | ||
public @NotNull ChannelChatResult process(@NotNull ProcessContext.ChannelChat context) { | ||
return this.findChannel(context) | ||
.flatMap(this::callPreChannelChatEvent) | ||
.flatMap(this::checkPermission) | ||
.flatMap(this::checkMuted) | ||
.map(this::convertChatMessageIfNeeded) | ||
.flatMap(this::detectNGWords) | ||
.map(this::formatChannelChat) | ||
.map(this::collectRecipients) | ||
.map(this::sendMessage) | ||
.map(this::callPostChannelChatEvent) | ||
.fold(Function.identity(), this::success); | ||
} | ||
|
||
@Override | ||
public @NotNull ChannelChatResult onNonMaskableNGWordDetected(@NotNull ChannelChatContext context, @NotNull DetectedNGWord detectedNGWord) { | ||
return new ChannelChatResult.NGWord(context.sender(), context.message(), context.channel(), detectedNGWord); | ||
} | ||
|
||
@Override | ||
public @NotNull ChannelChatContext createContextWithNewMessage(@NotNull ChannelChatContext context, @NotNull String newMessage) { | ||
return new ChannelChatContexts.Preparing(context.sender(), newMessage, context.channel()); | ||
} | ||
|
||
private @NotNull Either<ChannelChatResult, ChannelChatContext> findChannel(@NotNull ProcessContext.ChannelChat processContext) { | ||
return Either.<ProcessContext.ChannelChat, Either<ChannelChatResult, ChannelChatContext>>left(processContext) | ||
.flatMapLeft(this::useSpecifiedChannelIfPossible) | ||
.flatMapLeft(this::findGlobalChatMarker) | ||
.flatMapLeft(this::findQuickChannelMarker) | ||
.flatMapLeft(this::findDefaultChannel) | ||
.flatMapLeft(this::fallbackToGlobalChannel) | ||
.mapLeft(data -> (ChannelChatResult) new ChannelChatResult.ChannelNotFound(data.sender(), data.message(), null)) | ||
.flatMap(either -> either.isRight() ? this.callChannelFindEvent(processContext, either.right()) : either); | ||
} | ||
|
||
private @NotNull Either<ProcessContext.ChannelChat, Either<ChannelChatResult, ChannelChatContext>> useSpecifiedChannelIfPossible(@NotNull ProcessContext.ChannelChat context) { | ||
return context.channel() != null ? Either.right(Either.right(new ChannelChatContexts.Preparing(context.sender(), context.message(), context.channel()))) : Either.left(context); | ||
} | ||
|
||
private @NotNull Either<ProcessContext.ChannelChat, Either<ChannelChatResult, ChannelChatContext>> findGlobalChatMarker(ProcessContext.ChannelChat processContext) { | ||
if (this.globalChannelMarker == null || this.globalChannel == null) { | ||
return Either.left(processContext); | ||
} | ||
|
||
int markerLength = this.globalChannelMarker.length(); | ||
var message = processContext.message(); | ||
|
||
return markerLength != 0 && markerLength < message.length() && message.startsWith(this.globalChannelMarker) ? | ||
Either.right(channelFound(processContext.sender(), message.substring(markerLength), this.globalChannel)) : | ||
Either.left(processContext); | ||
} | ||
|
||
private @NotNull Either<ProcessContext.ChannelChat, Either<ChannelChatResult, ChannelChatContext>> findQuickChannelMarker(@NotNull ProcessContext.ChannelChat processContext) { | ||
if (this.quickChatSeparatorPattern == null) { | ||
return Either.left(processContext); | ||
} | ||
|
||
var message = processContext.message(); | ||
var parts = this.quickChatSeparatorPattern.split(message, 2); | ||
|
||
if (parts.length == 2 && !parts[1].isEmpty()) { | ||
var channel = this.channelAccessor.apply(parts[0]); | ||
return Either.right( | ||
channel != null ? | ||
Either.right(new ChannelChatContexts.Preparing(processContext.sender(), parts[1], channel)) : | ||
Either.left(new ChannelChatResult.ChannelNotFound(processContext.sender(), parts[1], parts[0])) | ||
); | ||
} else { | ||
return Either.left(processContext); | ||
} | ||
} | ||
|
||
private @NotNull Either<ProcessContext.ChannelChat, Either<ChannelChatResult, ChannelChatContext>> findDefaultChannel(@NotNull ProcessContext.ChannelChat processContext) { | ||
var defaultChannel = this.defaultChannelProvider.apply(processContext.sender()); | ||
return defaultChannel != null ? | ||
Either.right(channelFound(processContext.sender(), processContext.message(), defaultChannel)) : | ||
Either.left(processContext); | ||
} | ||
|
||
private @NotNull Either<ProcessContext.ChannelChat, Either<ChannelChatResult, ChannelChatContext>> fallbackToGlobalChannel(@NotNull ProcessContext.ChannelChat processContext) { | ||
return this.globalChannel != null ? | ||
Either.right(Either.right(new ChannelChatContexts.Preparing(processContext.sender(), processContext.message(), this.globalChannel))) : | ||
Either.left(processContext); | ||
} | ||
|
||
private @NotNull Either<ChannelChatResult, ChannelChatContext> callChannelFindEvent(@NotNull ProcessContext.ChannelChat processContext, @NotNull ChannelChatContext context) { | ||
var event = new ChannelFindEvent(context.channel(), context.sender(), processContext.message(), context.message()); | ||
this.eventCaller.call(event); | ||
return event.isCancelled() ? | ||
Either.left(new ChannelChatResult.ChannelNotFound(context.sender(), processContext.message(), null)) : | ||
Either.right(new ChannelChatContexts.Preparing(event.getSender(), event.getResultChatMessage(), event.getResultChannel())); | ||
} | ||
|
||
private static @NotNull Either<ChannelChatResult, ChannelChatContext> channelFound(@NotNull Sender sender, @NotNull String message, @NotNull Channel channel) { | ||
return Either.right(new ChannelChatContexts.Preparing(sender, message, channel)); | ||
} | ||
|
||
private @NotNull Either<ChannelChatResult, ChannelChatContext> callPreChannelChatEvent(@NotNull ChannelChatContext context) { | ||
var event = new PreChannelChatEvent(context.channel(), context.sender(), context.message()); | ||
this.eventCaller.call(event); | ||
return event.isCancelled() ? | ||
Either.left(new ChannelChatResult.EventCancelled(event.getSender(), event.getOriginalMessage(), event.getChannel())) : | ||
Either.right(new ChannelChatContexts.Preparing(event.getSender(), event.getResultMessage(), event.getChannel())); | ||
} | ||
|
||
private @NotNull Either<ChannelChatResult, ChannelChatContext> checkPermission(@NotNull ChannelChatContext context) { | ||
var permission = context.channel().getSpeakPermission(); | ||
return context.sender().hasPermission(permission.node(), permission.defaultValue()) ? | ||
Either.right(context) : | ||
Either.left(new ChannelChatResult.NoPermission(context.sender(), context.message(), context.channel(), permission)); | ||
} | ||
|
||
private @NotNull Either<ChannelChatResult, ChannelChatContext> checkMuted(@NotNull ChannelChatContext context) { | ||
return context.sender() instanceof Identified identified && context.channel().getMemberHolder().getStatus(identified.identity()) instanceof MemberStatus.Muted ? | ||
Either.left(new ChannelChatResult.Muted(context.sender(), context.message(), context.channel())) : | ||
Either.right(context); | ||
} | ||
|
||
private @NotNull ChannelChatContexts.Formatted formatChannelChat(@NotNull ChannelChatContext context) { | ||
return new ChannelChatContexts.Formatted(context.sender(), context.message(), context.channel(), context.channel().getChatMessageFormat().render(context)); | ||
} | ||
|
||
private @NotNull ChannelChatContexts.Ready collectRecipients(@NotNull ChannelChatContexts.Formatted context) { | ||
// TODO | ||
return new ChannelChatContexts.Ready(context.sender(), context.message(), context.channel(), context.formattedMessage(), Collections.emptyList()); | ||
} | ||
|
||
private @NotNull ChannelChatContexts.Ready sendMessage(@NotNull ChannelChatContexts.Ready context) { | ||
context.recipients().forEach(recipient -> recipient.sendMessage(context.formattedMessage())); // TODO: event? | ||
return context; | ||
} | ||
|
||
private @NotNull ChannelChatContexts.Ready callPostChannelChatEvent(@NotNull ChannelChatContexts.Ready context) { | ||
this.eventCaller.call(new PostChannelChatEvent(context.channel(), context.sender(), context.message(), context.formattedMessage(), context.recipients())); | ||
return context; | ||
} | ||
|
||
private @NotNull ChannelChatResult success(@NotNull ChannelChatContexts.Ready context) { | ||
return new ChannelChatResult.Success(context.sender(), context.message(), context.channel(), context.formattedMessage(), context.recipients()); | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
core/src/main/java/net/okocraft/okochat/core/chat/processor/ChatProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package net.okocraft.okochat.core.chat.processor; | ||
|
||
import net.okocraft.okochat.api.chat.context.ChatContext; | ||
import net.okocraft.okochat.api.chat.converter.ChatConverter; | ||
import net.okocraft.okochat.api.sender.Sender; | ||
import net.okocraft.okochat.api.util.either.Either; | ||
import net.okocraft.okochat.api.chat.ngword.DetectedNGWord; | ||
import net.okocraft.okochat.api.chat.ngword.NGWord; | ||
import net.okocraft.okochat.api.chat.ngword.NGWordOperationType; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.Collection; | ||
import java.util.function.Function; | ||
|
||
public interface ChatProcessor<PC extends ProcessContext, C extends ChatContext, R> { | ||
|
||
@NotNull R process(@NotNull PC chatData); | ||
|
||
@NotNull Function<Sender, ChatConverter> converterAccessor(); | ||
|
||
@NotNull String disableChatConvertMarker(); | ||
|
||
@NotNull Collection<NGWord> ngWords(); | ||
|
||
@NotNull R onNonMaskableNGWordDetected(@NotNull C context, @NotNull DetectedNGWord detectedNGWord); | ||
|
||
@NotNull C createContextWithNewMessage(@NotNull C context, @NotNull String newMessage); | ||
|
||
default @NotNull C convertChatMessageIfNeeded(@NotNull C context) { | ||
var converter = this.converterAccessor().apply(context.sender()); | ||
|
||
if (converter == ChatConverter.NOOP) { | ||
return context; | ||
} | ||
|
||
var disableMarker = this.disableChatConvertMarker(); | ||
|
||
if (!disableMarker.isEmpty() && context.message().startsWith(disableMarker)) { | ||
return context.message().equals(disableMarker) ? | ||
context : | ||
this.createContextWithNewMessage(context, context.message().substring(disableMarker.length())); | ||
} | ||
|
||
return this.createContextWithNewMessage(context, converter.convert(context.message())); | ||
} | ||
|
||
default @NotNull Either<R, C> detectNGWords(@NotNull C context) { | ||
var currentMessage = context.message(); | ||
boolean masked = false; | ||
|
||
for (var ngWord : this.ngWords()) { | ||
var result = ngWord.detect(currentMessage); | ||
if (result.isPresent()) { | ||
var detected = result.get(); | ||
if (detected.ngWord().operationType() == NGWordOperationType.MASK) { | ||
currentMessage = detected.maskedMessage().orElseThrow(); | ||
masked = true; | ||
} else { | ||
return Either.left(this.onNonMaskableNGWordDetected(context, detected)); | ||
} | ||
} | ||
} | ||
|
||
return Either.right(masked ? this.createContextWithNewMessage(context, currentMessage) : context); | ||
} | ||
} |
86 changes: 86 additions & 0 deletions
86
core/src/main/java/net/okocraft/okochat/core/chat/processor/PrivateChatProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package net.okocraft.okochat.core.chat.processor; | ||
|
||
import com.github.siroshun09.event4j.caller.EventCaller; | ||
import net.okocraft.okochat.api.chat.context.PrivateChatContext; | ||
import net.okocraft.okochat.api.chat.format.ChatMessageFormat; | ||
import net.okocraft.okochat.api.chat.converter.ChatConverter; | ||
import net.okocraft.okochat.api.event.LunaChatEvent; | ||
import net.okocraft.okochat.api.event.chat.privatechat.PostPrivateChatEvent; | ||
import net.okocraft.okochat.api.event.chat.privatechat.PrePrivateChatEvent; | ||
import net.okocraft.okochat.api.sender.Sender; | ||
import net.okocraft.okochat.api.util.either.Either; | ||
import net.okocraft.okochat.core.chat.context.PrivateChatContexts; | ||
import net.okocraft.okochat.api.chat.result.PrivateChatResult; | ||
import net.okocraft.okochat.api.chat.ngword.DetectedNGWord; | ||
import net.okocraft.okochat.api.chat.ngword.NGWord; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.Collection; | ||
import java.util.function.Function; | ||
|
||
public record PrivateChatProcessor( | ||
@NotNull EventCaller<? super LunaChatEvent> eventCaller, | ||
@NotNull ChatMessageFormat<PrivateChatContext> chatMessageFormat, | ||
@NotNull Function<Sender, ChatConverter> converterAccessor, | ||
@NotNull String disableChatConvertMarker, | ||
@NotNull Collection<NGWord> ngWords | ||
) implements ChatProcessor<ProcessContext.PrivateChat, PrivateChatContext, PrivateChatResult> { | ||
|
||
@Override | ||
public @NotNull PrivateChatResult process(@NotNull ProcessContext.PrivateChat context) { | ||
return this.toPrivateChatContext(context) | ||
.flatMap(this::callPrePrivateChatEvent) | ||
.flatMap(this::checkMuted) | ||
.map(this::convertChatMessageIfNeeded) | ||
.flatMap(this::detectNGWords) | ||
.map(this::formatPrivateChat) | ||
.map(this::sendMessage) | ||
.map(this::callPostPrivateChatEvent) | ||
.fold(Function.identity(), this::success); | ||
} | ||
|
||
@Override | ||
public @NotNull PrivateChatResult onNonMaskableNGWordDetected(@NotNull PrivateChatContext context, @NotNull DetectedNGWord detectedNGWord) { | ||
return new PrivateChatResult.NGWord(context.sender(), context.message(), context.target(), detectedNGWord); | ||
} | ||
|
||
@Override | ||
public @NotNull PrivateChatContext createContextWithNewMessage(@NotNull PrivateChatContext context, @NotNull String newMessage) { | ||
return new PrivateChatContexts.Preparing(context.sender(), newMessage, context.target()); | ||
} | ||
|
||
private @NotNull Either<PrivateChatResult, PrivateChatContext> toPrivateChatContext(@NotNull ProcessContext.PrivateChat processContext) { | ||
return Either.right(new PrivateChatContexts.Preparing(processContext.sender(), processContext.message(), processContext.target())); | ||
} | ||
|
||
private @NotNull Either<PrivateChatResult, PrivateChatContext> callPrePrivateChatEvent(@NotNull PrivateChatContext context) { | ||
var event = new PrePrivateChatEvent(context.sender(), context.target(), context.message()); | ||
this.eventCaller.call(event); | ||
return event.isCancelled() ? | ||
Either.left(new PrivateChatResult.EventCancelled(event.getSender(), event.getOriginalMessage(), event.getTarget())) : | ||
Either.right(new PrivateChatContexts.Preparing(event.getSender(), event.getResultMessage(), event.getTarget())); | ||
} | ||
|
||
private @NotNull Either<PrivateChatResult, PrivateChatContext> checkMuted(@NotNull PrivateChatContext context) { // TODO | ||
return Either.right(context); | ||
} | ||
|
||
private @NotNull PrivateChatContexts.Ready formatPrivateChat(@NotNull PrivateChatContext context) { | ||
return new PrivateChatContexts.Ready(context.sender(), context.message(), context.target(), this.chatMessageFormat.render(context)); | ||
} | ||
|
||
private @NotNull PrivateChatContexts.Ready callPostPrivateChatEvent(@NotNull PrivateChatContexts.Ready context) { | ||
this.eventCaller.call(new PostPrivateChatEvent(context.sender(), context.target(), context.message(), context.formattedMessage())); | ||
return context; | ||
} | ||
|
||
private @NotNull PrivateChatContexts.Ready sendMessage(@NotNull PrivateChatContexts.Ready context) { | ||
context.sender().sendMessage(context.formattedMessage()); | ||
context.target().sendMessage(context.formattedMessage()); | ||
return context; | ||
} | ||
|
||
private @NotNull PrivateChatResult success(@NotNull PrivateChatContexts.Ready context) { | ||
return new PrivateChatResult.Success(context.sender(), context.message(), context.target(), context.formattedMessage()); | ||
} | ||
} |
Oops, something went wrong.