6
6
import net .dv8tion .jda .api .entities .Role ;
7
7
import net .dv8tion .jda .api .entities .UserSnowflake ;
8
8
import net .dv8tion .jda .api .entities .channel .concrete .TextChannel ;
9
+ import net .dv8tion .jda .api .entities .channel .middleman .MessageChannel ;
9
10
import net .dv8tion .jda .api .events .interaction .component .ButtonInteractionEvent ;
10
11
import net .dv8tion .jda .api .events .interaction .component .StringSelectInteractionEvent ;
11
12
import net .dv8tion .jda .api .interactions .components .ComponentInteraction ;
43
44
import java .util .stream .Collectors ;
44
45
import java .util .stream .Stream ;
45
46
46
- // TODO Javadoc everywhere
47
+ /**
48
+ * Semi-automatic routine for assigning and announcing Top Helpers of the month.
49
+ * <p>
50
+ * The routine triggers once on the 1st of each month, but can also be started manually by calling
51
+ * {@link #startDialogFor(Guild)}.
52
+ * <p>
53
+ * The routine starts a dialog in a configured channel, proposing a list of Top Helpers. A user can
54
+ * now choose from a list who to reward with the Top Helper role. These users are now assigned the
55
+ * configured Top Helper role, while users who had the role in the past have it removed from them.
56
+ * Afterward, one can select from the dialog whether a generic announcement that thanks the selected
57
+ * Top Helpers should be posted in a configured channel.
58
+ */
47
59
public final class TopHelpersAssignmentRoutine implements Routine , UserInteractor {
48
60
private static final Logger logger = LoggerFactory .getLogger (TopHelpersAssignmentRoutine .class );
49
61
private static final int CHECK_AT_HOUR = 12 ;
@@ -58,6 +70,12 @@ public final class TopHelpersAssignmentRoutine implements Routine, UserInteracto
58
70
private final Predicate <String > announcementChannelNamePredicate ;
59
71
private final ComponentIdInteractor componentIdInteractor ;
60
72
73
+ /**
74
+ * Creates a new instance.
75
+ *
76
+ * @param config the config to use
77
+ * @param service the service to use to compute Top Helpers
78
+ */
61
79
public TopHelpersAssignmentRoutine (Config config , TopHelpersService service ) {
62
80
this .config = config .getTopHelpers ();
63
81
this .service = service ;
@@ -88,19 +106,30 @@ public void acceptComponentIdGenerator(ComponentIdGenerator generator) {
88
106
89
107
@ Override
90
108
public Schedule createSchedule () {
109
+ // Routine schedules are hour-based. Ensuring it only triggers once per a month is done
110
+ // inside the routine as early-check instead.
91
111
return Schedule .atFixedHour (CHECK_AT_HOUR );
92
112
}
93
113
94
114
@ Override
95
115
public void runRoutine (JDA jda ) {
96
- int dayOfMonth = Instant .now ().atOffset (ZoneOffset .UTC ).getDayOfMonth ();
97
- if (dayOfMonth != 1 ) {
116
+ if (!isFirstDayOfMonth ()) {
98
117
return ;
99
118
}
100
119
101
120
jda .getGuilds ().forEach (this ::startDialogFor );
102
121
}
103
122
123
+ private static boolean isFirstDayOfMonth () {
124
+ return Instant .now ().atOffset (ZoneOffset .UTC ).getDayOfMonth () == 1 ;
125
+ }
126
+
127
+ /**
128
+ * Starts a dialog for semi-automatic Top Helper assignment and announcement. See
129
+ * {@link TopHelpersAssignmentRoutine} for details.
130
+ *
131
+ * @param guild to start the dialog and compute Top Helpers for
132
+ */
104
133
public void startDialogFor (Guild guild ) {
105
134
Optional <TextChannel > assignmentChannel = guild .getTextChannelCache ()
106
135
.stream ()
@@ -116,8 +145,8 @@ private void startDialogIn(TextChannel channel) {
116
145
Guild guild = channel .getGuild ();
117
146
118
147
TopHelpersService .TimeRange timeRange = TopHelpersService .TimeRange .ofPreviousMonth ();
119
- List <TopHelpersService .TopHelperResult > topHelpers = service
120
- .computeTopHelpersDescending (guild . getIdLong () , timeRange . start (), timeRange . end () );
148
+ List <TopHelpersService .TopHelperStats > topHelpers =
149
+ service .computeTopHelpersDescending (guild , timeRange );
121
150
122
151
if (topHelpers .isEmpty ()) {
123
152
channel .sendMessage (
@@ -127,24 +156,21 @@ private void startDialogIn(TextChannel channel) {
127
156
return ;
128
157
}
129
158
130
- service .retrieveTopHelperMembers (topHelpers , guild )
131
- .onSuccess (members -> sendSelectionMenu (topHelpers , members , timeRange , channel ))
132
- .onError (error -> {
133
- logger .warn ("Failed to compute top-helpers for automatic assignment" , error );
134
- channel .sendMessage ("Wanted to assign Top Helpers, but something went wrong." )
135
- .queue ();
136
- });
159
+ TopHelpersService .retrieveTopHelperMembers (topHelpers , guild ).onError (error -> {
160
+ logger .warn ("Failed to compute top-helpers for automatic assignment" , error );
161
+ channel .sendMessage ("Wanted to assign Top Helpers, but something went wrong." ).queue ();
162
+ }).onSuccess (members -> sendSelectionMenu (topHelpers , members , timeRange , channel ));
137
163
}
138
164
139
- private void sendSelectionMenu (Collection <TopHelpersService .TopHelperResult > topHelpers ,
165
+ private void sendSelectionMenu (Collection <TopHelpersService .TopHelperStats > topHelpers ,
140
166
Collection <? extends Member > members , TopHelpersService .TimeRange timeRange ,
141
- TextChannel channel ) {
167
+ MessageChannel channel ) {
142
168
String content = """
143
169
Starting assignment of Top Helpers for %s:
144
170
```java
145
171
%s
146
172
```""" .formatted (timeRange .description (),
147
- service .asAsciiTable (topHelpers , members , false ));
173
+ TopHelpersService .asAsciiTable (topHelpers , members ));
148
174
149
175
StringSelectMenu .Builder menu =
150
176
StringSelectMenu .create (componentIdInteractor .generateComponentId ())
@@ -168,7 +194,7 @@ private void sendSelectionMenu(Collection<TopHelpersService.TopHelperResult> top
168
194
channel .sendMessage (message ).queue ();
169
195
}
170
196
171
- private static SelectOption topHelperToSelectOption (TopHelpersService .TopHelperResult topHelper ,
197
+ private static SelectOption topHelperToSelectOption (TopHelpersService .TopHelperStats topHelper ,
172
198
@ Nullable Member member ) {
173
199
String id = Long .toString (topHelper .authorId ());
174
200
@@ -179,19 +205,6 @@ private static SelectOption topHelperToSelectOption(TopHelpersService.TopHelperR
179
205
return SelectOption .of (label , id );
180
206
}
181
207
182
- @ Override
183
- public void onButtonClick (ButtonInteractionEvent event , List <String > args ) {
184
- event .deferEdit ().queue ();
185
- String name = args .getFirst ();
186
-
187
- switch (name ) {
188
- case CANCEL_BUTTON_NAME -> endFlow (event , "cancelled" );
189
- case NO_MESSAGE_BUTTON_NAME -> endFlow (event , "not posting an announcement" );
190
- case YES_MESSAGE_BUTTON_NAME -> prepareAnnouncement (event , args );
191
- default -> throw new AssertionError ("Unknown button name: " + name );
192
- }
193
- }
194
-
195
208
@ Override
196
209
public void onStringSelectSelection (StringSelectInteractionEvent event , List <String > args ) {
197
210
Guild guild = Objects .requireNonNull (event .getGuild ());
@@ -214,17 +227,16 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List<Str
214
227
}
215
228
216
229
event .deferEdit ().queue ();
217
- guild .findMembersWithRoles (List .of (topHelperRole .orElseThrow ()))
230
+ guild .findMembersWithRoles (List .of (topHelperRole .orElseThrow ())).onError (error -> {
231
+ logger .warn ("Failed to find existing top-helpers for automatic assignment" , error );
232
+ event .getHook ()
233
+ .editOriginal (event .getMessage ()
234
+ + "\n ❌ Sorry, something went wrong trying to find existing top-helpers." )
235
+ .setComponents ()
236
+ .queue ();
237
+ })
218
238
.onSuccess (currentTopHelpers -> manageTopHelperRole (currentTopHelpers ,
219
- selectedTopHelperIds , event , topHelperRole .orElseThrow ()))
220
- .onError (error -> {
221
- logger .warn ("Failed to find existing top-helpers for automatic assignment" , error );
222
- event .getHook ()
223
- .editOriginal (event .getMessage ()
224
- + "\n ❌ Sorry, something went wrong trying to find existing top-helpers." )
225
- .setComponents ()
226
- .queue ();
227
- });
239
+ selectedTopHelperIds , event , topHelperRole .orElseThrow ()));
228
240
}
229
241
230
242
private void manageTopHelperRole (Collection <? extends Member > currentTopHelpers ,
@@ -275,20 +287,30 @@ private void reportRoleManageSuccess(StringSelectInteractionEvent event) {
275
287
.queue ();
276
288
}
277
289
290
+ @ Override
291
+ public void onButtonClick (ButtonInteractionEvent event , List <String > args ) {
292
+ event .deferEdit ().queue ();
293
+ String name = args .getFirst ();
294
+
295
+ switch (name ) {
296
+ case YES_MESSAGE_BUTTON_NAME -> prepareAnnouncement (event , args );
297
+ case NO_MESSAGE_BUTTON_NAME -> reportFlowFinished (event , "not posting an announcement" );
298
+ case CANCEL_BUTTON_NAME -> reportFlowFinished (event , "cancelled" );
299
+ default -> throw new AssertionError ("Unknown button name: " + name );
300
+ }
301
+ }
302
+
278
303
private void prepareAnnouncement (ButtonInteractionEvent event , List <String > args ) {
279
304
List <Long > topHelperIds = args .stream ().skip (1 ).map (Long ::parseLong ).toList ();
280
305
281
- event .getGuild ()
282
- .retrieveMembersByIds (topHelperIds )
283
- .onSuccess (topHelpers -> postAnnouncement (event , topHelpers ))
284
- .onError (error -> {
285
- logger .warn ("Failed to retrieve top-helper data for automatic assignment" , error );
286
- event .getHook ()
287
- .editOriginal (event .getMessage ()
288
- + "\n ❌ Sorry, something went wrong trying to retrieve top-helper data." )
289
- .setComponents ()
290
- .queue ();
291
- });
306
+ event .getGuild ().retrieveMembersByIds (topHelperIds ).onError (error -> {
307
+ logger .warn ("Failed to retrieve top-helper data for automatic assignment" , error );
308
+ event .getHook ()
309
+ .editOriginal (event .getMessage ()
310
+ + "\n ❌ Sorry, something went wrong trying to retrieve top-helper data." )
311
+ .setComponents ()
312
+ .queue ();
313
+ }).onSuccess (topHelpers -> postAnnouncement (event , topHelpers ));
292
314
}
293
315
294
316
private void postAnnouncement (ButtonInteractionEvent event , List <? extends Member > topHelpers ) {
@@ -310,7 +332,9 @@ private void postAnnouncement(ButtonInteractionEvent event, List<? extends Membe
310
332
return ;
311
333
}
312
334
313
- Collections .shuffle (topHelpers ); // for fairness
335
+ // For fairness Top Helpers in the announcement are ordered randomly
336
+ Collections .shuffle (topHelpers );
337
+
314
338
String topHelperList = topHelpers .stream ()
315
339
.map (Member ::getAsMention )
316
340
.map (mention -> "* " + mention )
@@ -321,12 +345,12 @@ private void postAnnouncement(ButtonInteractionEvent event, List<? extends Membe
321
345
322
346
announcementChannel .orElseThrow ().sendMessage (announcement ).queue ();
323
347
324
- endFlow (event , "posted an announcement" );
348
+ reportFlowFinished (event , "posted an announcement" );
325
349
}
326
350
327
- private void endFlow (ComponentInteraction event , String message ) {
328
- String content = event . getMessage (). getContentRaw () + " \ n ✅ Okay, " + message
329
- + ". See you next time 👋" ;
351
+ private void reportFlowFinished (ComponentInteraction event , String phrase ) {
352
+ String content = "%s% n✅ Okay, %s. See you next time 👋"
353
+ . formatted ( event . getMessage (). getContentRaw (), phrase ) ;
330
354
event .getHook ().editOriginal (content ).setComponents ().queue ();
331
355
}
332
356
}
0 commit comments