Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Feature/calendar #90

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ dependencies {

implementation("com.github.Softawii:curupira:v0.3.0")
implementation("com.github.Softawii:curupira:v0.3.0:all")

implementation 'com.google.api-client:google-api-client:2.0.0'
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.34.1'
implementation 'com.google.apis:google-api-services-calendar:v3-rev20220715-2.0.0'
}

tasks.register('deploy') {
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/com/softawii/capivara/config/SpringConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.softawii.capivara.config;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.calendar.Calendar;
import com.softawii.capivara.utils.CapivaraExceptionHandler;
import com.softawii.curupira.core.Curupira;
import net.dv8tion.jda.api.JDA;
Expand Down Expand Up @@ -40,6 +44,9 @@ public class SpringConfig {
@Value("${token}")
private String discordToken;

@Value("${google.calendar.api_token}")
private String googleCalendarApiToken;

public SpringConfig(Environment env) {
this.env = env;
}
Expand Down Expand Up @@ -126,4 +133,22 @@ public PlatformTransactionManager transactionManager() {
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}

@Bean
public Calendar googleCalendar() {
NetHttpTransport httpTransport;
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
} catch (Exception e) {
throw new RuntimeException("Failed to initialize GoogleNetHttpTransport", e);
}
GsonFactory factory = GsonFactory.getDefaultInstance();

Calendar service = new Calendar.Builder(httpTransport, factory, null)
.setApplicationName("Capivara")
.setGoogleClientRequestInitializer(request -> request.set("key", googleCalendarApiToken))
.build();

return service;
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/softawii/capivara/core/CalendarManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.softawii.capivara.core;

import com.softawii.capivara.entity.Calendar;
import com.softawii.capivara.exceptions.DuplicatedKeyException;
import com.softawii.capivara.services.CalendarService;
import com.softawii.capivara.threads.CalendarSubscriptionManager;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class CalendarManager {

private final Logger LOGGER = LogManager.getLogger(CalendarManager.class);
private final JDA jda;
private final CalendarService service;
private final CalendarSubscriptionManager subscriber;

public CalendarManager(JDA jda, CalendarService service, CalendarSubscriptionManager subscriber) {
this.jda = jda;
this.service = service;
this.subscriber = subscriber;
}

public void createCalendar(String googleCalendarId, String name, GuildChannelUnion channel, Role role) throws DuplicatedKeyException {
Long guildId = channel.getGuild().getIdLong();
Long channelId = channel.getIdLong();
Long roleId = role != null ? role.getIdLong() : null;
Calendar calendar = new Calendar(guildId, googleCalendarId, name, channelId, roleId);

this.service.create(calendar);
this.subscriber.subscribe(calendar);
}

public Calendar getCalendar(String name, Long guildId) {
return this.service.findByNameAndGuildId(name, guildId);
}

public List<String> getCalendarNames(Long guildId) {
return this.service.getCalendarNames(guildId);
}
}
1 change: 1 addition & 0 deletions src/main/java/com/softawii/capivara/core/DroneManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down
103 changes: 103 additions & 0 deletions src/main/java/com/softawii/capivara/entity/Calendar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.softawii.capivara.entity;

import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import java.io.Serializable;

@Entity
public class Calendar {

@EmbeddedId
private CalendarKey calendarKey;

@Embeddable
public static class CalendarKey implements Serializable {

@Column
private Long guildId;

@Column
private String calendarName;

public CalendarKey() {
}

public CalendarKey(Long guildId, String calendarName) {
this.guildId = guildId;
this.calendarName = calendarName;
}

public Long getGuildId() {
return guildId;
}

public void setGuildId(Long guildId) {
this.guildId = guildId;
}

public String getCalendarName() {
return calendarName;
}

public void setCalendarName(String calendarName) {
this.calendarName = calendarName;
}

}

public Calendar(Long guildId, String googleCalendarId, String name, Long channelId, Long roleId) {
this.calendarKey = new CalendarKey(guildId, name);
this.googleCalendarId = googleCalendarId;
this.channelId = channelId;
this.roleId = roleId;
}

@Column
private String googleCalendarId;

@Column
private Long channelId;

@Column
private Long roleId;

public Calendar() {
}

public CalendarKey getCalendarKey() {
return this.calendarKey;
}

public void setCalendarKey(CalendarKey calendarKey) {
this.calendarKey = calendarKey;
}

public String getGoogleCalendarId() {
return googleCalendarId;
}

public void setGoogleCalendarId(String googleCalendarId) {
this.googleCalendarId = googleCalendarId;
}

public Long getChannelId() {
return channelId;
}

public void setChannelId(Long channelId) {
this.channelId = channelId;
}

public Long getRoleId() {
return roleId;
}

public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package com.softawii.capivara.entity.internal;

import com.google.api.client.util.DateTime;
import com.google.api.services.calendar.model.Event;
import com.google.api.services.calendar.model.EventDateTime;
import com.softawii.capivara.entity.Calendar;
import com.softawii.capivara.services.GoogleCalendarService;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.Role;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
import java.util.stream.Collectors;

public class CalendarSubscriber {
private final Logger LOGGER = LogManager.getLogger(CalendarSubscriber.class);
private final String googleCalendarId;
private final List<Calendar> consumers;
private final GoogleCalendarService googleCalendarService;
private final HashMap<String, EventWrapper> events;
private final JDA jda;

private class EventWrapper extends TimerTask {
private static final Logger LOGGER = LogManager.getLogger(EventWrapper.class);
private Event event;
private Timer timer;

public EventWrapper(Event event) {
this.event = event;
schedule();
}

public boolean startChanged(Event event) {
return !this.event.getStart().equals(event.getStart());
}
public void setEvent(Event event) {
this.event = event;
}

private void schedule() {
EventDateTime eventStart = this.event.getStart();
EventDateTime eventEnd = this.event.getEnd();
boolean isAllDayEvent = eventStart.getDate() != null && eventEnd.getDate() != null;
DateTime dateStart;

if (isAllDayEvent) {
dateStart = eventStart.getDate();
} else {
dateStart = eventStart.getDateTime();
}

Date scheduled = new Date(dateStart.getValue());
this.timer = new Timer("EventWrapper-" + event.getId());
this.timer.schedule(this, scheduled);
LOGGER.info("Event scheduled: " + this.event.getSummary() + ", date: " + scheduled);
}

public void purge() {
if(this.timer != null) {
this.timer.cancel();
this.timer.purge();
}
}

@Override
public void run() {
LOGGER.info("Event started: " + this.event.getSummary());
EventDateTime eventStart = this.event.getStart();
boolean isAllDayEvent = eventStart.getDate() != null;
DateTime dateStart;

if (isAllDayEvent) {
dateStart = eventStart.getDate();
} else {
dateStart = eventStart.getDateTime();
}

EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setTitle(event.getSummary());
embedBuilder.setDescription(event.getDescription());
embedBuilder.addField("Hora", dateStart.toStringRfc3339(), false);

MessageEmbed embed = embedBuilder.build();
dispatchMessage(embed);
}
}

public CalendarSubscriber(String googleCalendarId, GoogleCalendarService googleCalendarService, JDA jda) {
this.googleCalendarId = googleCalendarId;
this.googleCalendarService = googleCalendarService;
this.jda = jda;
this.events = new HashMap<>();
this.consumers = new ArrayList<>();

// Forcing Get Event Info
this.update();
}

public synchronized void subscribe(Calendar consumer) {
if (!this.consumers.contains(consumer)) {
this.consumers.add(consumer);
}
}

public synchronized void unsubscribe(Calendar consumer) {
this.consumers.remove(consumer);
}

public boolean isThereAnyConsumer() {
return !this.consumers.isEmpty();
}

public synchronized void purge() {
this.events.values().forEach(EventWrapper::purge);
this.events.clear();
}

private void checkForUpdates(List<Event> events) {
// Removing events
Set<String> localKeys = new HashSet<>(this.events.keySet());
Set<String> remoteKeys = events.stream().map(Event::getId).collect(Collectors.toSet());
localKeys.removeAll(remoteKeys);

// Removing events that are not being listed
for (String key : localKeys) {
EventWrapper eventWrapper = this.events.get(key);
LOGGER.info("Event removed: " + eventWrapper.event.getSummary());
eventWrapper.purge();
this.events.remove(key);
}

// Updating events
for (Event event : events) {
String eventId = event.getId();
EventWrapper eventWrapper = this.events.get(eventId);
if (eventWrapper == null) {
this.events.put(eventId, new EventWrapper(event));
LOGGER.info("Event added: " + event.getSummary());
} else {
if(eventWrapper.startChanged(event)) {
LOGGER.info("Event updated: " + event.getSummary());
eventWrapper.purge();
this.events.put(eventId, new EventWrapper(event));
LOGGER.info("Event re-added: " + event.getSummary());
} else {
eventWrapper.setEvent(event);
LOGGER.info("Event updated (not change in hour): " + event.getSummary());
}
}
}
}

public synchronized void update() {
LOGGER.info("Updating calendar events for calendar '{}'", this.googleCalendarId);
List<Event> events = this.googleCalendarService.getEvents(this.googleCalendarId, true, true);
if (!events.isEmpty()) {
checkForUpdates(events);
}
}

protected synchronized void dispatchMessage(MessageEmbed embed) {
// TODO: bulk request
// TODO: automatic delete wrapper after dispatch?
// - maybe a list of wrappers to remove and a faster schedule?
this.consumers.forEach(calendar -> {
TextChannel textChannel = this.jda.getTextChannelById(calendar.getChannelId());
Role role = calendar.getRoleId() != null ? this.jda.getRoleById(calendar.getRoleId()) : null;
if (textChannel != null) {
MessageCreateAction messageAction = textChannel.sendMessageEmbeds(embed);
if (role != null) {
messageAction = messageAction.mentionRoles(role.getId());
}

messageAction.submit();
}
});
}
}
Loading