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