1111import net .dv8tion .jda .api .interactions .commands .OptionType ;
1212import net .dv8tion .jda .api .interactions .commands .build .OptionData ;
1313import net .dv8tion .jda .api .interactions .commands .build .SubcommandData ;
14+ import org .apache .commons .lang3 .IntegerRange ;
1415import org .slf4j .Logger ;
1516import org .slf4j .LoggerFactory ;
1617
3738 * <li>{@code /xkcd custom <id>} - Posts a specific XKCD comic by ID from local cache.</li>
3839 * </ul>
3940 *
40- * Relies on {@link XkcdRetriever } for local XKCD data and {@link ChatGptService} for AI-powered
41+ * Relies on {@link XkcdService } for local XKCD data and {@link ChatGptService} for AI-powered
4142 * relevance matching via OpenAI's file search tool and vector stores.
4243 */
4344public final class XkcdCommand extends SlashCommandAdapter {
@@ -50,21 +51,22 @@ public final class XkcdCommand extends SlashCommandAdapter {
5051 private static final String LAST_MESSAGES_AMOUNT_OPTION_NAME = "amount" ;
5152 private static final String XKCD_ID_OPTION_NAME = "id" ;
5253 private static final int MAXIMUM_MESSAGE_HISTORY = 100 ;
54+ private static final int MESSAGE_HISTORY_CUTOFF_SIZE_KB = 40_000 ;
5355 private static final String VECTOR_STORE_XKCD = "xkcd-comics" ;
5456 private static final ChatGptModel CHAT_GPT_MODEL = ChatGptModel .FAST ;
5557 private static final Pattern XKCD_POST_PATTERN = Pattern .compile ("^\\ D*(\\ d+)" );
5658 private static final String CHATGPT_NO_ID_MESSAGE =
5759 "ChatGPT could not respond with a XKCD post ID." ;
5860
5961 private final ChatGptService chatGptService ;
60- private final XkcdRetriever xkcdRetriever ;
62+ private final XkcdService xkcdService ;
6163
6264 public XkcdCommand (ChatGptService chatGptService ) {
6365 super (COMMAND_NAME , "Post a relevant XKCD from the chat or your own" ,
6466 CommandVisibility .GLOBAL );
6567
6668 this .chatGptService = chatGptService ;
67- this .xkcdRetriever = new XkcdRetriever (chatGptService );
69+ this .xkcdService = new XkcdService (chatGptService );
6870
6971 OptionData lastMessagesAmountOption =
7072 new OptionData (OptionType .INTEGER , LAST_MESSAGES_AMOUNT_OPTION_NAME ,
@@ -81,7 +83,7 @@ public XkcdCommand(ChatGptService chatGptService) {
8183 "The XKCD number to post to the chat" )
8284 .setMinValue (0 )
8385 .setRequired (true )
84- .setMaxValue (xkcdRetriever .getXkcdPosts ().size ());
86+ .setMaxValue (xkcdService .getXkcdPosts ().size ());
8587
8688 SubcommandData customSubcommand = new SubcommandData (SUBCOMMAND_CUSTOM ,
8789 "Post your own XKCD regardless of the recent chat messages" )
@@ -149,17 +151,18 @@ private void handleCustomXkcd(SlashCommandInteractionEvent event) {
149151
150152 private void sendRelevantXkcdEmbedFromMessages (List <Message > messages ,
151153 SlashCommandInteractionEvent event ) {
152- String discordChat = formatDiscordChatHistory (messages );
153- String xkcdComicsFileId = xkcdRetriever .getXkcdUploadedFileId ();
154+ List <Message > discordChatCutoff = cutoffDiscordChatHistory (messages );
155+ String discordChatFormatted = formatDiscordChatHistory (discordChatCutoff );
156+ String xkcdComicsFileId = xkcdService .getXkcdUploadedFileId ();
154157 String xkcdVectorStore =
155158 chatGptService .createOrGetVectorStore (xkcdComicsFileId , VECTOR_STORE_XKCD );
156159 FileSearchTool fileSearch =
157160 FileSearchTool .builder ().vectorStoreIds (List .of (xkcdVectorStore )).build ();
158161
159162 Tool tool = Tool .ofFileSearch (fileSearch );
160163
161- Optional <String > responseOptional = chatGptService
162- . sendPrompt ( getChatgptRelevantPrompt (discordChat ), CHAT_GPT_MODEL , List .of (tool ));
164+ Optional <String > responseOptional = chatGptService . sendPrompt (
165+ getChatgptRelevantPrompt (discordChatFormatted ), CHAT_GPT_MODEL , List .of (tool ));
163166
164167 Optional <Integer > responseIdOptional = getXkcdIdFromMessage (responseOptional .orElseThrow ());
165168
@@ -174,11 +177,15 @@ private void sendRelevantXkcdEmbedFromMessages(List<Message> messages,
174177 Optional <MessageEmbed > embedOptional =
175178 constructEmbed (responseId , "Most relevant XKCD according to ChatGPT." );
176179
177- embedOptional .ifPresent (embed -> event .getHook ().sendMessageEmbeds (embed ).queue ());
180+ embedOptional .ifPresentOrElse (embed -> event .getHook ().sendMessageEmbeds (embed ).queue (),
181+ () -> event .getHook ()
182+ .setEphemeral (true )
183+ .sendMessage ("I could not find post with ID " + responseId )
184+ .queue ());
178185 }
179186
180187 private Optional <MessageEmbed > constructEmbed (int xkcdId , String footer ) {
181- Optional <XkcdPost > xkcdPostOptional = xkcdRetriever .getXkcdPost (xkcdId );
188+ Optional <XkcdPost > xkcdPostOptional = xkcdService .getXkcdPost (xkcdId );
182189
183190 if (xkcdPostOptional .isEmpty ()) {
184191 logger .warn ("Could not find XKCD post with ID {} from local map" , xkcdId );
@@ -220,6 +227,23 @@ private String formatDiscordChatHistory(List<Message> messages) {
220227 .toString ();
221228 }
222229
230+ private List <Message > cutoffDiscordChatHistory (List <Message > messages ) {
231+ int cutoffMessageIndex = (int ) IntegerRange .of (0 , messages .size () - 1 )
232+ .toIntStream ()
233+ .map (index -> countMessagesLength (messages .subList (0 , index )))
234+ .filter (length -> length < MESSAGE_HISTORY_CUTOFF_SIZE_KB )
235+ .count ();
236+
237+ return messages .subList (0 , cutoffMessageIndex );
238+ }
239+
240+ private int countMessagesLength (List <Message > messages ) {
241+ return messages .stream ()
242+ .mapToInt (message -> message .getContentRaw ().length ()
243+ + message .getAuthor ().getName ().length ())
244+ .sum ();
245+ }
246+
223247 private static String getChatgptRelevantPrompt (String discordChat ) {
224248 return """
225249 <discord-chat>
0 commit comments