Skip to content

Commit

Permalink
finish up automod messages (hopefully)
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnyTheCarrot committed Sep 23, 2023
1 parent a4aab59 commit 3c5b689
Show file tree
Hide file tree
Showing 10 changed files with 668 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const preview = {
values: [
{
name: "discord-dark",
value: "#424549",
value: "#313338",
},
],
},
Expand Down
5 changes: 4 additions & 1 deletion src/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ interface MessageAccessoriesProps {
children?: ReactNode;
}

function MessageAccessories({ children, active }: MessageAccessoriesProps) {
export function MessageAccessories({
children,
active,
}: MessageAccessoriesProps) {
if (!active || !children || Children.count(children) === 0) return <></>;

return <Styles.MessageAccessories>{children}</Styles.MessageAccessories>;
Expand Down
30 changes: 30 additions & 0 deletions src/Message/style/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,36 @@ export const AutomodMessageContent = styled.withConfig({
fontSize: theme.fontSizes.l,
});

export const AutomodMatchInfoContainer = styled.withConfig({
displayName: "automod-match-info-container",
componentId: commonComponentId,
})("span", {
display: "flex",
flexDirection: "row",
color: theme.colors.textMuted,
fontSize: theme.fontSizes.s,
});

export const AutomodMatchInfo = styled.withConfig({
displayName: "automod-match-info",
componentId: commonComponentId,
})("span", {
display: "flex",
flexDirection: "row",
alignItems: "center",

"&:not(:last-child)::after": {
marginLeft: theme.space.large,
marginRight: theme.space.large,
content: "",
display: "inline-block",
width: 4,
height: 4,
backgroundColor: theme.colors.automodDot,
borderRadius: "100%",
},
});

export const AutomodFlaggedKeyword = styled.withConfig({
displayName: "automod-flagged-keyword",
componentId: commonComponentId,
Expand Down
188 changes: 181 additions & 7 deletions src/Message/variants/AutomodAction.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,118 @@
import React from "react";
import type { APIMessage } from "discord-api-types/v10";
import type { APIMessage, APIReaction, Snowflake } from "discord-api-types/v10";
import * as Styles from "../style/message";
import MessageAuthor, { AutomodAuthor } from "../MessageAuthor";
import LargeTimestamp from "../LargeTimestamp";
import { Trans, useTranslation } from "react-i18next";
import { ChannelMention } from "../../markdown/render/elements/mentions";
import { useConfig } from "../../core/ConfigContext";
import { error } from "../../utils/error";
import { MessageAccessories } from "../../Content";
import Reactions from "../Reactions";

type QuarantineUserAction =
| "quarantine_user"
| "block_profile_update"
| "block_guest_join"
| undefined;

type QuarantineEvent =
| "nickname_update"
| "nickname_reset"
| "username_update"
| "message_send"
| "guild_join"
| undefined;

type VoiceChannelStatusOutcome = "blocked" | "flagged" | undefined;

interface AutomodHeaderProps {
isMessageBlocked: boolean;
profileAction: QuarantineUserAction;
quarantineEvent: QuarantineEvent;
voiceChannelStatusOutcome: VoiceChannelStatusOutcome;
channelId: Snowflake | undefined;
}

function AutomodHeader({
isMessageBlocked,
profileAction,
quarantineEvent,
voiceChannelStatusOutcome,
channelId,
}: AutomodHeaderProps) {
const { t } = useTranslation();

switch (profileAction) {
case "quarantine_user":
return t("AutomodAction.quarantine_user", {
event: t(`AutomodAction.profile_event.${quarantineEvent}`),
});
case "block_profile_update":
return t("AutomodAction.block_profile_update", {
event: t(`AutomodAction.profile_event.${quarantineEvent}`),
});
case "block_guest_join":
return t("AutomodAction.block_guest_join", {
event: t(`AutomodAction.profile_event.guild_join`),
});
}

if (!channelId) {
error("AutomodAction: Channel ID is undefined");
return null;
}

switch (voiceChannelStatusOutcome) {
case "blocked":
return (
<Trans
i18nKey="AutomodAction.voice_channel_status_blocked"
components={{
Channel: <ChannelMention channelId={channelId} />,
}}
t={t}
/>
);
case "flagged":
return (
<Trans
i18nKey="AutomodAction.voice_channel_status_flagged"
components={{
Channel: <ChannelMention channelId={channelId} />,
}}
t={t}
/>
);
}

if (isMessageBlocked)
return (
<Trans
i18nKey="AutomodAction.blocked_message"
components={{
Channel: <ChannelMention channelId={channelId} />,
}}
t={t}
/>
);

return (
<Trans
i18nKey="AutomodAction.flagged_message"
components={{
Channel: <ChannelMention channelId={channelId} />,
}}
t={t}
/>
);
}

function hasReactions(
reactions: Array<APIReaction> | undefined
): reactions is Array<APIReaction> {
return reactions !== undefined && reactions.length > 0;
}

interface AutomodActionProps {
message: APIMessage;
Expand All @@ -26,34 +133,83 @@ function AutomodAction({ message, isHovered }: AutomodActionProps) {
automodInfoEmbed.fields === undefined ||
automodInfoEmbed.description === undefined
) {
error("AutomodAction: Embed fields and/or description is undefined");
return null;
}

const keyword = automodInfoEmbed.fields.find(
(field) => field.name === "keyword"
)?.value;

const rule = automodInfoEmbed.fields.find(
(field) => field.name === "rule_name"
)?.value;

const keywordMatchedContent = automodInfoEmbed.fields.find(
(field) => field.name === "keyword_matched_content"
)?.value;

const channelId = automodInfoEmbed.fields.find(
(field) => field.name === "channel_id"
)?.value;

const voiceChannelStatusOutcome = automodInfoEmbed.fields.find(
(field) => field.name === "voice_channel_status_outcome"
)?.value as VoiceChannelStatusOutcome;

if (keyword === undefined) {
error("AutomodAction: Keyword is undefined");
return null;
}

if (rule === undefined) {
error("AutomodAction: Rule is undefined");
return null;
}

if (keywordMatchedContent === undefined) {
error("AutomodAction: Keyword matched content is undefined");
return null;
}

const timeoutDuration = automodInfoEmbed.fields.find(
(field) => field.name === "timeout_duration"
)?.value;

const messageContent = automodInfoEmbed.description.split(
keywordMatchedContent
);

const matches = automodInfoEmbed.description.match(keywordMatchedContent);

const profileAction = automodInfoEmbed.fields.find(
(field) => field.name === "quarantine_user_action"
)?.value as QuarantineUserAction;

const quarantineEvent = automodInfoEmbed.fields.find(
(field) => field.name === "quarantine_event"
)?.value as QuarantineEvent;

const flaggedMessageId = automodInfoEmbed.fields.find(
(field) => field.name === "flagged_message_id"
);

const isBlockedOrFlagged = profileAction === undefined;
const isBlocked = isBlockedOrFlagged && flaggedMessageId === undefined;

const messageHasReactions = hasReactions(message.reactions);

return (
<Styles.Message>
<Styles.MessageHeaderBase>
<AutomodAuthor isAvatarAnimated={isHovered} />
<Styles.AutomodHeaderText>
<Trans
i18nKey="AutomodAction.flaggedMessage"
components={{
Channel: <ChannelMention channelId={message.channel_id} />,
}}
t={t}
<AutomodHeader
isMessageBlocked={isBlocked}
profileAction={profileAction}
quarantineEvent={quarantineEvent}
voiceChannelStatusOutcome={voiceChannelStatusOutcome}
channelId={channelId}
/>
</Styles.AutomodHeaderText>
<LargeTimestamp timestamp={message.timestamp} />
Expand All @@ -78,7 +234,25 @@ function AutomodAction({ message, isHovered }: AutomodActionProps) {
</>
))}
</Styles.AutomodMessageContent>
<Styles.AutomodMatchInfoContainer>
<Styles.AutomodMatchInfo>
{t("AutomodAction.info.keyword", { keyword })}
</Styles.AutomodMatchInfo>
<Styles.AutomodMatchInfo>
{t("AutomodAction.info.rule", { rule })}
</Styles.AutomodMatchInfo>
{timeoutDuration && (
<Styles.AutomodMatchInfo>
{t("AutomodAction.info.timeout", { timeout: timeoutDuration })}
</Styles.AutomodMatchInfo>
)}
</Styles.AutomodMatchInfoContainer>
</Styles.AutomodMessageInQuestion>
<MessageAccessories active={messageHasReactions}>
{/* Message.reactions cannot be undefined because messageHasReactions is only true if message.reactions is not undefined or empty. */}
{/* The children of MessageAccessories don't get added to the DOM unless active is true, so if messageHasReactions is false, Reactions won't get evaluated. */}
<Reactions reactions={message.reactions as Array<APIReaction>} />
</MessageAccessories>
</Styles.Message>
);
}
Expand Down
6 changes: 4 additions & 2 deletions src/Stitches/stitches.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const stitches = createStitches({
interactiveNormal: "#dcddde",
accent: "#5865f2",
background: "#36393f",
backgroundSecondary: "#2f3136",
backgroundTertiary: "#202225",
backgroundSecondary: "#2b2d31",
backgroundTertiary: "#1e1f22",
lazyImageBackground: "#2c2e32",
messageHover: "rgba(0, 0, 0, .05)",
link: "#00b0f4",
Expand All @@ -47,6 +47,8 @@ const stitches = createStitches({
buttonSuccessHoverBackground: "#1a6334",
automodUsername: "#949CF7",
automodMatchedWord: "rgba(240, 177, 50, 0.3)",
automodMessageBackground: "rgb(43, 45, 49)",
automodDot: "rgba(78, 80, 88, 0.48)",
},
fonts: {
main: "Open Sans, sans-serif",
Expand Down
36 changes: 36 additions & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,39 @@ void i18next
},
})
.then(console.log);

if (i18next.services.formatter) {
i18next.services.formatter.add("duration", (value, lng, options) => {
const numericValue = Number(value);

if (numericValue < 60)
return i18next.t("duration.seconds", { count: numericValue }, options);

if (numericValue < 3600)
return i18next.t(
"duration.minutes",
{ count: Math.floor(numericValue / 60) },
options
);

if (numericValue < 86400)
return i18next.t(
"duration.hours",
{ count: Math.floor(numericValue / 3600) },
options
);

if (numericValue < 604800)
return i18next.t(
"duration.days",
{ count: Math.floor(numericValue / 86400) },
options
);

return i18next.t(
"duration.weeks",
{ count: Math.floor(numericValue / 604800) },
options
);
});
}
33 changes: 32 additions & 1 deletion src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,37 @@
},
"AutomodAction": {
"username": "AutoMod",
"flaggedMessage": "detected cringe in <Channel/>"
"flagged_message": "has flagged a message in <Channel/>",
"blocked_message": "has blocked a message in <Channel/>",
"block_profile_update": "blocked a {{event}}",
"quarantine_user": "has quarantined a member at {{event}}",
"block_guest_join": "has blocked a guest at {{event}}",
"voice_channel_status_flagged": "has flagged a voice channel status for <Channel/>",
"voice_channel_status_blocked": "has blocked a voice channel status for <Channel/>",
"profile_event": {
"username_update": "username update",
"nickname_update": "nickname update",
"nickname_reset": "nickname reset",
"message_send": "message send",
"guild_join": "server join"
},
"info": {
"keyword": "Keyword: {{keyword}}",
"rule": "Rule: {{rule}}",
"timeout": "Timeout: {{timeout, duration}}",
"reason": "Reason: {{reason}}"
}
},
"duration": {
"seconds_one": "1 sec",
"seconds_other": "{{count}} secs",
"minutes_one": "1 min",
"minutes_other": "{{count}} mins",
"hours_one": "1 hour",
"hours_other": "{{count}} hours",
"days_one": "1 day",
"days_other": "{{count}} days",
"weeks_one": "1 week",
"weeks_other": "{{count}} weeks"
}
}
Loading

0 comments on commit 3c5b689

Please sign in to comment.