알고리즘을 안 푼 친구를 신나게 갈궈보자
기획 의도
: 친구들과 함께 만든 ps워크스페이스에서 오늘 문제 안푼 놈들에게 꼽을 주기위해서!
등록되어 있는 사용자들에 대해, 특정 시간대 마다 오늘 하루 문제를 풀지 않은 사람들을 메시지에 담아 꼽을 줍니다.
ex) 1pm. 네! 뭐 점심먹고 얼마 안됐으니깐요! @Chlee4858 @Hyj879 님~
ex) 7pm. 식사는 맛있게 하셨을테고, 알고리즘 문제는 어떻게 되가나요 @Chlee4858 님???
ex) 1am. 잘시간이 다가와요!! 하하, 너 말고 @Chlee4858.
ex) 6am. @Chlee4858. 아웃.
시간대는 점심 후, 저녁 후, 자정 후, 동트기 직전 이 가장 괜찮은 것 같다.
: java 로 백준 크롤링 구현해놓은 것이 있어서, 이걸 가져와서 구현한다. DB는 쓰지 않도록 한다. 왜냐면 지금은 사람 수가 적기 때문이다.
: maven을 사용해 본 적이 없을 뿐더러, gradle이 훨씬 보기 편하다
: Itiviti/simple-slack-api 스타가 400개가 넘게 있는 java slack 라이브러리! 사용이 간편하다 해서 가져와보았다.
//error message
Error: LinkageError occurred while loading main class algoBot.App
java.lang.UnsupportedClassVersionError: algoBot/App has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
b. 해결) gradle에 java11 사용한다고 명시하기 gradle문서 introduction살펴보기//in build.gradle
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
gradle clean build
java -jar app/build/libs/app.jar
api-docs 읽어보기
기각 (API 가 바뀐걸 반영하지 않은 듯 함)
slack api chat.postMessage 의 tester와 reference docs를 보고 직접 요청을 보내봤다.
(Slack 봇은 그대로 사용하였다)
POST https://slack.com/api/chat.postMessage
Headers
- ContentType : application/json, application/x-www-form-urlencoded
- Authorization : Bearer {slack 봇 토큰}
Query Params
- channel : {채널ID}
- text : {보낼 메시지}
public void send() throws Exception {
//create a connection to a given URL using POST method
URL url = new URL("https://slack.com/api/chat.postMessage");
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
//Setting Headers
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httpURLConnection.setRequestProperty("Authorization", "Bearer {slack Bot 토큰}");
httpURLConnection.setRequestMethod("POST");
//Adding Request Params
Map<String, String> params = new HashMap<>();
params.put("channel", "{채널ID}");
params.put("text", "test text");
httpURLConnection.setDoOutput(true);
DataOutputStream out = new DataOutputStream(httpURLConnection.getOutputStream());
out.writeBytes(ParameterStringBuilder.getParamsString(params));
out.flush();
out.close();
//Configuring TimeOut
httpURLConnection.setConnectTimeout(5000);
httpURLConnection.setReadTimeout(5000);
//Reading the Response
BufferedReader in = new BufferedReader(
new InputStreamReader(httpURLConnection.getInputStream())
);
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
//Disconnect
httpURLConnection.disconnect();
//Print the content
System.out.println("content = " + content);
}
우선 DB는 사용하지 않는다. 따로 설치해야 하기도 하고, 사람이 많지 않아서 우선은 필요없을 듯 하다.
-> DB 도입하면 더 많은 것을 할 수 있을 듯
GET 'https://www.acmicpc.net/status'
query params
problem_id : 특정 문제만 보려면 값을 넣고, 아니면 value를 안쓰고 보내면 됨
user_id : BOJ 아이디
language_id : -1 이면 언어 선택 안한거
reseult_id : 4가 '맞았습니다!' 가 뜬 문제만 보는거
https://www.acmicpc.net/status?problem_id=&user_id=dlckdgk4858&language_id=-1&result_id=4
table\[id="status-table"]>tbody>tr
에서 td>a\[class="real-time-update show-date"]
인 요소에서 title 을 확인하면 날짜와 시간을 알 수 있다.<table class="table table-striped table-bordered" id="status-table">
...
<tbody>
...
<tr id="solution-36065509" data-can-view="1">
...
<td>
<a href="javascript:void(0);" rel="tooltip" data-placement="top" title="2021-12-06 02:42:25" data-timestamp="1638726145" class="real-time-update show-date " data-method="from-now">22시간 전</a>
</td>
</tr>
...
</tbody>
</table>
algoBot
├── App.java
├── boj
│ └── BOJClient.java
├── helper
│ └── httpHelper
│ ├── HttpConnection.java
│ ├── HttpHelper.java
│ └── ParameterStringBuilder.java
├── slack
│ ├── SlackBot.java
│ └── SlackMessage.java
└── timer
├── HowAreUToday.java
└── Scheduler.java
다른 예상되는 원인은 Timer 를 이중으로 써서 그런 듯 한데... 다음 코드를 보면 mainTimer의 schedule 함수에 dailyTimer 가 있다. 쓰레드에 대해 더 공부를 해야 풀 수 있는 문제인 것 같다.
(단순하게 타이머를 짜도 되긴 하는데... 더 테스트 해보자)
public void schedule() {
long cycle = 24*60*60*1000L;
HowAreUToday.setTodayDate();
TimerTask theFirstTask = new TimerTask() {
@Override
public void run() {
initDailySchedule();
}
};
mainTimer.scheduleAtFixedRate(theFirstTask, toDate(LocalDateTime.of(HowAreUToday.TODAY_DATE, LocalTime.of(6,0))), cycle);
}
public void initDailySchedule() {
HowAreUToday.setDailyTime();
for (LocalDateTime time : HowAreUToday.getDailyTimeList()) {
if (time.isAfter(LocalDateTime.now()))
dailyTimer.schedule(getTaskAt(time), toDate(time));
}
}
private TimerTask getTaskAt(LocalDateTime todo) {
return new TimerTask() {
@Override
public void run() {
try {
logger.info("{}에 할 일이 {}에 실행되었습니다.",
todo.format(DateTimeFormatter.ofPattern("MM월dd일 HH시mm분")),
LocalDateTime.now().format(DateTimeFormatter.ofPattern("MM월dd일 HH시mm분"))
);
List<String> beingNotSolveMembersToday = bojClient.crawlBeingNotSolveMembersToday();
SlackMessage message = new SlackMessage();
message.setContentByDailyTime(todo, beingNotSolveMembersToday);
slackBot.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
수많은 테스트....ㅠ
DB가 없다
: 현재는 모든 정보를 메모리나 파일로 저장해야한다. '며칠동안 안풀었는지' '몇일 연속 풀었는지' 등의 고도화된 정보를 보내기 위해서는 DB가 있어야 더 편할 듯 하다
Slack API 더 활용하기
: 꼽을 더 제대로 주기 위해서는 slack 채널에서 개인 DM을 보내는 등의 조치가 필요하다. 이를 위해서는 슬랙 봇의 권한을 더 열어서 더 구현을 하면 좋을 듯 하다. (재밌겠당 ㅎ)
이정도의 개선점이 당장 생각난다.
이 외에도 spring 을 도입해서 더 나은 성능을 보장한다거나, http 요청을 더 고도화해서 안정적으로 요청을 처리한다거나 하는 등의 것들이 있다.
친구들에게 꼽을 주겠다는 일념 하나로 개발한 첫 봇이다. Java 와 Gradle 의 사용법에 대해 더 알아갔고, 지금까지 Spring 으로만 개발을 해와서 겪을 수 없었던 것을 공부할 수 있었다. 특히 http 요청을 날려 페이지를 수집하는 것에 에로사항이 좀 있었는데, 이에 대해 생각해 볼 수 있어서 좋았다.
확실히 개발은 누구 괴롭힐 생각으로 하면 제일 재밌는것 같다.
백준봇의 꼽질에 정신이 번쩍듭니다!