feat(Streaks): shows a streak next to a user when you exchange dms with them on consecutive days (#1062)
* feat(Streaks): show DM streak badge for consecutive day messages * Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * bleh * ConcatenatedModules stuff * Update concatenatedModules.tsx --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
parent
91ceac2c3d
commit
747c7f23ed
5 changed files with 165 additions and 7 deletions
|
|
@ -14,19 +14,20 @@ export default definePlugin({
|
|||
name: "ConcatenatedModules",
|
||||
description: "Extract modules that have been concatenated by the bundler",
|
||||
authors: [Devs.thororen],
|
||||
|
||||
required: true,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "AngleBracketsIcon",
|
||||
replacement: {
|
||||
match: /\i\.\i\((\i)\),\i\.\i\(\i,\{AIcon/,
|
||||
replace: "$self.iconsModule($1),$&"
|
||||
replace: "$self.setIconsModule($1),$&"
|
||||
}
|
||||
}
|
||||
],
|
||||
iconsModule(value: IconsDef) {
|
||||
setIconsModule(value: IconsDef) {
|
||||
iconsModule = value;
|
||||
|
||||
// incase you dont want to use iconviewer you can do
|
||||
// Vencord.Plugins.plugins.ConcatenatedModules.iconsModule instead for icons and viewing paths
|
||||
this.iconsModule = value;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export default definePlugin({
|
|||
description: "Group your commonly visited channels in tabs, like a browser",
|
||||
tags: ["Appearance", "Customisation", "Organisation", "Servers"],
|
||||
authors: [Devs.TheSun, Devs.TheKodeToad, EquicordDevs.keifufu, Devs.Nickyux, EquicordDevs.DiabeloDEV, EquicordDevs.justjxke, EquicordDevs.keircn],
|
||||
dependencies: ["ContextMenuAPI"],
|
||||
dependencies: ["ContextMenuAPI", "ConcatenatedModules"],
|
||||
contextMenus: {
|
||||
"channel-mention-context": contextMenuPatch,
|
||||
"channel-context": contextMenuPatch,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default definePlugin({
|
|||
description: "Adds a new tab to settings to preview all icons.",
|
||||
tags: ["Developers"],
|
||||
authors: [EquicordDevs.iamme],
|
||||
dependencies: ["Settings"],
|
||||
dependencies: ["Settings", "ConcatenatedModules"],
|
||||
startAt: StartAt.WebpackReady,
|
||||
toolboxActions: {
|
||||
"Open Icons Tab"() {
|
||||
|
|
|
|||
144
src/equicordplugins/streaks/index.tsx
Normal file
144
src/equicordplugins/streaks/index.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { DecoratorProps } from "@api/MemberListDecorators";
|
||||
import { iconsModule } from "@equicordplugins/_core/concatenatedModules";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Message } from "@vencord/discord-types";
|
||||
import { ChannelStore, moment, SelectedChannelStore, Tooltip, UserStore } from "@webpack/common";
|
||||
|
||||
const cl = classNameFactory("vc-streaks-");
|
||||
const dataKey = "vc-streaks-data";
|
||||
|
||||
const Flags = {
|
||||
SENT: 1,
|
||||
RECEIVED: 2,
|
||||
BOTH: 3
|
||||
};
|
||||
|
||||
const STREAK_THRESHOLDS = {
|
||||
ELITE: 100,
|
||||
DIAMOND: 60,
|
||||
GOLD: 30,
|
||||
SILVER: 14
|
||||
};
|
||||
|
||||
interface UserStreak {
|
||||
count: number;
|
||||
lastDay: string;
|
||||
todayFlags: number;
|
||||
todayDate: string;
|
||||
}
|
||||
|
||||
let streaks: Record<string, UserStreak> = {};
|
||||
|
||||
const todayKey = () => moment().format("YYYY-MM-DD");
|
||||
const yesterdayKey = () => moment().subtract(1, "day").format("YYYY-MM-DD");
|
||||
|
||||
const updateStreak = (userId: string, outgoing: boolean) => {
|
||||
const today = todayKey();
|
||||
const entry = streaks[userId] ??= { count: 0, lastDay: "", todayFlags: 0, todayDate: "" };
|
||||
|
||||
if (entry.todayDate !== today) {
|
||||
entry.todayDate = today;
|
||||
entry.todayFlags = 0;
|
||||
}
|
||||
|
||||
const next = entry.todayFlags | (outgoing ? Flags.SENT : Flags.RECEIVED);
|
||||
if (next === entry.todayFlags) return;
|
||||
entry.todayFlags = next;
|
||||
|
||||
if (next === Flags.BOTH && entry.lastDay !== today) {
|
||||
entry.count = entry.lastDay === yesterdayKey() ? entry.count + 1 : 1;
|
||||
entry.lastDay = today;
|
||||
}
|
||||
|
||||
DataStore.set(dataKey, streaks);
|
||||
};
|
||||
|
||||
const getStreakInfo = (userId: string) => {
|
||||
const entry = streaks[userId];
|
||||
if (!entry) return null;
|
||||
const today = todayKey();
|
||||
if (entry.lastDay !== today && entry.lastDay !== yesterdayKey()) return null;
|
||||
return { count: entry.count, active: entry.lastDay === today };
|
||||
};
|
||||
|
||||
const colorFor = (streak: number) => {
|
||||
if (streak >= STREAK_THRESHOLDS.ELITE) return "#9b39fe";
|
||||
if (streak >= STREAK_THRESHOLDS.DIAMOND) return "#f7409c";
|
||||
if (streak >= STREAK_THRESHOLDS.GOLD) return "#f75340";
|
||||
if (streak >= STREAK_THRESHOLDS.SILVER) return "#f57b0b";
|
||||
return "#f59e0b";
|
||||
};
|
||||
|
||||
const StreakBadge = ({ userId }: { userId: string; }) => {
|
||||
const info = getStreakInfo(userId);
|
||||
if (!info || info.count < 1) return null;
|
||||
|
||||
const FireIcon = iconsModule?.FireIcon;
|
||||
const color = info.active ? colorFor(info.count) : "#9ca3af";
|
||||
|
||||
return (
|
||||
<Tooltip text={`${info.count} day streak`}>
|
||||
{tooltipProps => (
|
||||
<span {...tooltipProps} className={cl("badge")} style={{ color }}>
|
||||
{FireIcon && <FireIcon size="xs" color={color} />}
|
||||
<span className={cl("count")}>{info.count}</span>
|
||||
</span>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "Streaks",
|
||||
description: "Shows a streak next to a user when you exchange DMs with them on consecutive days.",
|
||||
authors: [EquicordDevs.Moowi],
|
||||
tags: ["Friends", "Fun"],
|
||||
dependencies: ["MessageDecorationsAPI", "MemberListDecoratorsAPI", "ConcatenatedModules"],
|
||||
|
||||
async start() {
|
||||
streaks = await DataStore.get(dataKey) ?? {};
|
||||
},
|
||||
|
||||
stop() {
|
||||
streaks = {};
|
||||
},
|
||||
|
||||
flux: {
|
||||
async MESSAGE_CREATE({ optimistic, type, message, channelId }: { optimistic: boolean; type: string; message: Message; channelId: string; }) {
|
||||
if (optimistic || type !== "MESSAGE_CREATE" || message.state === "SENDING") return;
|
||||
if (message.author?.bot || channelId !== SelectedChannelStore.getChannelId()) return;
|
||||
|
||||
const channel = ChannelStore.getChannel(channelId);
|
||||
if (!channel.isDM()) return;
|
||||
|
||||
const recipientId = channel.recipients[0];
|
||||
if (!recipientId) return;
|
||||
|
||||
const me = UserStore.getCurrentUser()?.id;
|
||||
if (message.author.id === me) updateStreak(recipientId, true);
|
||||
else if (message.author.id === recipientId) updateStreak(recipientId, false);
|
||||
},
|
||||
},
|
||||
|
||||
renderMessageDecoration({ message }) {
|
||||
const userId = message?.author?.id;
|
||||
if (!userId || userId === UserStore.getCurrentUser()?.id) return null;
|
||||
return <StreakBadge userId={userId} />;
|
||||
},
|
||||
|
||||
renderMemberListDecorator({ user }: DecoratorProps) {
|
||||
if (!user || user.id === UserStore.getCurrentUser()?.id) return null;
|
||||
return <StreakBadge userId={user.id} />;
|
||||
},
|
||||
});
|
||||
13
src/equicordplugins/streaks/style.css
Normal file
13
src/equicordplugins/streaks/style.css
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.vc-streaks-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.vc-streaks-count {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue