44import net .dv8tion .jda .api .entities .Member ;
55import net .dv8tion .jda .api .entities .Role ;
66import net .dv8tion .jda .api .entities .User ;
7- import net .dv8tion .jda .api .entities .UserSnowflake ;
8- import net .dv8tion .jda .api .requests .RestAction ;
9- import net .dv8tion .jda .api .utils .Result ;
7+ import org .jetbrains .annotations .NotNull ;
108import org .jooq .Query ;
119import org .jooq .impl .DSL ;
1210import org .slf4j .Logger ;
2321import java .util .List ;
2422import java .util .Optional ;
2523import java .util .Set ;
26- import java .util .function .Predicate ;
27- import java .util .regex .Pattern ;
28- import java .util .stream .Collectors ;
2924
3025import static org .togetherjava .tjbot .db .generated .tables .CakeDays .CAKE_DAYS ;
3126
@@ -37,29 +32,29 @@ public class CakeDayService {
3732 private static final DateTimeFormatter MONTH_DAY_FORMATTER =
3833 DateTimeFormatter .ofPattern ("MM-dd" );
3934 private final Set <String > cakeDaysCache = new HashSet <>();
40- private final Predicate < String > cakeDayRolePredicate ;
41- private final CakeDayConfig config ;
35+ private final String cakeDayRolePattern ;
36+ private final CakeDayConfig fullConfig ;
4237 private final Database database ;
4338
4439 /**
4540 * Constructs a {@link CakeDayService} with the given configuration and database.
4641 *
47- * @param config the configuration for cake day management
42+ * @param fullConfig the full configuration for cake day management
4843 * @param database the database for storing cake day information
4944 */
50- public CakeDayService (Config config , Database database ) {
51- this .config = config .getCakeDayConfig ();
45+ public CakeDayService (Config fullConfig , Database database ) {
46+ this .fullConfig = fullConfig .getCakeDayConfig ();
5247 this .database = database ;
5348
54- this .cakeDayRolePredicate = Pattern . compile ( this .config .rolePattern ()). asPredicate ();
49+ this .cakeDayRolePattern = this .fullConfig .rolePattern ();
5550 }
5651
5752 private Optional <Role > getCakeDayRole (Guild guild ) {
5853 Optional <Role > cakeDayRole = getCakeDayRoleFromGuild (guild );
5954
6055 if (cakeDayRole .isEmpty ()) {
6156 logger .warn ("Cake day role with pattern {} not found for guild: {}" ,
62- config .rolePattern (), guild .getName ());
57+ fullConfig .rolePattern (), guild .getName ());
6358 }
6459
6560 return cakeDayRole ;
@@ -74,13 +69,7 @@ private Optional<Role> getCakeDayRole(Guild guild) {
7469 * @param guild the guild for which to reassign the cake day role
7570 */
7671 protected void reassignCakeDayRole (Guild guild ) {
77- Optional <Role > cakeDayRole = getCakeDayRole (guild );
78-
79- if (cakeDayRole .isEmpty ()) {
80- return ;
81- }
82-
83- refreshMembersCakeDayRoles (cakeDayRole .get (), guild );
72+ getCakeDayRole (guild ).ifPresent (role -> refreshMembersCakeDayRoles (role , guild ));
8473 }
8574
8675 /**
@@ -92,7 +81,7 @@ protected void reassignCakeDayRole(Guild guild) {
9281 private void refreshMembersCakeDayRoles (Role cakeDayRole , Guild guild ) {
9382 guild .findMembersWithRoles (cakeDayRole ).onSuccess (members -> {
9483 removeRoleFromMembers (guild , cakeDayRole , members );
95- addTodayMembersCakeDayRole (guild );
84+ addTodayMembersCakeDayRole (guild , cakeDayRole );
9685 });
9786 }
9887
@@ -106,99 +95,96 @@ private void refreshMembersCakeDayRoles(Role cakeDayRole, Guild guild) {
10695 *
10796 * @param guild the guild to check for members celebrating their cake day today
10897 */
109- private void addTodayMembersCakeDayRole (Guild guild ) {
98+ private void addTodayMembersCakeDayRole (Guild guild , Role cakeDayRole ) {
11099 findCakeDaysTodayFromDatabase (guild ).forEach (cakeDayRecord -> {
111100 Member member = guild .getMemberById (cakeDayRecord .getUserId ());
112101
113102 if (member == null ) {
114103 return ;
115104 }
116105
117- boolean isAnniversaryDay = hasMemberCakeDayToday (member );
118- int yearsSinceJoin = OffsetDateTime .now ().getYear () - cakeDayRecord .getJoinedYear ();
119-
120- if (yearsSinceJoin > 0 && isAnniversaryDay ) {
121- addCakeDayRole (member );
106+ if (!hasMemberCakeDayToday (member )) {
107+ return ;
122108 }
109+
110+ addCakeDayRole (member , cakeDayRole );
123111 });
124112 }
125113
126114 /**
127- * Adds the cake day role to the specified member if the cake day role exists in the guild.
115+ * Adds the cake day role supplied to the specified member if the cake day role exists in the
116+ * guild.
128117 *
129118 * @param member the {@link Member} to whom the cake day role will be added
130119 */
131120 protected void addCakeDayRole (Member member ) {
132121 Guild guild = member .getGuild ();
133- UserSnowflake snowflake = UserSnowflake .fromId (member .getId ());
134122 Optional <Role > cakeDayRole = getCakeDayRole (guild );
135123
136124 if (cakeDayRole .isEmpty ()) {
137125 return ;
138126 }
139127
140- guild . addRoleToMember ( snowflake , cakeDayRole .get ()). complete ( );
128+ addCakeDayRole ( member , cakeDayRole .get ());
141129 }
142130
143131 /**
144- * Removes a specified role from a list of members in a {@link Guild} .
132+ * Adds the cake day role supplied to the specified member .
145133 *
146- * @param guild the {@link Guild} from which to remove the role from members
147- * @param role the {@link Role} to be removed from the members
148- * @param members the {@link List} of members from which the {@link Role} will be removed
134+ * @param member the {@link Member} to whom the cake day role will be added
135+ * @param cakeDayRole the cake day {@link Role}
149136 */
150- private void removeRoleFromMembers (Guild guild , Role role , List <Member > members ) {
151- List <RestAction <Result <Void >>> chain = members .stream ()
152- .map (member -> guild .removeRoleFromMember (member , role ).mapToResult ())
153- .toList ();
154-
155- if (chain .isEmpty ()) {
156- return ;
157- }
137+ private void addCakeDayRole (Member member , @ NotNull Role cakeDayRole ) {
138+ Guild guild = member .getGuild ();
158139
159- RestAction . allOf ( chain ).queue ();
140+ guild . addRoleToMember ( member , cakeDayRole ).queue ();
160141 }
161142
162143 /**
163- * Creates a query to insert a member's cake day information into the database .
144+ * Removes a specified role from a list of members in a {@link Guild} .
164145 *
165- * @param member the member whose cake day information is to be inserted
166- * @param guildId the ID of the guild to which the member belongs
167- * @return an Optional containing the query to insert cake day information if the member has a
168- * join time; empty Optional otherwise
146+ * @param guild the {@link Guild} from which to remove the role from members
147+ * @param role the {@link Role} to be removed from the members
148+ * @param members the {@link List} of members from which the {@link Role} will be removed
169149 */
170- private Optional <Query > createMemberCakeDayQuery (Member member , long guildId ) {
171- if (!member .hasTimeJoined ()) {
172- return Optional .empty ();
173- }
174-
175- OffsetDateTime cakeDay = member .getTimeJoined ();
176- String joinedMonthDay = cakeDay .format (MONTH_DAY_FORMATTER );
177-
178- return Optional .of (DSL .insertInto (CAKE_DAYS )
179- .set (CAKE_DAYS .JOINED_MONTH_DAY , joinedMonthDay )
180- .set (CAKE_DAYS .JOINED_YEAR , cakeDay .getYear ())
181- .set (CAKE_DAYS .GUILD_ID , guildId )
182- .set (CAKE_DAYS .USER_ID , member .getIdLong ()));
150+ private void removeRoleFromMembers (Guild guild , Role role , List <Member > members ) {
151+ members .forEach (member -> guild .removeRoleFromMember (member , role )
152+ .queue (null ,
153+ failure -> logger .error ("Could not remove role {} from {} ({}): {}" ,
154+ role .getName (), member .getEffectiveName (), member .getId (),
155+ failure .getMessage ())));
183156 }
184157
185158 /**
186- * Inserts the cake day of a member into the database.
159+ * Asynchronously inserts a member's cake day information into the database.
160+ *
161+ * <p>
162+ * This method retrieves the {@link Member} from the Discord API to obtain the member's join
163+ * timestamp, derives the month/day portion using {@code MONTH_DAY_FORMATTER}, and then inserts
164+ * a row into {@code CAKE_DAYS} with the join month-day, join year, guild id, and user id.
165+ *
187166 * <p>
188- * If the member has no join date, nothing happens.
167+ * The Discord retrieval is performed asynchronously; on success the database write is executed.
168+ * If the Discord retrieval fails (e.g., member not found, missing permissions, network issues),
169+ * the insert is skipped and a warning is logged.
189170 *
190- * @param member the member whose cake day is to be inserted into the database
191- * @param guildId the ID of the guild to which the member belongs
171+ * @param member the member whose cake day information should be inserted
172+ * @param guildId the id of the guild associated with the cake day record
192173 */
193174 protected void insertMemberCakeDayToDatabase (Member member , long guildId ) {
194- Query insertQuery = createMemberCakeDayQuery (member , guildId ).orElse (null );
195-
196- if (insertQuery == null ) {
197- logger .warn ("Tried to add member {} to database but found no time joined" ,
198- member .getId ());
199- }
200-
201- database .write (context -> context .batch (insertQuery ).execute ());
175+ member .getGuild ().retrieveMemberById (member .getIdLong ()).queue (retrievedMember -> {
176+ OffsetDateTime timeJoined = retrievedMember .getTimeJoined ();
177+ String joinedMonthDay = timeJoined .format (MONTH_DAY_FORMATTER );
178+
179+ Query query = DSL .insertInto (CAKE_DAYS )
180+ .set (CAKE_DAYS .JOINED_MONTH_DAY , joinedMonthDay )
181+ .set (CAKE_DAYS .JOINED_YEAR , timeJoined .getYear ())
182+ .set (CAKE_DAYS .GUILD_ID , guildId )
183+ .set (CAKE_DAYS .USER_ID , member .getIdLong ());
184+
185+ database .write (ctx -> ctx .execute (query ));
186+ }, failure -> logger .warn ("Could not insert member cake day into the database: {}" ,
187+ failure .getMessage ()));
202188 }
203189
204190 /**
@@ -221,10 +207,7 @@ protected void removeMemberCakeDayFromDatabase(long userId, long guildId) {
221207 * @return an {@link Optional} containing the cake day role if found, otherwise empty
222208 */
223209 private Optional <Role > getCakeDayRoleFromGuild (Guild guild ) {
224- return guild .getRoles ()
225- .stream ()
226- .filter (role -> cakeDayRolePredicate .test (role .getName ()))
227- .findFirst ();
210+ return guild .getRolesByName (cakeDayRolePattern , true ).stream ().findFirst ();
228211 }
229212
230213 /**
@@ -234,7 +217,7 @@ private Optional<Role> getCakeDayRoleFromGuild(Guild guild) {
234217 * @param user the {@link User} who left the guild
235218 * @param guild the {@link Guild} from which the user left
236219 */
237- protected void handleUserLeft (User user , Guild guild ) {
220+ protected void removeUserCakeDay (User user , Guild guild ) {
238221 removeMemberCakeDayFromDatabase (user .getIdLong (), guild .getIdLong ());
239222 cakeDaysCache .remove (guild .getId ());
240223 }
@@ -247,12 +230,10 @@ protected void handleUserLeft(User user, Guild guild) {
247230 private List <CakeDaysRecord > findCakeDaysTodayFromDatabase (Guild guild ) {
248231 String todayMonthDay = OffsetDateTime .now ().format (MONTH_DAY_FORMATTER );
249232
250- return database
251- .read (context -> context .selectFrom (CAKE_DAYS )
252- .where (CAKE_DAYS .JOINED_MONTH_DAY .eq (todayMonthDay ))
253- .and (CAKE_DAYS .GUILD_ID .eq (guild .getIdLong ()))
254- .fetch ())
255- .collect (Collectors .toList ());
233+ return database .read (context -> context .selectFrom (CAKE_DAYS )
234+ .where (CAKE_DAYS .JOINED_MONTH_DAY .eq (todayMonthDay ))
235+ .and (CAKE_DAYS .GUILD_ID .eq (guild .getIdLong ()))
236+ .fetch ());
256237 }
257238
258239 /**
@@ -263,13 +244,8 @@ private List<CakeDaysRecord> findCakeDaysTodayFromDatabase(Guild guild) {
263244 * {@link Optional} if no record is found
264245 */
265246 protected Optional <CakeDaysRecord > findUserCakeDayFromDatabase (long userId ) {
266- return database
267- .read (context -> context .selectFrom (CAKE_DAYS )
268- .where (CAKE_DAYS .USER_ID .eq (userId ))
269- .fetch ())
270- .collect (Collectors .toList ())
271- .stream ()
272- .findFirst ();
247+ return database .read (ctx -> Optional
248+ .ofNullable (ctx .selectFrom (CAKE_DAYS ).where (CAKE_DAYS .USER_ID .eq (userId )).fetchOne ()));
273249 }
274250
275251 /**
0 commit comments