55import net .dv8tion .jda .api .interactions .commands .OptionType ;
66import net .dv8tion .jda .api .interactions .commands .build .OptionData ;
77import org .jooq .DSLContext ;
8+ import org .jooq .Field ;
89import org .jooq .OrderField ;
910import org .jooq .Record1 ;
1011
3839public class HelpThreadStatsCommand extends SlashCommandAdapter {
3940 public static final String COMMAND_NAME = "help-thread-stats" ;
4041 public static final String DURATION_OPTION = "duration-option" ;
42+ private static final String TOTAL_CREATED_FIELD = "total_created" ;
43+ private static final String OPEN_NOW_ALIAS = "open_now" ;
44+ private static final String GHOST_NOW_ALIAS = "ghost_count" ;
45+ private static final String AVERAGE_PARTICIPANTS_ALIAS = "avg_parts" ;
46+ private static final String AVERAGE_MESSAGE_COUNT_ALIAS = "avg_msgs" ;
47+ private static final String AVERAGE_THREAD_DURATION_IN_SECONDS_ALIAS = "avg_sec" ;
48+ private static final String MINIMUM_THREAD_DURATION_IN_SECONDS_ALIAS = "min_sec" ;
49+ private static final String MAXIMUM_THREAD_DURATION_IN_SECONDS_ALIAS = "max_sec" ;
4150
4251 private final Database database ;
4352
@@ -72,36 +81,33 @@ public void onSlashCommand(SlashCommandInteractionEvent event) {
7281
7382 database .read (context -> {
7483 var statsRecord = context
75- .select (count ().as ("total_created" ), count ()
84+ .select (count ().as (TOTAL_CREATED_FIELD ), count ()
7685 .filterWhere (
7786 HELP_THREADS .TICKET_STATUS .eq (HelpSystemHelper .TicketStatus .ACTIVE .val ))
78- .as ("open_now" ),
79- count ().filterWhere (HELP_THREADS .PARTICIPANTS .eq (1 )).as ("ghost_count" ),
80- avg (HELP_THREADS .PARTICIPANTS ).as ("avg_parts" ),
81- avg (HELP_THREADS .MESSAGE_COUNT ).as ("avg_msgs" ),
82- avg (field ("unixepoch({0}) - unixepoch({1})" , Double .class ,
83- HELP_THREADS .CLOSED_AT , HELP_THREADS .CREATED_AT ))
84- .as ("avg_sec" ),
85- min (field ("unixepoch({0}) - unixepoch({1})" , Double .class ,
86- HELP_THREADS .CLOSED_AT , HELP_THREADS .CREATED_AT ))
87- .as ("min_sec" ),
88- max (field ("unixepoch({0}) - unixepoch({1})" , Double .class ,
89- HELP_THREADS .CLOSED_AT , HELP_THREADS .CREATED_AT ))
90- .as ("max_sec" ))
87+ .as (OPEN_NOW_ALIAS ),
88+ count ().filterWhere (HELP_THREADS .PARTICIPANTS .eq (1 )).as (GHOST_NOW_ALIAS ),
89+ avg (HELP_THREADS .PARTICIPANTS ).as (AVERAGE_PARTICIPANTS_ALIAS ),
90+ avg (HELP_THREADS .MESSAGE_COUNT ).as (AVERAGE_MESSAGE_COUNT_ALIAS ),
91+ avg (durationInSeconds (HELP_THREADS .CLOSED_AT , HELP_THREADS .CREATED_AT ))
92+ .as (AVERAGE_THREAD_DURATION_IN_SECONDS_ALIAS ),
93+ min (durationInSeconds (HELP_THREADS .CLOSED_AT , HELP_THREADS .CREATED_AT ))
94+ .as (MINIMUM_THREAD_DURATION_IN_SECONDS_ALIAS ),
95+ max (durationInSeconds (HELP_THREADS .CLOSED_AT , HELP_THREADS .CREATED_AT ))
96+ .as (MAXIMUM_THREAD_DURATION_IN_SECONDS_ALIAS ))
9197 .from (HELP_THREADS )
9298 .where (HELP_THREADS .CREATED_AT .ge (startDate ))
9399 .fetchOne ();
94100
95- if (statsRecord == null || statsRecord .get ("total_created" , Integer .class ) == 0 ) {
101+ if (statsRecord == null || statsRecord .get (TOTAL_CREATED_FIELD , Integer .class ) == 0 ) {
96102 event .getHook ()
97103 .editOriginal ("No stats available for the last " + days + " days." )
98104 .queue ();
99105 return null ;
100106 }
101107
102- int totalCreated = statsRecord .get ("total_created" , Integer .class );
103- int openThreads = statsRecord .get ("open_now" , Integer .class );
104- long ghostThreads = statsRecord .get ("ghost_count" , Number .class ).longValue ();
108+ int totalCreated = statsRecord .get (TOTAL_CREATED_FIELD , Integer .class );
109+ int openThreads = statsRecord .get (OPEN_NOW_ALIAS , Integer .class );
110+ long ghostThreads = statsRecord .get (GHOST_NOW_ALIAS , Number .class ).longValue ();
105111
106112 double rawResRate =
107113 totalCreated > 0 ? ((double ) (totalCreated - ghostThreads ) / totalCreated ) * 100
@@ -124,27 +130,32 @@ public void onSlashCommand(SlashCommandInteractionEvent event) {
124130 Objects .requireNonNull (event .getGuild ()).getIconUrl ());
125131
126132 embed .addField ("📝 THREAD ACTIVITY" ,
127- "Created: `%d`\ n Currently Open: `%d`\ n Response Rate: %.1f%%\ n Peak Hours: `%s`"
133+ "Created: `%d`% nCurrently Open: `%d`% nResponse Rate: %.1f%%% nPeak Hours: `%s`"
128134 .formatted (totalCreated , openThreads , rawResRate , peakHourRange ),
129135 false );
130136
131137 embed .addField ("💬 ENGAGEMENT" ,
132- "Avg Messages: `%s`\n Avg Helpers: `%s`\n Unanswered (Ghost): `%d`" .formatted (
133- formatDouble (Objects .requireNonNull (statsRecord .get ("avg_msgs" ))),
134- formatDouble (Objects .requireNonNull (statsRecord .get ("avg_parts" ))),
138+ "Avg Messages: `%s`%nAvg Helpers: `%s`%nUnanswered (Ghost): `%d`" .formatted (
139+ formatDouble (Objects
140+ .requireNonNull (statsRecord .get (AVERAGE_MESSAGE_COUNT_ALIAS ))),
141+ formatDouble (Objects
142+ .requireNonNull (statsRecord .get (AVERAGE_PARTICIPANTS_ALIAS ))),
135143 ghostThreads ),
136144 false );
137145
138146 embed .addField ("🏷️ TAG ACTIVITY" ,
139- "Most Used: `%s`\ n Most Active: `%s`\ n Needs Love: `%s`" .formatted (highVolumeTag ,
147+ "Most Used: `%s`% nMost Active: `%s`% nNeeds Love: `%s`" .formatted (highVolumeTag ,
140148 highActivityTag , lowActivityTag ),
141149 false );
142150
143151 embed .addField ("⚡ RESOLUTION SPEED" ,
144- "Average: `%s`\n Fastest: `%s`\n Slowest: `%s`" .formatted (
145- smartFormat (statsRecord .get ("avg_sec" , Double .class )),
146- smartFormat (statsRecord .get ("min_sec" , Double .class )),
147- smartFormat (statsRecord .get ("max_sec" , Double .class ))),
152+ "Average: `%s`%nFastest: `%s`%nSlowest: `%s`" .formatted (
153+ smartFormat (statsRecord .get (AVERAGE_THREAD_DURATION_IN_SECONDS_ALIAS ,
154+ Double .class )),
155+ smartFormat (statsRecord .get (MINIMUM_THREAD_DURATION_IN_SECONDS_ALIAS ,
156+ Double .class )),
157+ smartFormat (statsRecord .get (MAXIMUM_THREAD_DURATION_IN_SECONDS_ALIAS ,
158+ Double .class ))),
148159 false );
149160
150161 event .getHook ().editOriginalEmbeds (embed .build ()).queue ();
@@ -208,4 +219,12 @@ private String smartFormat(Double seconds) {
208219 private String formatDouble (Object val ) {
209220 return val instanceof Number num ? "%.2f" .formatted (num .doubleValue ()) : "0.00" ;
210221 }
222+
223+ /**
224+ * Calculates the duration in seconds between two timestamp fields. Uses SQLite unixepoch for
225+ * conversion.
226+ */
227+ private Field <Double > durationInSeconds (Field <Instant > end , Field <Instant > start ) {
228+ return field ("unixepoch({0}) - unixepoch({1})" , Double .class , end , start );
229+ }
211230}
0 commit comments