useChatInputActions.ts 14.3 Ko
Télécharger le fichier
import {
AvatarExpressionEnum,
CreateLinkEvent,
GetEventDispatcher,
GetRoomEngine,
GetSessionDataManager,
GetTicker,
HabboClubLevelEnum,
RoomControllerLevel,
RoomRotatingEffect,
RoomSettingsComposer,
RoomShakingEffect,
RoomZoomEvent,
TextureUtils,
TriggerHabbiconComposer
} from '@octane/renderer';
import { useCallback, useRef } from 'react';
import { CanOpenFurniChooser, CanOpenUserChooser,ChatMessageTypeEnum, GetClubMemberLevel, GetConfigurationValue, IsAnyRoomController, LocalizeText, parseSignId, SendMessageComposer, WIRED_PLAYTEST_TOGGLE_LINK } from '../../../api';
import { useNotification } from '../../notification';
import { useTranslation } from '../../translation';
import { useRoom } from '../useRoom';
/**
* Pure imperative dispatch for the chat-input widget. Exposes
* `sendChat(text, chatType, recipientName?, styleId?)` which:
*
* 1. Intercepts in-room slash commands (`:shake`, `:rotate`, `:zoom`,
* `:screenshot`, `:pickall`, ...) and turns them into the matching
* renderer/composer call — these never reach the server as chat
* payload.
* 2. Falls back to the regular default/shout/whisper composer path,
* optionally piping the text through the translation pipeline if
* outgoing translation is enabled.
*
* No state lives in this hook — the typing/flood/idle state belongs
* to useChatInputState.
*/
export const useChatInputActions = () => {
const { showOctaneAlert = null, showConfirm = null } = useNotification();
const { settings, translateOutgoing, enqueueOutgoingTranslation } = useTranslation();
const { roomSession = null } = useRoom();
// Translated lines leave in the order they were typed and only in the room they were typed in: each waits for
// the one before it, and one whose room has changed meanwhile is dropped.
const roomSessionRef = useRef(roomSession);
roomSessionRef.current = roomSession;
const translatedSendsRef = useRef<Promise<void>>(Promise.resolve());
const sendChat = useCallback(
(text: string, chatType: number, recipientName: string = '', styleId: number = 0) => {
if (text === '') return null;
const parts = text.split(' ');
if (parts.length > 0) {
const firstPart = parts[0];
let secondPart = '';
if (parts.length > 1) secondPart = parts[1];
if (firstPart.charAt(0) === ':' && secondPart === 'x') {
const selectedAvatarId = GetRoomEngine().selectedAvatarId;
if (selectedAvatarId > -1) {
const userData = roomSession?.userDataManager?.getUserDataByIndex(selectedAvatarId);
if (userData) {
secondPart = userData.name;
text = text.replace(' x', ' ' + userData.name);
}
}
}
switch (firstPart.toLowerCase()) {
case ':shake':
RoomShakingEffect.init(2500, 5000);
RoomShakingEffect.turnVisualizationOn();
return null;
case ':rotate':
RoomRotatingEffect.init(2500, 5000);
RoomRotatingEffect.turnVisualizationOn();
return null;
case ':d':
case ';d':
if (GetClubMemberLevel() === HabboClubLevelEnum.VIP) {
roomSession?.sendExpressionMessage(AvatarExpressionEnum.LAUGH.ordinal);
}
break;
case 'o/':
case '_o/':
roomSession?.sendExpressionMessage(AvatarExpressionEnum.WAVE.ordinal);
return null;
case ':kiss':
if (GetClubMemberLevel() === HabboClubLevelEnum.VIP) {
roomSession?.sendExpressionMessage(AvatarExpressionEnum.BLOW.ordinal);
return null;
}
break;
case ':67':
if (GetConfigurationValue('avatar.expression.67.enabled', false) && GetClubMemberLevel() === HabboClubLevelEnum.VIP) {
roomSession?.sendExpressionMessage(AvatarExpressionEnum.EXPRESSION_67.ordinal);
return null;
}
break;
case ':jump':
if (GetClubMemberLevel() === HabboClubLevelEnum.VIP) {
roomSession?.sendExpressionMessage(AvatarExpressionEnum.JUMP.ordinal);
return null;
}
break;
case ':idle':
roomSession?.sendExpressionMessage(AvatarExpressionEnum.IDLE.ordinal);
return null;
case '_b':
roomSession?.sendExpressionMessage(AvatarExpressionEnum.RESPECT.ordinal);
return null;
case ':sign': {
const signId = parseSignId(secondPart);
if (signId !== null) roomSession?.sendSignMessage(signId);
return null;
}
case ':habbicon': {
const habbiconId = parseInt(secondPart);
if(Number.isFinite(habbiconId) && habbiconId > 0) SendMessageComposer(new TriggerHabbiconComposer(habbiconId));
return null;
}
case ':iddqd':
case ':flip':
if (roomSession) GetEventDispatcher().dispatchEvent(new RoomZoomEvent(roomSession.roomId, -1, true));
return null;
case ':zoom': {
const zoomLevel = parseInt(secondPart);
if (roomSession && Number.isFinite(zoomLevel)) GetEventDispatcher().dispatchEvent(new RoomZoomEvent(roomSession.roomId, zoomLevel));
return null;
}
case ':screenshot':
if (!roomSession) return null;
{
const texture = GetRoomEngine().createTextureFromRoom(roomSession.roomId, 1);
(async () => {
try {
const imageUrl = await TextureUtils.generateImageUrl(texture);
texture?.destroy?.(true);
if (!imageUrl) return;
const link = document.createElement('a');
link.href = imageUrl;
link.download = `room_${roomSession.roomId}_${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (e) {
console.warn('[Screenshot] Failed:', e);
}
})();
}
return null;
case ':pickall':
if (roomSession?.isRoomOwner || IsAnyRoomController()) {
showConfirm(
LocalizeText('room.confirm.pick_all'),
() => {
GetSessionDataManager().sendSpecialCommandMessage(':pickall');
},
null,
null,
null,
LocalizeText('generic.alert.title')
);
}
return null;
case ':pickallbc':
if (
roomSession?.isRoomOwner ||
IsAnyRoomController() ||
(roomSession?.controllerLevel ?? 0) >= RoomControllerLevel.GUEST
) {
showConfirm(
LocalizeText('room.confirm.pick_all_bc'),
() => {
GetSessionDataManager().sendSpecialCommandMessage(':pickallbc');
},
null,
null,
null,
LocalizeText('generic.alert.title')
);
}
return null;
case ':ejectall':
if (
roomSession?.isRoomOwner ||
IsAnyRoomController() ||
(roomSession?.controllerLevel ?? 0) >= RoomControllerLevel.GUEST
) {
showConfirm(
LocalizeText('room.confirm.eject_all'),
() => {
GetSessionDataManager().sendSpecialCommandMessage(':ejectall');
},
null,
null,
null,
LocalizeText('generic.alert.title')
);
}
return null;
case ':furni':
// AIR: rights holders, security level 2+ and ambassadors only; anyone else is swallowed silently.
if (CanOpenFurniChooser(roomSession)) CreateLinkEvent('furni-chooser/');
return null;
case ':chooser':
// AIR: swallowed silently for visitors below rights while the room's :chooser disabler is on.
if (CanOpenUserChooser(roomSession)) CreateLinkEvent('user-chooser/');
return null;
case ':floor':
case ':bcfloor':
if ((roomSession?.controllerLevel ?? 0) >= RoomControllerLevel.ROOM_OWNER) CreateLinkEvent('floor-editor/show');
return null;
case ':wf':
case ':wired':
CreateLinkEvent('wiredmenu/open');
return null;
case ':var':
case ':variables':
CreateLinkEvent('wiredmenu/open/variable_overview');
return null;
case ':inspect':
case ':inspection':
CreateLinkEvent('wiredmenu/open/inspection');
return null;
case ':playtest':
// AIR ChatInputWidgetHandler: flips the wired menu playtest preference, with its notification.
CreateLinkEvent(WIRED_PLAYTEST_TOGGLE_LINK);
return null;
case ':togglefps': {
if (GetTicker().maxFPS > 0) GetTicker().maxFPS = 0;
else GetTicker().maxFPS = GetConfigurationValue<number>('system.fps.animation', 24);
return null;
}
case ':client':
case ':octane':
showOctaneAlert();
return null;
case ':settings':
if (roomSession && (roomSession.isRoomOwner || IsAnyRoomController())) {
SendMessageComposer(new RoomSettingsComposer(roomSession.roomId));
}
return null;
case ':customize':
CreateLinkEvent('customize/show');
return null;
}
}
if (!roomSession) return null;
const preserveTrailingSpaces = (message: string) => {
if (message.startsWith(':')) return message;
return message.replace(/ +$/g, (match) => ' '.repeat(match.length));
};
const dispatchChatMessage = (message: string) => {
const preservedMessage = preserveTrailingSpaces(message);
switch (chatType) {
case ChatMessageTypeEnum.CHAT_DEFAULT:
roomSession.sendChatMessage(preservedMessage, styleId);
return;
case ChatMessageTypeEnum.CHAT_SHOUT:
roomSession.sendShoutMessage(preservedMessage, styleId);
return;
case ChatMessageTypeEnum.CHAT_WHISPER:
roomSession.sendWhisperMessage(recipientName, preservedMessage, styleId);
return;
}
};
const trimmedText = text.trimStart();
const shouldTranslateOutgoing = settings.enabled && !!trimmedText.length && trimmedText.charAt(0) !== ':';
if (!shouldTranslateOutgoing) {
dispatchChatMessage(text);
return null;
}
const sessionAtSend = roomSession;
const translating = translateOutgoing(text).catch(() => null);
translatedSendsRef.current = translatedSendsRef.current.then(async () => {
const translation = await translating;
if (roomSessionRef.current !== sessionAtSend) return;
if (translation) {
enqueueOutgoingTranslation(translation);
dispatchChatMessage(translation.translatedText);
return;
}
dispatchChatMessage(text);
});
return null;
},
[roomSession, settings, translateOutgoing, enqueueOutgoingTranslation, showConfirm, showOctaneAlert]
);
return { sendChat };
};
