Plugin

Summaries

Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations

Chat Fun
index.tsx
Download

Source

src/plugins/seeSummaries/index.tsx
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import * as DataStore from "@api/DataStore";
8import { definePluginSettings } from "@api/Settings";
9import { Devs } from "@utils/constants";
10import { hasGuildFeature } from "@utils/discord";
11import definePlugin, { OptionType } from "@utils/types";
12import { findByPropsLazy } from "@webpack";
13import { ChannelStore, GuildStore } from "@webpack/common";
14
15const SummaryStore = findByPropsLazy("allSummaries", "findSummary");
16
17const settings = definePluginSettings({
18 summaryExpiryThresholdDays: {
19 type: OptionType.SLIDER,
20 description: "The time in days before a summary is removed. Note that only up to 50 summaries are kept per channel",
21 markers: [1, 3, 5, 7, 10, 15, 20, 25, 30],
22 stickToMarkers: false,
23 default: 3,
24 }
25});
26
27interface Summary {
28 count: number;
29 end_id: string;
30 id: string;
31 message_ids: string[];
32 people: string[];
33 source: number;
34 start_id: string;
35 summ_short: string;
36 topic: string;
37 type: number;
38 unsafe: boolean;
39}
40
41interface ChannelSummary {
42 type: string;
43 channel_id: string;
44 guild_id: string;
45 summaries: Summary[];
46
47 // custom property
48 time?: number;
49}
50// TODO: these types are wrong and evil and incorrect
51function createChannelSummaryFromServer(s: Summary, channelId: string): ChannelSummary {
52 return {
53 id: s.id,
54 topic: s.topic,
55 summShort: s.summ_short,
56 people: Array.from(new Set(s.people)),
57 startId: s.start_id,
58 endId: s.end_id,
59 count: s.count,
60 channelId,
61 source: s.source,
62 type: s.type as any,
63 } as any as ChannelSummary;
64}
65
66export default definePlugin({
67 name: "Summaries",
68 description: "Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations",
69 tags: ["Chat", "Fun"],
70 authors: [Devs.mantikafasi],
71 settings,
72 patches: [
73 {
74 find: "SUMMARIZEABLE.has",
75 replacement: {
76 match: /\i\.features\.has\(\i\.\i\.SUMMARIES_ENABLED\w+?\)/g,
77 replace: "true"
78 }
79 },
80 {
81 find: "RECEIVE_CHANNEL_SUMMARY(",
82 replacement: {
83 match: /shouldFetch\((\i),\i\){/,
84 replace: "$& if(!$self.shouldFetch($1)) return false;"
85 }
86 }
87 ],
88
89 flux: {
90 CONVERSATION_SUMMARY_UPDATE(data) {
91 const incomingSummaries: ChannelSummary[] = data.summaries.map((summary: any) => ({
92 ...createChannelSummaryFromServer(summary, undefined!),
93 time: Date.now()
94 }));
95
96 // idk if this is good for performance but it doesnt seem to be a problem in my experience
97 DataStore.update("summaries-data", summaries => {
98 summaries ??= {};
99 summaries[data.channel_id] ? summaries[data.channel_id].unshift(...incomingSummaries) : (summaries[data.channel_id] = incomingSummaries);
100 if (summaries[data.channel_id].length > 50)
101 summaries[data.channel_id] = summaries[data.channel_id].slice(0, 50);
102
103 return summaries;
104 });
105 }
106 },
107
108 async start() {
109 await DataStore.update("summaries-data", summaries => {
110 summaries ??= {};
111 for (const key of Object.keys(summaries)) {
112 for (let i = summaries[key].length - 1; i >= 0; i--) {
113 if (summaries[key][i].time < Date.now() - 1000 * 60 * 60 * 24 * settings.store.summaryExpiryThresholdDays) {
114 summaries[key].splice(i, 1);
115 }
116 }
117
118 if (summaries[key].length === 0) {
119 delete summaries[key];
120 }
121 }
122
123 Object.assign(SummaryStore.allSummaries(), summaries);
124 return summaries;
125 });
126 },
127
128 shouldFetch(channelId: string) {
129 const channel = ChannelStore.getChannel(channelId);
130 // SUMMARIES_ENABLED feature is not in discord-types
131 const guild = GuildStore.getGuild(channel.guild_id);
132
133 return hasGuildFeature(guild, "SUMMARIES_ENABLED_GA");
134 }
135});
136