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

feat: 구독한 구글폼 응답 최신화 및 알림 발송 #15

Open
wants to merge 5 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package greedy.greedybot.application.googleform;

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.FileInputStream;
import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public GoogleFormApiClient(final RestClient restClient) {
}

// ref: https://developers.google.com/forms/api/reference/rest/v1/forms/get
public GoogleFormInformationResponse readForm(String formId, String accessToken) {
public GoogleFormInformationResponse readForm(final String formId, final String accessToken) {
return restClient.get()
.uri(GET_FORM_ENDPOINT, formId)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
Expand All @@ -40,7 +40,7 @@ public GoogleFormInformationResponse readForm(String formId, String accessToken)
}

// ref: https://developers.google.com/forms/api/reference/rest/v1/forms.responses/list
public int readFormResponseCount(String formId, String accessToken) {
public int readFormResponseCount(final String formId, final String accessToken) {
return restClient.get()
.uri(LIST_RESPONSE_ENDPOINT, formId)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package greedy.greedybot.application.googleform;

import greedy.greedybot.domain.form.GoogleFormWatch;
import greedy.greedybot.domain.form.GoogleFormWatchRepository;
import java.util.List;
import net.dv8tion.jda.api.JDA;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class GoogleFormResponseBatch {

private static final Logger log = LoggerFactory.getLogger(GoogleFormResponseBatch.class);

private final GoogleFormWatchRepository googleFormWatchDiscordRepository;
private final GoogleFormApiClient googleFormApiClient;
private final GoogleCredential googleCredential;
private final JDA jda;

@Value("${discord.google_form_alert_channel_id}")
private String googleFormAlertChannelId;

public GoogleFormResponseBatch(final GoogleFormWatchRepository googleFormWatchDiscordRepository,
final GoogleFormApiClient googleFormApiClient,
final GoogleCredential googleCredential,
final JDA jda) {
this.googleFormWatchDiscordRepository = googleFormWatchDiscordRepository;
this.googleFormApiClient = googleFormApiClient;
this.googleCredential = googleCredential;
this.jda = jda;
}

@Scheduled(fixedDelay = 5 * 60 * 1000) // 5분마다 실행
public void fetchGoogleFormWatchResponses() {
final String accessToken = googleCredential.getAccessToken();
final List<GoogleFormWatch> googleFormWatches = googleFormWatchDiscordRepository.findAll();
for (final GoogleFormWatch googleFormWatch : googleFormWatches) {
log.info("[FETCH GOOGLE FORM RESPONSES] formId: {}", googleFormWatch.targetFormId());
final String formId = googleFormWatch.targetFormId();
final int responseCount = googleFormApiClient.readFormResponseCount(formId, accessToken);

if (googleFormWatch.hasNewResponse(responseCount)) {
updateGoogleFormWatchResponseCount(googleFormWatch, responseCount);
}
}
}

private void updateGoogleFormWatchResponseCount(final GoogleFormWatch googleFormWatch, final int responseCount) {
final int previousResponseCount = googleFormWatch.responseCount();
final GoogleFormWatch updateGoogleFormWatch = googleFormWatch.updateResponseCount(responseCount);
googleFormWatchDiscordRepository.updateGoogleFormWatch(updateGoogleFormWatch);
final int newResponseCount = responseCount - previousResponseCount;
jda.getTextChannelById(googleFormAlertChannelId).sendMessage("""
📩 %s에 %d개의 새로운 응답이 도착했어요! 총 응답 수: %d
""".formatted(updateGoogleFormWatch.targetFormTitle(), newResponseCount, responseCount)
).queue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public GoogleFormService(final GoogleCredential googleCredential,
}

// TODO: test
public EnrollFormWatchResult enrollFormWatch(String formId) {
public EnrollFormWatchResult enrollFormWatch(final String formId) {
googleFormWatchDiscordRepository.findByFormId(formId).ifPresent(googleFormWatch -> {
throw new GreedyBotException("이미 등록된 구글폼 감지기입니다");
});
Expand All @@ -35,7 +35,7 @@ public EnrollFormWatchResult enrollFormWatch(String formId) {
return new EnrollFormWatchResult(formInformationResponse.title(), responseCount);
}

public String removeFormWatch(String formId) {
public String removeFormWatch(final String formId) {
final GoogleFormWatch googleFormWatch = googleFormWatchDiscordRepository.findByFormId(formId)
.orElseThrow(() -> new GreedyBotException("해당 구글폼 감지기가 존재하지 않습니다"));
googleFormWatchDiscordRepository.deleteByFormId(formId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package greedy.greedybot.common.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulerConfiguration {
}
10 changes: 9 additions & 1 deletion src/main/java/greedy/greedybot/domain/form/GoogleFormWatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ public record GoogleFormWatch(
int responseCount,
LocalDateTime lastUpdatedTime
) {
public GoogleFormWatch(String targetFormId, String targetFormTitle, int responseCount) {
public GoogleFormWatch(final String targetFormId, final String targetFormTitle, final int responseCount) {
this(targetFormId, targetFormTitle, responseCount, LocalDateTime.now());
}

public GoogleFormWatch updateResponseCount(final int responseCount) {
return new GoogleFormWatch(targetFormId, targetFormTitle, responseCount, LocalDateTime.now());
}

public boolean hasNewResponse(final int responseCount) {
return this.responseCount() < responseCount;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package greedy.greedybot.domain.form;

import greedy.greedybot.common.exception.GreedyBotException;
import java.util.List;
import java.util.Optional;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import org.springframework.context.annotation.Lazy;
Expand Down Expand Up @@ -43,4 +44,23 @@ public Optional<GoogleFormWatch> findByFormId(final String formId) {
.findAny()
.map(message -> googleFormWatchMapper.toEntity(message.getContentDisplay()));
}

@Override
public List<GoogleFormWatch> findAll() {
return googleFormWatchChannel.getHistory().retrievePast(100).complete()
.stream()
.map(message -> googleFormWatchMapper.toEntity(message.getContentDisplay()))
.toList();
}

@Override
public void updateGoogleFormWatch(final GoogleFormWatch googleFormWatch) {
googleFormWatchChannel.getHistory().retrievePast(100).complete()
.stream()
.filter(message -> message.getContentDisplay().contains(googleFormWatch.targetFormId()))
.findAny()
.orElseThrow(() -> new GreedyBotException("존재 하지 않는 구글폼 감지기입니다"))
.editMessage(googleFormWatchMapper.toTextEntity(googleFormWatch))
.queue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,29 @@
@Component
public class GoogleFormWatchMapper {

public String toTextEntity(GoogleFormWatch googleFormWatch) {
public String toTextEntity(final GoogleFormWatch googleFormWatch) {
return Arrays.stream(googleFormWatch.getClass().getRecordComponents())
.map(component -> formatField(googleFormWatch, component))
.collect(Collectors.joining(","));
}

private String formatField(Record record, RecordComponent component) {
private String formatField(final Record record, final RecordComponent component) {
try {
Object value = component.getAccessor().invoke(record);
final Object value = component.getAccessor().invoke(record);
return component.getName() + ":" + value;
} catch (Exception e) {
throw new RuntimeException("Failed to convert record to string", e);
}
}

public GoogleFormWatch toEntity(String text) {
String targetFormId = findValueByKey(text, "targetFormId");
String targetFormTitle = findValueByKey(text, "targetFormTitle");
int responseCount = Integer.parseInt(findValueByKey(text, "responseCount"));
public GoogleFormWatch toEntity(final String text) {
final String targetFormId = findValueByKey(text, "targetFormId");
final String targetFormTitle = findValueByKey(text, "targetFormTitle");
final int responseCount = Integer.parseInt(findValueByKey(text, "responseCount"));
return new GoogleFormWatch(targetFormId, targetFormTitle, responseCount);
}

private String findValueByKey(String text, String key) {
private String findValueByKey(final String text, final String key) {
return Arrays.stream(text.split(","))
.filter(field -> field.contains(key))
.map(field -> field.split(":")[1])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package greedy.greedybot.domain.form;

import java.util.List;
import java.util.Optional;

public interface GoogleFormWatchRepository {
Expand All @@ -9,4 +10,8 @@ public interface GoogleFormWatchRepository {
void deleteByFormId(final String formId);

Optional<GoogleFormWatch> findByFormId(final String formId);

List<GoogleFormWatch> findAll();

void updateGoogleFormWatch(final GoogleFormWatch googleFormWatch);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ JDA jda() throws InterruptedException {
}

@Bean
Guild greedyGuild(JDA jda) {
Guild greedyGuild(final JDA jda) {
return jda.getGuildById(guildId);
}

@Bean
TextChannel googleFormWatchChannel(JDA jda) {
TextChannel googleFormWatchChannel(final JDA jda) {
return jda.getTextChannelById(channelId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public EnrollGoogleFormWatchCommandListener(final GoogleFormService googleFormSe
this.googleFormService = googleFormService;
}

@Override
public String getCommandName() {
return "form-add";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public RemoveGoogleFormWatchCommandListener(final GoogleFormService googleFormSe
this.googleFormService = googleFormService;
}

@Override
public String getCommandName() {
return "form-delete";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void onSlashCommandInteraction(@NotNull final SlashCommandInteractionEven
run(event);
}

private void run(final @NotNull SlashCommandInteractionEvent event) {
private void run(@NotNull final SlashCommandInteractionEvent event) {
final String commandName = event.getName();
final SlashCommandListener slashCommand = slashCommandListenersByCommandName.get(commandName);
log.info("[RECEIVED DISCORD SLASH COMMAND] : {}", commandName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

// Sample code
@Component
public class StatusCommandListener implements SlashCommandListener {

Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ spring:
application:
name: greedy-bot

main:
web-application-type: none
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것은 웹 서버 없이 spring을 실행 하는 조건 같은데
스크린샷 2025-02-27 오후 12 13 55

  1. 해당 그림에서 tomcat 관련 기능들을 들고 오지 않는 것일까요?
  2. 저희 디스코드 앱은 controller없이 작동하기에 추가한 것일까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네, 처음에도 web 의존서 없이 사용하다가 restClient를 위해 추가했었는데요.
DiscordBot은 소켓으로 연결해서 사용 되지 않는내장 톰캣을 띄우는게 비효율적이라 생각했습니다!


profiles:
active: discord