From fd1602bc4f8a82c6c69e45feadb5ed5c141b94ea Mon Sep 17 00:00:00 2001 From: Nova Fox Date: Sat, 2 Oct 2021 23:01:01 -0500 Subject: [PATCH] Announcement runner rewrite (#114) Full rewrite of the announcement runner in kotlin and remove several now-unused deprecated methods --- .editorconfig | 13 - build.gradle.kts | 2 +- .../message/AnnouncementMessageFormatter.java | 288 +----------------- .../announcement/AnnouncementThread.java | 255 ---------------- .../module/announcement/package-info.java | 1 - .../module/command/AnnouncementCommand.java | 46 +-- .../discal/client/DisCalClient.kt | 13 +- .../client/message/embed/AnnouncementEmbed.kt | 21 +- .../client/service/AnnouncementService.kt | 234 ++++++++++++++ .../discal/core/utils/PermissionChecker.java | 102 ------- .../discal/core/database/DatabaseManager.kt | 26 +- .../core/object/announcement/Announcement.kt | 11 +- .../object/announcement/AnnouncementCache.kt | 13 + .../core/wrapper/google/EventWrapper.kt | 20 +- .../db/migration/V19__Remove_Info_Only.sql | 2 + .../CreateAnnouncementEndpoint.kt | 1 - .../UpdateAnnouncementEndpoint.kt | 1 - 17 files changed, 288 insertions(+), 761 deletions(-) delete mode 100644 .editorconfig delete mode 100644 client/src/main/java/org/dreamexposure/discal/client/module/announcement/AnnouncementThread.java delete mode 100644 client/src/main/java/org/dreamexposure/discal/client/module/announcement/package-info.java create mode 100644 client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt create mode 100644 core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt create mode 100644 core/src/main/resources/db/migration/V19__Remove_Info_Only.sql diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index ec008cdb..00000000 --- a/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -root = true - -[*] -indent_style = space -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false - -# Credit to Discord4J for the .editorconfig file <3 \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 5c8a1614..22b37dd4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,7 +26,7 @@ val discord4jVersion = "3.2.0" //Has to be here to show up in git properties tas allprojects { //Project props group = "org.dreamexposure.discal" - version = "4.1.2" + version = "4.1.3-SNAPSHOT" description = "DisCal" //Plugins diff --git a/client/src/main/java/org/dreamexposure/discal/client/message/AnnouncementMessageFormatter.java b/client/src/main/java/org/dreamexposure/discal/client/message/AnnouncementMessageFormatter.java index acfa7143..f563a278 100644 --- a/client/src/main/java/org/dreamexposure/discal/client/message/AnnouncementMessageFormatter.java +++ b/client/src/main/java/org/dreamexposure/discal/client/message/AnnouncementMessageFormatter.java @@ -2,28 +2,23 @@ package org.dreamexposure.discal.client.message; import com.google.api.services.calendar.model.Event; import discord4j.common.util.Snowflake; -import discord4j.core.object.entity.*; +import discord4j.core.object.entity.Guild; +import discord4j.core.object.entity.Member; +import discord4j.core.object.entity.Role; import discord4j.core.object.entity.channel.GuildChannel; -import discord4j.core.object.entity.channel.GuildMessageChannel; import discord4j.core.spec.EmbedCreateSpec; -import discord4j.rest.http.client.ClientException; import discord4j.rest.util.Image; -import io.netty.handler.codec.http.HttpResponseStatus; import org.dreamexposure.discal.client.DisCalClient; import org.dreamexposure.discal.core.database.DatabaseManager; -import org.dreamexposure.discal.core.enums.announcement.AnnouncementStyle; import org.dreamexposure.discal.core.enums.announcement.AnnouncementType; -import org.dreamexposure.discal.core.enums.event.EventColor; import org.dreamexposure.discal.core.object.BotSettings; import org.dreamexposure.discal.core.object.GuildSettings; import org.dreamexposure.discal.core.object.announcement.Announcement; -import org.dreamexposure.discal.core.object.calendar.CalendarData; import org.dreamexposure.discal.core.object.event.EventData; import org.dreamexposure.discal.core.utils.GlobalVal; import org.dreamexposure.discal.core.utils.ImageUtils; import org.dreamexposure.discal.core.utils.RoleUtils; import org.dreamexposure.discal.core.utils.UserUtils; -import org.dreamexposure.discal.core.wrapper.google.CalendarWrapper; import org.dreamexposure.discal.core.wrapper.google.EventWrapper; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -68,7 +63,7 @@ public class AnnouncementMessageFormatter { embed.author("DisCal", BotSettings.BASE_URL.get(), GlobalVal.getIconUrl()); embed.title(Messages.getMessage("Embed.Announcement.Info.Title", settings)); - embed.addField(Messages.getMessage("Embed.Announcement.Info.ID", settings), a.getAnnouncementId().toString(), true); + embed.addField(Messages.getMessage("Embed.Announcement.Info.ID", settings), a.getId().toString(), true); embed.addField(Messages.getMessage("Embed.Announcement.Info.Type", settings), a.getType().name(), true); @@ -135,7 +130,7 @@ public class AnnouncementMessageFormatter { embed.author("DisCal", BotSettings.BASE_URL.get(), GlobalVal.getIconUrl()); embed.title(Messages.getMessage("Embed.Announcement.Condensed.Title", settings)); - embed.addField(Messages.getMessage("Embed.Announcement.Condensed.ID", settings), a.getAnnouncementId().toString(), false); + embed.addField(Messages.getMessage("Embed.Announcement.Condensed.ID", settings), a.getId().toString(), false); embed.addField(Messages.getMessage("Embed.Announcement.Condensed.Time", settings), condensedTime(a), false); if (a.getType().equals(AnnouncementType.SPECIFIC)) { @@ -172,238 +167,6 @@ public class AnnouncementMessageFormatter { })); } - public static Mono getCondensedAnnouncementEmbed(Announcement a, int calNum, - GuildSettings settings) { - Mono guild = DisCalClient.getClient().getGuildById(settings.getGuildID()); - - Mono event = Mono.just(a) - .map(Announcement::getType) - .filter(t -> t.equals(AnnouncementType.SPECIFIC)) - .flatMap(t -> DatabaseManager.INSTANCE.getCalendar(a.getGuildId(), calNum)) - .flatMap(cd -> EventWrapper.INSTANCE.getEvent(cd, a.getEventId())) - .defaultIfEmpty(new Event()); - - Mono eData = Mono.just(a) - .map(Announcement::getType) - .filter(t -> t.equals(AnnouncementType.SPECIFIC) || t.equals(AnnouncementType.RECUR)) - .flatMap(t -> DatabaseManager.INSTANCE.getEventData(a.getGuildId(), a.getEventId())) - .defaultIfEmpty(new EventData()).cache(); - - Mono img = eData.filter(EventData::shouldBeSaved) - .flatMap(ed -> ImageUtils.validate(ed.getImageLink(), settings.getPatronGuild())) - .defaultIfEmpty(false); - - - return Mono.zip(guild, event, eData, img) - .map(TupleUtils.function((g, e, ed, hasImg) -> { - var embed = EmbedCreateSpec.builder(); - - if (settings.getBranded()) - embed.author(g.getName(), BotSettings.BASE_URL.get(), - g.getIconUrl(Image.Format.PNG).orElse(GlobalVal.getIconUrl())); - else - embed.author("DisCal", BotSettings.BASE_URL.get(), GlobalVal.getIconUrl()); - - embed.title(Messages.getMessage("Embed.Announcement.Condensed.Title", settings)); - embed.addField(Messages.getMessage("Embed.Announcement.Condensed.ID", settings), a.getAnnouncementId().toString(), false); - embed.addField(Messages.getMessage("Embed.Announcement.Condensed.Time", settings), condensedTime(a), false); - - if (a.getType().equals(AnnouncementType.SPECIFIC)) { - embed.addField(Messages.getMessage("Embed.Announcement.Condensed.EventID", settings), a.getEventId(), false); - - if (hasImg) - embed.thumbnail(ed.getImageLink()); - - if (e.getSummary() != null) { - String summary = e.getSummary(); - if (summary.length() > 250) { - summary = summary.substring(0, 250); - summary = summary + " (continues on Google Calendar View)"; - } - embed.addField(Messages.getMessage("Embed.Announcement.Condensed.Summary", settings), summary, true); - } - } else if (a.getType().equals(AnnouncementType.COLOR)) { - embed.addField(Messages.getMessage("Embed.Announcement.Condensed.Color", settings), a.getEventColor().name(), true); - } else if (a.getType().equals(AnnouncementType.RECUR)) { - embed.addField(Messages.getMessage("Embed.Announcement.Condensed.RecurID", settings), a.getEventId(), true); - } - embed.footer(Messages.getMessage("Embed.Announcement.Condensed.Type", "%type%", a.getType().name(), - settings), null); - - if (a.getType().equals(AnnouncementType.COLOR)) { - embed.color(a.getEventColor().asColor()); - } else { - embed.color(GlobalVal.getDiscalColor()); - } - - embed.addField(Messages.getMessage("Embed.Announcement.Info.Enabled", settings), a.getEnabled() + "", true); - - return embed.build(); - })); - } - - private static Mono getRealAnnouncementEmbed(Announcement a, Event event, CalendarData cd, - GuildSettings settings) { - Mono guild = DisCalClient.getClient().getGuildById(settings.getGuildID()); - - Mono startDate = EventMessageFormatter - .getHumanReadableDate(event.getStart(), cd.getCalendarNumber(), false, settings); - - Mono startTime = EventMessageFormatter - .getHumanReadableTime(event.getStart(), cd.getCalendarNumber(), false, settings); - - Mono timezone = CalendarWrapper.INSTANCE.getCalendar(cd) - .map(com.google.api.services.calendar.model.Calendar::getTimeZone) - .defaultIfEmpty("TZ Unknown/Error"); - - Mono eData = DatabaseManager.INSTANCE.getEventData(settings.getGuildID(), event.getId()) - .defaultIfEmpty(new EventData()) - .cache(); - - Mono img = eData.filter(EventData::shouldBeSaved) - .flatMap(ed -> ImageUtils.validate(ed.getImageLink(), settings.getPatronGuild())) - .defaultIfEmpty(false); - - return Mono.zip(guild, startDate, startTime, timezone, eData, img) - .map(TupleUtils.function((g, sDate, sTime, tz, ed, hasImg) -> { - var embed = EmbedCreateSpec.builder(); - - if (settings.getBranded()) - embed.author(g.getName(), BotSettings.BASE_URL.get(), - g.getIconUrl(Image.Format.PNG).orElse(GlobalVal.getIconUrl())); - else - embed.author("DisCal", BotSettings.BASE_URL.get(), GlobalVal.getIconUrl()); - - embed.title(Messages.getMessage("Embed.Announcement.Announce.Title", settings)); - if (hasImg) - embed.image(ed.getImageLink()); - - embed.url(event.getHtmlLink()); - - try { - EventColor ec = EventColor.Companion.fromNameOrHexOrId(event.getColorId()); - embed.color(ec.asColor()); - } catch (Exception e) { - //I dunno, color probably null. - embed.color(GlobalVal.getDiscalColor()); - } - - if (settings.getAnnouncementStyle() == AnnouncementStyle.FULL) { - embed.footer(Messages.getMessage("Embed.Announcement.Announce.ID", "%id%", - a.getAnnouncementId().toString(), settings), null); - } - - if (a.getInfoOnly() && !"none".equalsIgnoreCase(a.getInfo())) { - //Only send info... - embed.addField(Messages.getMessage("Embed.Announcement.Announce.Info", settings), a.getInfo(), false); - } else { - //Requires all announcement data - if (event.getSummary() != null) { - String summary = event.getSummary(); - if (summary.length() > 250) { - summary = summary.substring(0, 250); - summary = summary + " (continues on Google Calendar View)"; - } - embed.addField(Messages.getMessage("Embed.Announcement.Announce.Summary", settings), summary, true); - } - if (event.getDescription() != null) { - String description = event.getDescription(); - if (description.length() > 250) { - description = description.substring(0, 250); - description = description + " (continues on Google Calendar View)"; - } - embed.addField(Messages.getMessage("Embed.Announcement.Announce.Description", settings), description, true); - } - if (settings.getAnnouncementStyle() == AnnouncementStyle.FULL) { - embed.addField(Messages.getMessage("Embed.Announcement.Announce.Date", settings), sDate, true); - embed.addField(Messages.getMessage("Embed.Announcement.Announce.Time", settings), sTime, true); - embed.addField(Messages.getMessage("Embed.Announcement.Announce.TimeZone", settings), tz, true); - } else { - String start = sDate + " at " + sTime + " " + tz; - embed.addField(Messages.getMessage("Embed.Announcement.Announce.Start", settings), start, false); - } - - if (event.getLocation() != null && !"".equalsIgnoreCase(event.getLocation())) { - if (event.getLocation().length() > 300) { - String location = event.getLocation().substring(0, 300).trim() + "... (cont. on Google Cal)"; - embed.addField(Messages.getMessage("Embed.Event.Confirm.Location", settings), location, true); - } else { - embed.addField(Messages.getMessage("Embed.Event.Confirm.Location", settings), event.getLocation(), true); - } - } - - if (settings.getAnnouncementStyle() == AnnouncementStyle.FULL) - embed.addField(Messages.getMessage("Embed.Announcement.Announce.EventID", settings), event.getId(), false); - if (!"None".equalsIgnoreCase(a.getInfo()) && !"".equalsIgnoreCase(a.getInfo())) - embed.addField(Messages.getMessage("Embed.Announcement.Announce.Info", settings), a.getInfo(), false); - } - - return embed.build(); - })); - } - - @Deprecated - public static Mono sendAnnouncementMessage(Announcement a, Event event, CalendarData data, - GuildSettings settings) { - Mono guild = DisCalClient.getClient().getGuildById(settings.getGuildID()).cache(); - - Mono embed = getRealAnnouncementEmbed(a, event, data, settings); - Mono mentions = guild.flatMap(g -> getSubscriberMentions(a, g)); - - return Mono.zip(guild, embed, mentions) - .flatMap(TupleUtils.function((g, em, men) -> - g.getChannelById(Snowflake.of(a.getAnnouncementChannelId())) - .ofType(GuildMessageChannel.class) - .onErrorResume(ClientException.class, e -> - Mono.just(e.getStatus()) - .filter(HttpResponseStatus.NOT_FOUND::equals) - .flatMap(ignored -> DatabaseManager.INSTANCE.deleteAnnouncement(a.getAnnouncementId().toString())) - .then(Mono.empty())) - .flatMap(chan -> { - if (a.getPublish()) { - return Messages.sendMessage(men, em, chan) - .flatMap(Message::publish) - .onErrorResume(e -> Mono.empty()); - } else - return Messages.sendMessage(men, em, chan); - }) - )).then(); - } - - public static Mono sendAnnouncementMessage(Guild guild, Announcement a, Event event, CalendarData data, - GuildSettings settings) { - Mono embed = getRealAnnouncementEmbed(a, event, data, settings); - Mono mentions = getSubscriberMentions(a, guild); - - return Mono.zip(embed, mentions) - .flatMap(TupleUtils.function((em, men) -> - guild.getChannelById(Snowflake.of(a.getAnnouncementChannelId())) - .ofType(GuildMessageChannel.class) - .onErrorResume(ClientException.class, e -> - Mono.just(e.getStatus()) - .filter(HttpResponseStatus.NOT_FOUND::equals) - .flatMap(ignored -> DatabaseManager.INSTANCE.deleteAnnouncement(a.getAnnouncementId().toString())) - .then(Mono.empty())) - .flatMap(chan -> { - if (a.getPublish()) { - return Messages.sendMessage(men, em, chan) - .flatMap(Message::publish) - .onErrorResume(e -> Mono.empty()); - } else - return Messages.sendMessage(men, em, chan); - }) - )).then(); - } - - public static Mono sendAnnouncementDM(Announcement a, Event event, User user, CalendarData data, - GuildSettings settings) { - return DisCalClient.getClient().getGuildById(settings.getGuildID()) - .map(g -> Messages.getMessage("Embed.Announcement.Announce.Dm.Message", "%guild%", g.getName(), settings)) - .flatMap(msg -> getRealAnnouncementEmbed(a, event, data, settings) - .flatMap(em -> Messages.sendDirectMessage(msg, em, user)) - ).then(); - } - private static String condensedTime(Announcement a) { return a.getHoursBefore() + "H" + a.getMinutesBefore() + "m"; } @@ -448,45 +211,4 @@ public class AnnouncementMessageFormatter { })); }); } - - private static Mono getSubscriberMentions(Announcement a, Guild guild) { - return Mono.defer(() -> { - Mono> userMentions = Flux.fromIterable(a.getSubscriberUserIds()) - .flatMap(s -> UserUtils.getUserFromID(s, guild)) - .map(Member::getNicknameMention) - .onErrorReturn("") - .collectList() - .defaultIfEmpty(new ArrayList<>()); - - Mono> roleMentions = Flux.fromIterable(a.getSubscriberRoleIds()) - .flatMap(s -> { - if ("everyone".equalsIgnoreCase(s)) - return Mono.just("@everyone"); - else if ("here".equalsIgnoreCase(s)) - return Mono.just("@here"); - else { - return RoleUtils.getRoleFromID(s, guild) - .map(Role::getMention) - .onErrorReturn(""); - } - }).collectList() - .defaultIfEmpty(new ArrayList<>()); - - return Mono.zip(userMentions, roleMentions).map(TupleUtils.function((users, roles) -> { - StringBuilder mentions = new StringBuilder(); - - mentions.append("Subscribers: "); - - for (String s : users) { - mentions.append(s).append(" "); - } - - for (String s : roles) { - mentions.append(s).append(" "); - } - - return mentions.toString(); - })); - }); - } } diff --git a/client/src/main/java/org/dreamexposure/discal/client/module/announcement/AnnouncementThread.java b/client/src/main/java/org/dreamexposure/discal/client/module/announcement/AnnouncementThread.java deleted file mode 100644 index 2c6f0090..00000000 --- a/client/src/main/java/org/dreamexposure/discal/client/module/announcement/AnnouncementThread.java +++ /dev/null @@ -1,255 +0,0 @@ -package org.dreamexposure.discal.client.module.announcement; - -import com.google.api.services.calendar.Calendar; -import com.google.api.services.calendar.model.Event; -import discord4j.common.util.Snowflake; -import discord4j.core.GatewayDiscordClient; -import discord4j.core.object.entity.Guild; -import org.dreamexposure.discal.client.message.AnnouncementMessageFormatter; -import org.dreamexposure.discal.core.database.DatabaseManager; -import org.dreamexposure.discal.core.enums.announcement.AnnouncementType; -import org.dreamexposure.discal.core.enums.event.EventColor; -import org.dreamexposure.discal.core.object.GuildSettings; -import org.dreamexposure.discal.core.object.announcement.Announcement; -import org.dreamexposure.discal.core.object.calendar.CalendarData; -import org.dreamexposure.discal.core.wrapper.google.EventWrapper; -import org.dreamexposure.discal.core.wrapper.google.GoogleAuthWrapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.function.TupleUtils; - -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static org.dreamexposure.discal.core.utils.GlobalVal.getDEFAULT; - -public class AnnouncementThread { - private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); - - private final GatewayDiscordClient client; - - private final Map> allSettings = new ConcurrentHashMap<>(); - private final Map> calendars = new ConcurrentHashMap<>(); - private final Map> customServices = new ConcurrentHashMap<>(); - private final Map>> allEvents = new ConcurrentHashMap<>(); - - private final Map> discalServices = new ConcurrentHashMap<>(); - - private final long maxDifferenceMs = Duration.ofMinutes(5).toMillis(); - - public AnnouncementThread(GatewayDiscordClient client) { - this.client = client; - } - - public Mono run() { - //Get the credentials and cache them - Mono getCredsMono = GoogleAuthWrapper.INSTANCE.credentialsCount() - .flatMapMany(i -> Flux.range(0, i)) - .map(index -> { - this.discalServices.put(index, GoogleAuthWrapper.INSTANCE.getCalendarService(index).cache()); - return index; - }).then(); - - //Actually do announcements - Mono doAnnMono = this.client.getGuilds() - .flatMap(guild -> DatabaseManager.INSTANCE.getEnabledAnnouncements(guild.getId()) - .flatMapMany(Flux::fromIterable) - .flatMap(a -> { - - final Mono s = this.getSettings(a).cache(); - final Mono cd = this.getCalendarData(a).cache(); - final Mono se = cd.flatMap(calData -> s.flatMap(gs -> this.getService(calData))); - - return Mono.zip(s, cd, se) - .flatMap(TupleUtils.function((settings, calData, service) -> { - switch (a.getModifier()) { - case BEFORE: - return this.handleBeforeModifier(guild, a, settings, calData, service); - case DURING: - return this.handleDuringModifier(guild, a, settings, calData, service); - case END: - return this.handleEndModifier(guild, a, settings, calData, service); - default: - return Mono.empty(); - } - })); - }) - .doOnError(e -> LOGGER.error(getDEFAULT(), "Announcement error", e)) - .onErrorResume(e -> Mono.empty()) - ) - .doOnError(e -> LOGGER.error(getDEFAULT(), "Announcement error", e)) - .onErrorResume(e -> Mono.empty()) - .doFinally(ignore -> { - this.allSettings.clear(); - this.calendars.clear(); - this.customServices.clear(); - this.allEvents.clear(); - }).then(); - - //Finally execute those two chains, in order. - return getCredsMono.then(doAnnMono); - } - - //Modifier handling - private Mono handleBeforeModifier(Guild guild, Announcement a, GuildSettings settings, CalendarData calData, - Calendar service) { - switch (a.getType()) { - case SPECIFIC: - return EventWrapper.INSTANCE.getEvent(calData, a.getEventId()) - .switchIfEmpty(DatabaseManager.INSTANCE.deleteAnnouncement(a.getAnnouncementId().toString()) - .then(Mono.empty()) - ).flatMap(e -> this.inRangeSpecific(a, e) - .flatMap(inRange -> { - if (inRange) { - return AnnouncementMessageFormatter - .sendAnnouncementMessage(guild, a, e, calData, settings) - .then(DatabaseManager - .INSTANCE.deleteAnnouncement(a.getAnnouncementId().toString()) - ); - } else { - return Mono.empty(); //Not in range, but still valid. - } - })) - .then(); - case UNIVERSAL: - return this.getEvents(calData, service) - .flatMapMany(Flux::fromIterable) - .filter(e -> this.isInRange(a, e)) - .flatMap(e -> AnnouncementMessageFormatter - .sendAnnouncementMessage(guild, a, e, calData, settings)) - .then(); - case COLOR: - return this.getEvents(calData, service) - .flatMapMany(Flux::fromIterable) - .filter(e -> e.getColorId() != null - && a.getEventColor().equals(EventColor - .Companion.fromNameOrHexOrId(e.getColorId()))) - .filter(e -> this.isInRange(a, e)) - .flatMap(e -> AnnouncementMessageFormatter - .sendAnnouncementMessage(guild, a, e, calData, settings)) - .then(); - - case RECUR: - return this.getEvents(calData, service) - .flatMapMany(Flux::fromIterable) - .filter(e -> e.getId().contains("_") && e.getId().split("_")[0].equals(a.getEventId())) - .filter(e -> this.isInRange(a, e)) - .flatMap(e -> AnnouncementMessageFormatter - .sendAnnouncementMessage(guild, a, e, calData, settings)) - .then(); - default: - return Mono.empty(); - } - } - - //TODO: Actually support this. - private Mono handleDuringModifier(Guild guild, Announcement a, GuildSettings settings, CalendarData calData, - Calendar service) { - switch (a.getType()) { - case SPECIFIC: - case UNIVERSAL: - case COLOR: - case RECUR: - default: - return Mono.empty(); - } - } - - //TODO: Actually support this too - private Mono handleEndModifier(Guild guild, Announcement a, GuildSettings settings, CalendarData calData, - Calendar service) { - switch (a.getType()) { - case SPECIFIC: - case UNIVERSAL: - case COLOR: - case RECUR: - default: - return Mono.empty(); - } - } - - - //Utility - private Mono inRangeSpecific(Announcement a, Event e) { - return Mono.defer(() -> { - long announcementTimeMs = Integer.toUnsignedLong(a.getMinutesBefore() + (a.getHoursBefore() * 60)) * 60 * 1000; - long timeUntilEvent = this.getEventStartMs(e) - System.currentTimeMillis(); - - long difference = timeUntilEvent - announcementTimeMs; - - if (difference < 0) { - //Event past, we can delete announcement depending on the type - if (a.getType() == AnnouncementType.SPECIFIC) - return DatabaseManager.INSTANCE.deleteAnnouncement(a.getAnnouncementId().toString()) - .thenReturn(false); - - return Mono.just(false); - } else { - return Mono.just(difference <= this.maxDifferenceMs); - } - }); - } - - private boolean isInRange(Announcement a, Event e) { - long announcementTimeMs = Integer.toUnsignedLong(a.getMinutesBefore() + (a.getHoursBefore() * 60)) * 60 * 1000; - long timeUntilEvent = this.getEventStartMs(e) - System.currentTimeMillis(); - - long difference = timeUntilEvent - announcementTimeMs; - - if (difference < 0) { - //Event past, we can delete announcement depending on the type - if (a.getType() == AnnouncementType.SPECIFIC) - return false; //Shouldn't even be used for specific types... - - return false; - } else { - return difference <= this.maxDifferenceMs; - } - } - - private long getEventStartMs(Event e) { - if (e.getStart().getDateTime() != null) - return e.getStart().getDateTime().getValue(); - else - return e.getStart().getDate().getValue(); - - } - - private Mono getSettings(Announcement a) { - if (!this.allSettings.containsKey(a.getGuildId())) - this.allSettings.put(a.getGuildId(), DatabaseManager.INSTANCE.getSettings(a.getGuildId()).cache()); - - return this.allSettings.get(a.getGuildId()); - } - - //TODO: Allow multiple calendar support - private Mono getCalendarData(Announcement a) { - if (!this.calendars.containsKey(a.getGuildId())) - this.calendars.put(a.getGuildId(), DatabaseManager.INSTANCE.getMainCalendar(a.getGuildId()).cache()); - - return this.calendars.get(a.getGuildId()); - } - - //TODO: Just redo like literally everything - private Mono getService(CalendarData cd) { - if (cd.getExternal()) { - if (!this.customServices.containsKey(cd.getGuildId())) - this.customServices.put(cd.getGuildId(), GoogleAuthWrapper.INSTANCE.getCalendarService(cd).cache()); - - return this.customServices.get(cd.getGuildId()); - } - return GoogleAuthWrapper.INSTANCE.getCalendarService(cd.getCredentialId()); - } - - private Mono> getEvents(CalendarData cd, Calendar service) { - if (!this.allEvents.containsKey(cd.getGuildId())) { - Mono> events = EventWrapper.INSTANCE.getEvents(cd, service, 15, System.currentTimeMillis()).cache(); - this.allEvents.put(cd.getGuildId(), events); - } - return this.allEvents.get(cd.getGuildId()); - } -} diff --git a/client/src/main/java/org/dreamexposure/discal/client/module/announcement/package-info.java b/client/src/main/java/org/dreamexposure/discal/client/module/announcement/package-info.java deleted file mode 100644 index 2ced0c23..00000000 --- a/client/src/main/java/org/dreamexposure/discal/client/module/announcement/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.dreamexposure.discal.client.module.announcement; \ No newline at end of file diff --git a/client/src/main/java/org/dreamexposure/discal/client/module/command/AnnouncementCommand.java b/client/src/main/java/org/dreamexposure/discal/client/module/command/AnnouncementCommand.java index eb42ace8..20c57313 100644 --- a/client/src/main/java/org/dreamexposure/discal/client/module/command/AnnouncementCommand.java +++ b/client/src/main/java/org/dreamexposure/discal/client/module/command/AnnouncementCommand.java @@ -97,7 +97,6 @@ public class AnnouncementCommand implements Command { info.getSubCommands().put("info", "Sets an additional info."); info.getSubCommands().put("enable", "Enables or Disables the announcement (alias for `disable`)"); info.getSubCommands().put("disable", "Enables or Disables the announcement (alias for `enable`)"); - info.getSubCommands().put("infoOnly", "Allows for setting an announcement to ONLY display the 'extra info'"); info.getSubCommands().put("publish", "Allows for the event to be published if posted in a news channel"); return info; @@ -174,8 +173,6 @@ public class AnnouncementCommand implements Command { case "disable": case "disabled": return this.moduleEnable(args, event, settings); - case "infoonly": - return this.moduleInfoOnly(args, event, settings); case "channel": return this.moduleChannel(args, event, settings); case "color": @@ -574,7 +571,7 @@ public class AnnouncementCommand implements Command { + Messages.getMessage("Embed.Announcement.Subscribe.Roles", "%roles%", subRoleString, settings)) .footer(Messages.getMessage("Embed.Announcement.Subscribe.Footer", "%id%", - a.getAnnouncementId().toString(), settings), null) + a.getId().toString(), settings), null) .build(); return DatabaseManager.INSTANCE.updateAnnouncement(a).thenReturn(embed); @@ -635,7 +632,7 @@ public class AnnouncementCommand implements Command { + Messages.getMessage("Embed.Announcement.Subscribe.Roles", "%roles%", subRoleString, settings)) .footer(Messages.getMessage("Embed.Announcement.Subscribe.Footer", "%id%", - a.getAnnouncementId().toString(), settings), null) + a.getId().toString(), settings), null) .build(); return Mono.when(deleteUserMessage, deleteCreatorMessage).thenReturn(embed); @@ -840,7 +837,7 @@ public class AnnouncementCommand implements Command { + Messages.getMessage("Embed.Announcement.Unsubscribe.Roles", "%roles%", subRoleString, settings)) .footer(Messages.getMessage("Embed.Announcement.Unsubscribe.Footer", "%id%", - a.getAnnouncementId().toString(), settings), null) + a.getId().toString(), settings), null) .build(); @@ -902,7 +899,7 @@ public class AnnouncementCommand implements Command { + Messages.getMessage("Embed.Announcement.Unsubscribe.Roles", "%roles%", subRoleString, settings)) .footer(Messages.getMessage("Embed.Announcement.Unsubscribe.Footer", "%id%", - a.getAnnouncementId().toString(), settings), null) + a.getId().toString(), settings), null) .build(); @@ -1245,41 +1242,6 @@ public class AnnouncementCommand implements Command { }).then(); } - private Mono moduleInfoOnly(String[] args, MessageCreateEvent event, GuildSettings settings) { - return Mono.defer(() -> { - if (AnnouncementCreator.getCreator().hasAnnouncement(settings.getGuildID())) { - Announcement a = AnnouncementCreator.getCreator().getAnnouncement(settings.getGuildID()); - - Mono deleteUserMessage = Messages.deleteMessage(event); - Mono deleteCreatorMessage = Messages.deleteMessage(a.getCreatorMessage()); - - return Mono.when(deleteUserMessage, deleteCreatorMessage) - .then(AnnouncementMessageFormatter.getFormatAnnouncementEmbed(a, settings)) - .flatMap(em -> Messages.sendMessage( - Messages.getMessage("Announcement.InfoOnly.Creator", settings), em, event)) - .doOnNext(a::setCreatorMessage); - } else if (args.length == 2) { - return AnnouncementUtils.announcementExists(args[1], settings.getGuildID()).flatMap(exists -> { - if (exists) { - UUID id = UUID.fromString(args[1]); - AtomicBoolean io = new AtomicBoolean(false); //This has got to be tested... - return DatabaseManager.INSTANCE.getAnnouncement(id, settings.getGuildID()) - .doOnNext(a -> a.setInfoOnly(!a.getInfoOnly())) - .doOnNext(a -> io.set(a.getInfoOnly())) - .flatMap(DatabaseManager.INSTANCE::updateAnnouncement) - .map(i -> Messages.getMessage("Announcement.InfoOnly.Success", "%value%", io.get() + "", settings)) - .flatMap(msg -> Messages.sendMessage(msg, event)); - } else { - return Messages.sendMessage( - Messages.getMessage("Creator.Announcement.CannotFind.Announcement", settings), event); - } - }); - } else { - return Messages.sendMessage(Messages.getMessage("Announcement.InfoOnly.Specify", settings), event); - } - }).then(); - } - private Mono modulePublish(MessageCreateEvent event, GuildSettings settings) { return Mono.defer(() -> { if (AnnouncementCreator.getCreator().hasAnnouncement(settings.getGuildID())) { diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/DisCalClient.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/DisCalClient.kt index 6909cca8..bc0d76e6 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/DisCalClient.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/DisCalClient.kt @@ -24,7 +24,6 @@ import io.lettuce.core.RedisURI import org.dreamexposure.discal.Application import org.dreamexposure.discal.client.listeners.discord.* import org.dreamexposure.discal.client.message.Messages -import org.dreamexposure.discal.client.module.announcement.AnnouncementThread import org.dreamexposure.discal.client.module.command.* import org.dreamexposure.discal.client.service.TimeManager import org.dreamexposure.discal.core.`object`.BotSettings @@ -34,10 +33,8 @@ import org.dreamexposure.discal.core.utils.GlobalVal.DEFAULT import org.dreamexposure.discal.core.utils.GlobalVal.STATUS import org.springframework.boot.builder.SpringApplicationBuilder import org.springframework.stereotype.Component -import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.io.FileReader -import java.time.Duration import java.util.* import javax.annotation.PreDestroy import kotlin.system.exitProcess @@ -112,15 +109,7 @@ class DisCalClient { .on(ChatInputInteractionEvent::class.java, slashCommandListener::handle) .then() - val startAnnouncement = Flux.interval(Duration.ofMinutes(5)) - .onBackpressureBuffer() - .flatMap { - AnnouncementThread(client).run().doOnError { - LOGGER.error("Announcement error", it) - }.onErrorResume { Mono.empty() } - } - - Mono.`when`(onReady, onRoleDelete, onCommand, onSlashCommand, startAnnouncement) + Mono.`when`(onReady, onRoleDelete, onCommand, onSlashCommand) }.block() } } diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt index 52416a8c..4ca90809 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/message/embed/AnnouncementEmbed.kt @@ -6,6 +6,7 @@ import discord4j.core.`object`.entity.channel.GuildChannel import discord4j.core.spec.EmbedCreateSpec import org.dreamexposure.discal.core.`object`.announcement.Announcement import org.dreamexposure.discal.core.entities.Event +import org.dreamexposure.discal.core.enums.announcement.AnnouncementStyle import org.dreamexposure.discal.core.enums.announcement.AnnouncementType import org.dreamexposure.discal.core.extensions.asDiscordTimestamp import org.dreamexposure.discal.core.extensions.discord4j.getSettings @@ -14,6 +15,16 @@ import reactor.core.publisher.Mono import reactor.function.TupleUtils object AnnouncementEmbed : EmbedMaker { + fun determine(ann: Announcement, event: Event, guild: Guild): Mono { + return guild.getSettings().flatMap { settings -> + when (settings.announcementStyle) { + AnnouncementStyle.FULL -> full(ann, event, guild) + AnnouncementStyle.SIMPLE -> simple(ann, event, guild) + AnnouncementStyle.EVENT -> event(ann, event, guild) + } + } + } + fun full(ann: Announcement, event: Event, guild: Guild): Mono { return guild.getSettings().map { settings -> val builder = defaultBuilder(guild, settings) @@ -52,7 +63,7 @@ object AnnouncementEmbed : EmbedMaker { builder.image(event.image) } - builder.footer(getMessage("announcement", "full.footer", settings, ann.announcementId.toString()), null) + builder.footer(getMessage("announcement", "full.footer", settings, ann.id.toString()), null) builder.build() } @@ -84,7 +95,7 @@ object AnnouncementEmbed : EmbedMaker { builder.image(event.image) } - builder.footer(getMessage("announcement", "simple.footer", settings, ann.announcementId.toString()), null) + builder.footer(getMessage("announcement", "simple.footer", settings, ann.id.toString()), null) builder.build() } @@ -127,7 +138,7 @@ object AnnouncementEmbed : EmbedMaker { builder.image(event.image) } - builder.footer(getMessage("announcement", "event.footer", settings, ann.announcementId.toString()), null) + builder.footer(getMessage("announcement", "event.footer", settings, ann.id.toString()), null) builder.build() } @@ -137,7 +148,7 @@ object AnnouncementEmbed : EmbedMaker { return guild.getSettings().map { settings -> val builder = defaultBuilder(guild, settings) .title(getMessage("announcement", "con.title", settings)) - .addField(getMessage("announcement", "con.field.id", settings), ann.announcementId.toString(), false) + .addField(getMessage("announcement", "con.field.id", settings), ann.id.toString(), false) .addField(getMessage("announcement", "con.field.time", settings), condensedTime(ann), true) .addField(getMessage("announcement", "con.field.enabled", settings), "${ann.enabled}", true) .footer(getMessage("announcement", "con.footer", settings, ann.type.name, ann.modifier.name), null) @@ -180,7 +191,7 @@ object AnnouncementEmbed : EmbedMaker { } else builder.color(GlobalVal.discalColor) - builder.addField(getMessage("announcement", "view.field.id", settings), ann.announcementId.toString(), false) + builder.addField(getMessage("announcement", "view.field.id", settings), ann.id.toString(), false) .addField(getMessage("announcement", "view.field.enabled", settings), "${ann.enabled}", true) .addField(getMessage("announcement", "view.field.publish", settings), "${ann.publish}", true) .build() diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt new file mode 100644 index 00000000..bf04b986 --- /dev/null +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt @@ -0,0 +1,234 @@ +package org.dreamexposure.discal.client.service + +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.Guild +import discord4j.core.`object`.entity.Message +import discord4j.core.`object`.entity.Role +import discord4j.core.`object`.entity.channel.GuildMessageChannel +import discord4j.core.spec.MessageCreateSpec +import discord4j.rest.http.client.ClientException +import io.netty.handler.codec.http.HttpResponseStatus +import org.dreamexposure.discal.client.DisCalClient +import org.dreamexposure.discal.client.message.embed.AnnouncementEmbed +import org.dreamexposure.discal.core.`object`.announcement.Announcement +import org.dreamexposure.discal.core.`object`.announcement.AnnouncementCache +import org.dreamexposure.discal.core.database.DatabaseManager +import org.dreamexposure.discal.core.entities.Calendar +import org.dreamexposure.discal.core.entities.Event +import org.dreamexposure.discal.core.enums.announcement.AnnouncementModifier +import org.dreamexposure.discal.core.enums.announcement.AnnouncementType.* +import org.dreamexposure.discal.core.extensions.discord4j.getCalendar +import org.dreamexposure.discal.core.logger.LOGGER +import org.dreamexposure.discal.core.utils.GlobalVal +import org.springframework.boot.ApplicationArguments +import org.springframework.boot.ApplicationRunner +import org.springframework.stereotype.Component +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.function.TupleUtils +import java.time.Duration +import java.util.concurrent.ConcurrentHashMap + +@Component +class AnnouncementService : ApplicationRunner { + private val maxDifferenceMs = Duration.ofMinutes(5).toMillis() + + private val cached = ConcurrentHashMap() + + // Start + override fun run(args: ApplicationArguments?) { + Flux.interval(Duration.ofMinutes(5)) + .onBackpressureBuffer() + .flatMap { doAnnouncementCycle() } + .doOnError { LOGGER.error(GlobalVal.DEFAULT, "!-Announcement run error-!", it) } + .subscribe() + } + + // Runner + private fun doAnnouncementCycle(): Mono { + //TODO: This should come in through DI once other legacy is removed/rewritten + if (DisCalClient.client == null) return Mono.empty() + + return DisCalClient.client!!.guilds.flatMap { guild -> + DatabaseManager.getEnabledAnnouncements(guild.id) + .flatMapMany { Flux.fromIterable(it) } + .flatMap { announcement -> + when (announcement.modifier) { + AnnouncementModifier.BEFORE -> handleBeforeModifier(guild, announcement) + AnnouncementModifier.DURING -> handleDuringModifier(guild, announcement) + AnnouncementModifier.END -> handleEndModifier(guild, announcement) + } + }.doOnError { + LOGGER.error(GlobalVal.DEFAULT, "Announcement error", it) + }.onErrorResume { Mono.empty() } + }.doOnError { + LOGGER.error(GlobalVal.DEFAULT, "Announcement error", it) + }.onErrorResume { + Mono.empty() + }.doFinally { + cached.clear() + }.then() + } + + // Modifier handling + private fun handleBeforeModifier(guild: Guild, announcement: Announcement): Mono { + when (announcement.type) { + SPECIFIC -> { + return getCalendar(guild, announcement) + .flatMap { it.getEvent(announcement.eventId) } + //Event announcement is tied to was deleted + .switchIfEmpty(DatabaseManager.deleteAnnouncement(announcement.id.toString()).then(Mono.empty())) + .filterWhen { isInRange(announcement, it) } + .flatMap { sendAnnouncement(guild, announcement, it) } + // Delete specific announcement after posted + .flatMap { DatabaseManager.deleteAnnouncement(announcement.id.toString()) } + .then() + } + UNIVERSAL -> { + return getEvents(guild, announcement) + .filterWhen { isInRange(announcement, it) } + .flatMap { sendAnnouncement(guild, announcement, it) } + .then() + } + COLOR -> { + return getEvents(guild, announcement) + .filter { it.color == announcement.eventColor } + .filterWhen { isInRange(announcement, it) } + .flatMap { sendAnnouncement(guild, announcement, it) } + .then() + } + RECUR -> { + return getEvents(guild, announcement) + .filter { it.eventId.contains("_") && it.eventId.split("_")[0] == announcement.eventId } + .filterWhen { isInRange(announcement, it) } + .flatMap { sendAnnouncement(guild, announcement, it) } + .then() + } + } + } + + @Suppress("UNUSED_PARAMETER") + private fun handleDuringModifier(guild: Guild, announcement: Announcement): Mono { + //TODO: Not yet implemented + + return Mono.empty() + } + + @Suppress("UNUSED_PARAMETER") + private fun handleEndModifier(guild: Guild, announcement: Announcement): Mono { + //TODO: Not yet implemented + + return Mono.empty() + } + + // Utility + private fun isInRange(announcement: Announcement, event: Event): Mono { + val announcementTime = Duration + .ofHours(announcement.hoursBefore.toLong()) + .plusMinutes(announcement.minutesBefore.toLong()) + .toMillis() + val timeUntilEvent = event.start.minusMillis(System.currentTimeMillis()).toEpochMilli() + + val difference = timeUntilEvent - announcementTime + + if (difference < 0) { + //event past, delete if specific type + if (announcement.type == SPECIFIC) { + return DatabaseManager.deleteAnnouncement(announcement.id.toString()) + .thenReturn(false) + } + return Mono.just(false) + } else return Mono.just(difference <= maxDifferenceMs) + } + + private fun sendAnnouncement(guild: Guild, announcement: Announcement, event: Event): Mono { + val embedMono = AnnouncementEmbed.determine(announcement, event, guild) + val mentionsMono = buildMentions(guild, announcement).onErrorReturn("") + + return guild.getChannelById(Snowflake.of(announcement.announcementChannelId)) + .ofType(GuildMessageChannel::class.java) + .flatMap { channel -> + Mono.zip(embedMono, mentionsMono).flatMap(TupleUtils.function { embed, mentions -> + if (mentions.isEmpty()) + return@function channel.createMessage(embed) + else + return@function channel.createMessage( + MessageCreateSpec.builder() + .content(mentions) + .addEmbed(embed) + .build() + ) + }).flatMap { message -> + if (announcement.publish) { + message.publish() + } else Mono.just(message) + } + }.onErrorResume(ClientException::class.java) { + Mono.just(it) + .filter(HttpResponseStatus.NOT_FOUND::equals) + // Channel announcement should post to was deleted + .flatMap { DatabaseManager.deleteAnnouncement(announcement.id.toString()) } + .then(Mono.empty()) + } + } + + private fun buildMentions(guild: Guild, announcement: Announcement): Mono { + val userMentions = Flux.fromIterable(announcement.subscriberUserIds) + .flatMap { guild.getMemberById(Snowflake.of(it)) } + .map { it.nicknameMention } + .onErrorReturn("") + .collectList() + .defaultIfEmpty(listOf()) + + val roleMentions = Flux.fromIterable(announcement.subscriberRoleIds) + .flatMap { + if (it.equals("everyone", true)) guild.everyoneRole.map(Role::getMention) + else if (it.equals("here", true)) Mono.just("here") + else guild.getRoleById(Snowflake.of(it)).map(Role::getMention) + } + .onErrorReturn("") + .collectList() + .defaultIfEmpty(listOf()) + + return Mono.zip(userMentions, roleMentions).map(TupleUtils.function { users, roles -> + if (users.isEmpty() && roles.isEmpty()) "" + else { + val mentions = StringBuilder() + + mentions.append("Subscribers: ") + + for (u in users) mentions.append("$u ") + for (r in roles) mentions.append("$r ") + + mentions.toString() + } + }) + } + + // Cache things + private fun getCalendar(guild: Guild, announcement: Announcement): Mono { + val cached = getCached(announcement.guildId) + + return if (!cached.calendars.contains(announcement.calendarNumber)) { + guild.getCalendar(announcement.calendarNumber) + .doOnNext { cached.calendars[it.calendarNumber] = it } + } else Mono.justOrEmpty(cached.calendars[announcement.calendarNumber]) + } + + private fun getEvents(guild: Guild, announcement: Announcement): Flux { + val cached = getCached(announcement.guildId) + + return if (!cached.events.contains(announcement.calendarNumber)) { + getCalendar(guild, announcement).flatMapMany { + it.getUpcomingEvents(20).cache() + } + } else cached.events[announcement.calendarNumber]!! + } + + private fun getCached(guildId: Snowflake): AnnouncementCache { + if (!cached.contains(guildId)) + cached[guildId] = AnnouncementCache(guildId) + + return cached[guildId]!! + } +} diff --git a/core/src/main/java/org/dreamexposure/discal/core/utils/PermissionChecker.java b/core/src/main/java/org/dreamexposure/discal/core/utils/PermissionChecker.java index 6453b317..2a9e7809 100644 --- a/core/src/main/java/org/dreamexposure/discal/core/utils/PermissionChecker.java +++ b/core/src/main/java/org/dreamexposure/discal/core/utils/PermissionChecker.java @@ -4,20 +4,10 @@ import discord4j.common.util.Snowflake; import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.core.object.entity.Member; import discord4j.core.object.entity.Role; -import discord4j.discordjson.json.GuildUpdateData; -import discord4j.discordjson.json.MemberData; -import discord4j.discordjson.json.RoleData; -import discord4j.rest.entity.RestGuild; -import discord4j.rest.entity.RestMember; import discord4j.rest.util.Permission; -import discord4j.rest.util.PermissionSet; -import org.dreamexposure.discal.core.object.BotSettings; import org.dreamexposure.discal.core.object.GuildSettings; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.function.Predicate; - /** * Created by Nova Fox on 1/19/17. * Website: www.cloudcraftgaming.com @@ -49,64 +39,6 @@ public class PermissionChecker { }); } - public static Mono hasDisCalRole(final Member member, final GuildSettings settings) { - if ("everyone".equalsIgnoreCase(settings.getControlRole())) - return Mono.just(true); - if (Snowflake.of(settings.getControlRole()).equals(settings.getGuildID())) //also everyone - return Mono.just(true); - - //User doesn't need bot control role if they have admin permissions. - final Mono hasAdmin = Mono.just(member).flatMap(Member::getBasePermissions).map(perms -> - perms.contains(Permission.ADMINISTRATOR) || perms.contains(Permission.MANAGE_GUILD)); - - return hasAdmin.flatMap(has -> { - if (has) { - return Mono.just(true); - } else { - return Mono.just(member).flatMapMany(Member::getRoles) - .map(Role::getId) - .any(id -> id.equals(Snowflake.of(settings.getControlRole()))); - } - }); - } - - @Deprecated - public static Mono hasSufficientRole(final MessageCreateEvent event, final GuildSettings settings) { - if ("everyone".equalsIgnoreCase(settings.getControlRole())) - return Mono.just(true); - if (Snowflake.of(settings.getControlRole()).equals(settings.getGuildID())) //also everyone - return Mono.just(true); - - return Mono.justOrEmpty(event.getMember()) - .flatMapMany(Member::getRoles) - .map(Role::getId) - .any(snowflake -> snowflake.equals(Snowflake.of(settings.getControlRole()))); - } - - @Deprecated - public static Mono hasSufficientRole(final Member member, final GuildSettings settings) { - if ("everyone".equalsIgnoreCase(settings.getControlRole())) - return Mono.just(true); - if (Snowflake.of(settings.getControlRole()).equals(settings.getGuildID())) //also everyone - return Mono.just(true); - - return Mono.from(member.getRoles() - .map(Role::getId) - .any(snowflake -> snowflake.equals(Snowflake.of(settings.getControlRole()))) - ); - } - - public static Mono hasSufficientRole(final RestMember member, final GuildSettings settings) { - if ("everyone".equalsIgnoreCase(settings.getControlRole())) - return Mono.just(true); - if (Snowflake.of(settings.getControlRole()).equals(settings.getGuildID())) //also everyone - return Mono.just(true); - - return member.getData() - .map(MemberData::roles) - .map(roles -> roles.contains(settings.getControlRole())); - } - public static Mono hasManageServerRole(final MessageCreateEvent event) { return Mono.justOrEmpty(event.getMember()) .flatMap(Member::getBasePermissions) @@ -114,38 +46,4 @@ public class PermissionChecker { || perms.contains(Permission.ADMINISTRATOR)) .defaultIfEmpty(false); } - - public static Mono hasManageServerRole(final Member m) { - return m.getBasePermissions() - .map(perms -> perms.contains(Permission.MANAGE_GUILD) - || perms.contains(Permission.ADMINISTRATOR) - ); - } - - public static Mono hasManageServerRole(final RestMember m, final RestGuild g) { - return hasPermissions(m, g, permissions -> - permissions.contains(Permission.MANAGE_GUILD) - || permissions.contains(Permission.ADMINISTRATOR)); - } - - public static Mono hasPermissions(final RestMember m, final RestGuild g, - final Predicate pred) { - return m.getData().flatMap(memberData -> - g.getData().map(GuildUpdateData::roles) - .flatMapMany(Flux::fromIterable) - .filter(roleData -> memberData.roles().contains(roleData.id())) - .map(RoleData::permissions) - .reduce(0L, (perm, accumulator) -> accumulator | perm) - .map(PermissionSet::of) - .map(pred::test) - ); - } - - public static Mono botHasMessageManagePerms(final MessageCreateEvent event) { - return event.getGuild() - .flatMap(guild -> guild.getMemberById(Snowflake.of(BotSettings.ID.get())) - .flatMap(Member::getBasePermissions) - .map(perms -> perms.contains(Permission.MANAGE_MESSAGES)) - ); - } } diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt index 27d6cbba..eccc1855 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/database/DatabaseManager.kt @@ -249,7 +249,7 @@ object DatabaseManager { val query = "SELECT * FROM ${Tables.ANNOUNCEMENTS.table} WHERE ANNOUNCEMENT_ID = ?" Mono.from(c.createStatement(query) - .bind(0, announcement.announcementId.toString()) + .bind(0, announcement.id.toString()) .execute() ).flatMapMany { res -> res.map { row, _ -> row } @@ -259,7 +259,7 @@ object DatabaseManager { CALENDAR_NUMBER = ?, SUBSCRIBERS_ROLE = ?, SUBSCRIBERS_USER = ?, CHANNEL_ID = ?, ANNOUNCEMENT_TYPE = ?, MODIFIER = ?, EVENT_ID = ?, EVENT_COLOR = ?, HOURS_BEFORE = ?, MINUTES_BEFORE = ?, - INFO = ?, ENABLED = ?, INFO_ONLY = ?, PUBLISH = ? + INFO = ?, ENABLED = ?, PUBLISH = ? WHERE ANNOUNCEMENT_ID = ? """.trimMargin() @@ -276,9 +276,8 @@ object DatabaseManager { .bind(9, announcement.minutesBefore) .bind(10, announcement.info) .bind(11, announcement.enabled) - .bind(12, announcement.infoOnly) - .bind(13, announcement.publish) - .bind(14, announcement.announcementId.toString()) + .bind(12, announcement.publish) + .bind(13, announcement.id.toString()) .execute() ).flatMapMany(Result::getRowsUpdated) .hasElements() @@ -287,12 +286,12 @@ object DatabaseManager { val insertCommand = """INSERT INTO ${Tables.ANNOUNCEMENTS.table} (ANNOUNCEMENT_ID, CALENDAR_NUMBER, GUILD_ID, SUBSCRIBERS_ROLE, SUBSCRIBERS_USER, CHANNEL_ID, ANNOUNCEMENT_TYPE, MODIFIER, EVENT_ID, EVENT_COLOR, - HOURS_BEFORE, MINUTES_BEFORE, INFO, ENABLED, INFO_ONLY, PUBLISH) - VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + HOURS_BEFORE, MINUTES_BEFORE, INFO, ENABLED, PUBLISH) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """.trimMargin() Mono.from(c.createStatement(insertCommand) - .bind(0, announcement.announcementId.toString()) + .bind(0, announcement.id.toString()) .bind(1, announcement.calendarNumber) .bind(2, announcement.guildId.asString()) .bind(3, announcement.subscriberRoleIds.asStringList()) @@ -306,8 +305,7 @@ object DatabaseManager { .bind(11, announcement.minutesBefore) .bind(12, announcement.info) .bind(13, announcement.enabled) - .bind(14, announcement.infoOnly) - .bind(15, announcement.publish) + .bind(14, announcement.publish) .execute() ).flatMapMany(Result::getRowsUpdated) .hasElements() @@ -761,7 +759,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a @@ -799,7 +796,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a @@ -838,7 +834,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a @@ -876,7 +871,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a @@ -915,7 +909,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a @@ -953,7 +946,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a @@ -991,7 +983,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a @@ -1030,7 +1021,6 @@ object DatabaseManager { a.minutesBefore = row["MINUTES_BEFORE", Int::class.java]!! a.info = row["INFO", String::class.java]!! a.enabled = row["ENABLED", Boolean::class.java]!! - a.infoOnly = row["INFO_ONLY", Boolean::class.java]!! a.publish = row["PUBLISH", Boolean::class.java]!! a diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/Announcement.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/Announcement.kt index c52fde45..a4876620 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/Announcement.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/Announcement.kt @@ -19,8 +19,7 @@ data class Announcement( val guildId: Snowflake, ) { @Serializable(with = UUIDasStringSerializer::class) - @SerialName("id") - var announcementId: UUID = UUID.randomUUID() + var id: UUID = UUID.randomUUID() private set @SerialName("subscriber_roles") @@ -52,9 +51,6 @@ data class Announcement( var enabled = true - @Deprecated(message = "Info only support is dropping in favor of a guild-wide announcement-style") - @SerialName("info_only") - var infoOnly = false var publish = false //Stuff for wizards @@ -68,14 +64,14 @@ data class Announcement( var lastEdit = System.currentTimeMillis() constructor(guildId: Snowflake, announcementId: UUID) : this(guildId) { - this.announcementId = announcementId + this.id = announcementId } companion object { fun copy(from: Announcement, copyId: Boolean = false): Announcement { val to = from.copy() if (copyId) - to.announcementId = UUID.randomUUID() + to.id = UUID.randomUUID() //Copy all the other params... to.subscriberRoleIds.addAll(from.subscriberRoleIds) @@ -89,7 +85,6 @@ data class Announcement( to.minutesBefore = from.minutesBefore to.info = from.info to.enabled = from.enabled - to.infoOnly = from.infoOnly to.publish = to.publish return to diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt new file mode 100644 index 00000000..2ad07c3d --- /dev/null +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt @@ -0,0 +1,13 @@ +package org.dreamexposure.discal.core.`object`.announcement + +import discord4j.common.util.Snowflake +import org.dreamexposure.discal.core.entities.Calendar +import org.dreamexposure.discal.core.entities.Event +import reactor.core.publisher.Flux +import java.util.concurrent.ConcurrentHashMap + +data class AnnouncementCache( + val id: Snowflake, + val calendars: ConcurrentHashMap = ConcurrentHashMap(), + val events: ConcurrentHashMap> = ConcurrentHashMap(), +) diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/wrapper/google/EventWrapper.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/wrapper/google/EventWrapper.kt index 64676039..2930727d 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/wrapper/google/EventWrapper.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/wrapper/google/EventWrapper.kt @@ -69,7 +69,7 @@ object EventWrapper { } fun getEvents(calData: CalendarData, amount: Int, start: Long): Mono> { - return GoogleAuthWrapper.getCalendarService(calData).flatMap { service: Calendar -> + return GoogleAuthWrapper.getCalendarService(calData).flatMap { service -> Mono.fromCallable { service.events() .list(calData.calendarId) @@ -87,24 +87,6 @@ object EventWrapper { }.onErrorResume { Mono.empty() } } - @Deprecated(message = "Deprecated, do not use service directly") - fun getEvents(calData: CalendarData, service: Calendar, amount: Int, start: Long): Mono> { - return Mono.fromCallable { - service.events() - .list(calData.calendarId) - .setMaxResults(amount) - .setTimeMin(DateTime(start)) - .setOrderBy("startTime") - .setSingleEvents(true) - .setShowDeleted(false) - .setQuotaUser(calData.guildId.asString()) - .execute().items - }.subscribeOn(Schedulers.boundedElastic()) - .doOnError { - LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event list(2) failure", it) - }.onErrorResume { Mono.empty() } - } - fun getEvents(calData: CalendarData, amount: Int, start: Long, end: Long): Mono> { return GoogleAuthWrapper.getCalendarService(calData).flatMap { service: Calendar -> Mono.fromCallable { diff --git a/core/src/main/resources/db/migration/V19__Remove_Info_Only.sql b/core/src/main/resources/db/migration/V19__Remove_Info_Only.sql new file mode 100644 index 00000000..354bdc4a --- /dev/null +++ b/core/src/main/resources/db/migration/V19__Remove_Info_Only.sql @@ -0,0 +1,2 @@ +ALTER TABLE ${prefix}announcements + DROP COLUMN INFO_ONLY; diff --git a/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/CreateAnnouncementEndpoint.kt b/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/CreateAnnouncementEndpoint.kt index 8b8dbf07..b6c265d6 100644 --- a/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/CreateAnnouncementEndpoint.kt +++ b/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/CreateAnnouncementEndpoint.kt @@ -59,7 +59,6 @@ class CreateAnnouncementEndpoint(val client: DiscordClient) { announcement.minutesBefore = body.optInt("minutes", 0) announcement.info = body.optString("info", "N/a") - announcement.infoOnly = body.optBoolean("info_only", false) announcement.publish = body.optBoolean("publish", false) diff --git a/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/UpdateAnnouncementEndpoint.kt b/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/UpdateAnnouncementEndpoint.kt index 443e6219..c7d5c998 100644 --- a/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/UpdateAnnouncementEndpoint.kt +++ b/server/src/main/kotlin/org/dreamexposure/discal/server/endpoints/v2/announcement/UpdateAnnouncementEndpoint.kt @@ -50,7 +50,6 @@ class UpdateAnnouncementEndpoint(val client: DiscordClient) { ann.hoursBefore = body.optInt("hours", ann.hoursBefore) ann.minutesBefore = body.optInt("hours", ann.minutesBefore) ann.info = body.optString("info", ann.info) - ann.infoOnly = body.optBoolean("info_only", ann.infoOnly) ann.enabled = body.optBoolean("enabled", ann.enabled) ann.publish = body.optBoolean("publish", ann.publish)