66import net .dv8tion .jda .api .entities .Role ;
77import net .dv8tion .jda .api .entities .UserSnowflake ;
88import net .dv8tion .jda .api .entities .channel .concrete .TextChannel ;
9+ import net .dv8tion .jda .api .entities .channel .middleman .MessageChannel ;
910import net .dv8tion .jda .api .events .interaction .component .ButtonInteractionEvent ;
1011import net .dv8tion .jda .api .events .interaction .component .StringSelectInteractionEvent ;
1112import net .dv8tion .jda .api .interactions .components .ComponentInteraction ;
4344import java .util .stream .Collectors ;
4445import java .util .stream .Stream ;
4546
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+ */
4759public final class TopHelpersAssignmentRoutine implements Routine , UserInteractor {
4860 private static final Logger logger = LoggerFactory .getLogger (TopHelpersAssignmentRoutine .class );
4961 private static final int CHECK_AT_HOUR = 12 ;
@@ -58,6 +70,12 @@ public final class TopHelpersAssignmentRoutine implements Routine, UserInteracto
5870 private final Predicate <String > announcementChannelNamePredicate ;
5971 private final ComponentIdInteractor componentIdInteractor ;
6072
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+ */
6179 public TopHelpersAssignmentRoutine (Config config , TopHelpersService service ) {
6280 this .config = config .getTopHelpers ();
6381 this .service = service ;
@@ -88,19 +106,30 @@ public void acceptComponentIdGenerator(ComponentIdGenerator generator) {
88106
89107 @ Override
90108 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.
91111 return Schedule .atFixedHour (CHECK_AT_HOUR );
92112 }
93113
94114 @ Override
95115 public void runRoutine (JDA jda ) {
96- int dayOfMonth = Instant .now ().atOffset (ZoneOffset .UTC ).getDayOfMonth ();
97- if (dayOfMonth != 1 ) {
116+ if (!isFirstDayOfMonth ()) {
98117 return ;
99118 }
100119
101120 jda .getGuilds ().forEach (this ::startDialogFor );
102121 }
103122
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+ */
104133 public void startDialogFor (Guild guild ) {
105134 Optional <TextChannel > assignmentChannel = guild .getTextChannelCache ()
106135 .stream ()
@@ -116,8 +145,8 @@ private void startDialogIn(TextChannel channel) {
116145 Guild guild = channel .getGuild ();
117146
118147 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 );
121150
122151 if (topHelpers .isEmpty ()) {
123152 channel .sendMessage (
@@ -127,24 +156,21 @@ private void startDialogIn(TextChannel channel) {
127156 return ;
128157 }
129158
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 ));
137163 }
138164
139- private void sendSelectionMenu (Collection <TopHelpersService .TopHelperResult > topHelpers ,
165+ private void sendSelectionMenu (Collection <TopHelpersService .TopHelperStats > topHelpers ,
140166 Collection <? extends Member > members , TopHelpersService .TimeRange timeRange ,
141- TextChannel channel ) {
167+ MessageChannel channel ) {
142168 String content = """
143169 Starting assignment of Top Helpers for %s:
144170 ```java
145171 %s
146172 ```""" .formatted (timeRange .description (),
147- service .asAsciiTable (topHelpers , members , false ));
173+ TopHelpersService .asAsciiTable (topHelpers , members ));
148174
149175 StringSelectMenu .Builder menu =
150176 StringSelectMenu .create (componentIdInteractor .generateComponentId ())
@@ -168,7 +194,7 @@ private void sendSelectionMenu(Collection<TopHelpersService.TopHelperResult> top
168194 channel .sendMessage (message ).queue ();
169195 }
170196
171- private static SelectOption topHelperToSelectOption (TopHelpersService .TopHelperResult topHelper ,
197+ private static SelectOption topHelperToSelectOption (TopHelpersService .TopHelperStats topHelper ,
172198 @ Nullable Member member ) {
173199 String id = Long .toString (topHelper .authorId ());
174200
@@ -179,19 +205,6 @@ private static SelectOption topHelperToSelectOption(TopHelpersService.TopHelperR
179205 return SelectOption .of (label , id );
180206 }
181207
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-
195208 @ Override
196209 public void onStringSelectSelection (StringSelectInteractionEvent event , List <String > args ) {
197210 Guild guild = Objects .requireNonNull (event .getGuild ());
@@ -214,17 +227,16 @@ public void onStringSelectSelection(StringSelectInteractionEvent event, List<Str
214227 }
215228
216229 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+ })
218238 .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 ()));
228240 }
229241
230242 private void manageTopHelperRole (Collection <? extends Member > currentTopHelpers ,
@@ -275,20 +287,30 @@ private void reportRoleManageSuccess(StringSelectInteractionEvent event) {
275287 .queue ();
276288 }
277289
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+
278303 private void prepareAnnouncement (ButtonInteractionEvent event , List <String > args ) {
279304 List <Long > topHelperIds = args .stream ().skip (1 ).map (Long ::parseLong ).toList ();
280305
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 ));
292314 }
293315
294316 private void postAnnouncement (ButtonInteractionEvent event , List <? extends Member > topHelpers ) {
@@ -310,7 +332,9 @@ private void postAnnouncement(ButtonInteractionEvent event, List<? extends Membe
310332 return ;
311333 }
312334
313- Collections .shuffle (topHelpers ); // for fairness
335+ // For fairness Top Helpers in the announcement are ordered randomly
336+ Collections .shuffle (topHelpers );
337+
314338 String topHelperList = topHelpers .stream ()
315339 .map (Member ::getAsMention )
316340 .map (mention -> "* " + mention )
@@ -321,12 +345,12 @@ private void postAnnouncement(ButtonInteractionEvent event, List<? extends Membe
321345
322346 announcementChannel .orElseThrow ().sendMessage (announcement ).queue ();
323347
324- endFlow (event , "posted an announcement" );
348+ reportFlowFinished (event , "posted an announcement" );
325349 }
326350
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 ) ;
330354 event .getHook ().editOriginal (content ).setComponents ().queue ();
331355 }
332356}
0 commit comments