Plugin
Summaries
Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import * as DataStore from "@api/DataStore";8
import { definePluginSettings } from "@api/Settings";9
import { Devs } from "@utils/constants";10
import { hasGuildFeature } from "@utils/discord";11
import definePlugin, { OptionType } from "@utils/types";12
import { findByPropsLazy } from "@webpack";13
import { ChannelStore, GuildStore } from "@webpack/common";14
15
const SummaryStore = findByPropsLazy("allSummaries", "findSummary");16
17
const 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
27
interface 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
41
interface ChannelSummary {42
type: string;43
channel_id: string;44
guild_id: string;45
summaries: Summary[];46
47
// custom property48
time?: number;49
}50
// TODO: these types are wrong and evil and incorrect51
function 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
66
export default definePlugin({67
name: "Summaries",68
description: "Enables Discord039;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 experience97
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-types131
const guild = GuildStore.getGuild(channel.guild_id);132
133
return hasGuildFeature(guild, "SUMMARIES_ENABLED_GA");134
}135
});136