/* * This file is part of JuniperBot. * * JuniperBot is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * JuniperBot is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with JuniperBot. If not, see <http://www.gnu.org/licenses/>. */ package ru.juniperbot.common.worker.modules.audit.service; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.TextChannel; import org.jasypt.encryption.StringEncryptor; import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; import org.jasypt.iv.StringFixedIvGenerator; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.juniperbot.common.model.AuditActionType; import ru.juniperbot.common.persistence.entity.AuditConfig; import ru.juniperbot.common.persistence.entity.LocalMember; import ru.juniperbot.common.persistence.entity.MessageHistory; import ru.juniperbot.common.persistence.repository.MessageHistoryRepository; import ru.juniperbot.common.service.AuditConfigService; import ru.juniperbot.common.service.MemberService; import ru.juniperbot.common.worker.configuration.WorkerProperties; import ru.juniperbot.common.worker.modules.audit.model.AuditActionBuilder; import ru.juniperbot.common.worker.utils.DiscordUtils; import java.util.Date; import java.util.Objects; import static ru.juniperbot.common.worker.modules.audit.provider.MessageEditAuditForwardProvider.*; @Service public class HistoryServiceImpl implements HistoryService { @Autowired private AuditService auditService; @Autowired private AuditConfigService auditConfigService; @Autowired private MemberService memberService; @Autowired private MessageHistoryRepository historyRepository; @Autowired private WorkerProperties workerProperties; @Override @Transactional public void onMessageCreate(Message message) { if (isDisabled(message.getGuild())) { return; } MessageHistory messageHistory = createHistory(message); historyRepository.save(messageHistory); } @Override @Transactional public void onMessageUpdate(Message message) { if (isDisabled(message.getGuild())) { return; } MessageHistory history = historyRepository.findByMessageId(message.getId()); if (history == null) { history = createHistory(message); historyRepository.save(history); return; } TextChannel channel = message.getTextChannel(); String oldContent = decrypt(channel, history); String newContent = DiscordUtils.getContent(message); if (Objects.equals(oldContent, newContent)) { return; } history.setMessage(encrypt(channel, message.getId(), newContent)); history.setUpdateDate(new Date()); historyRepository.save(history); auditService.log(message.getGuild(), AuditActionType.MESSAGE_EDIT) .withUser(message.getMember()) .withChannel(channel) .withAttribute(MESSAGE_ID, message.getId()) .withAttribute(OLD_CONTENT, oldContent) .withAttribute(NEW_CONTENT, newContent) .save(); } @Override @Transactional public void onMessageDelete(TextChannel channel, String messageId) { Guild guild = channel.getGuild(); if (isDisabled(guild)) { return; } MessageHistory history = historyRepository.findByMessageId(messageId); if (history == null) { return; } historyRepository.delete(history); AuditActionBuilder builder = auditService.log(guild, AuditActionType.MESSAGE_DELETE) .withChannel(channel) .withAttribute(MESSAGE_ID, messageId) .withAttribute(OLD_CONTENT, decrypt(channel, history)); Member member = guild.getMemberById(history.getUserId()); if (member != null) { builder.withUser(member); } else { LocalMember localMember = memberService.get(guild.getIdLong(), history.getUserId()); if (localMember != null) { builder.withUser(localMember); } } builder.save(); } @Scheduled(cron = "0 0 0 * * ?") @Transactional public void runCleanUp() { runCleanUp(this.workerProperties.getAudit().getHistoryDays()); } @Override @Transactional public void runCleanUp(int durationDays) { historyRepository.deleteByCreateDateBefore(DateTime.now().minusDays(durationDays).toDate()); } private boolean isDisabled(Guild guild) { if (!workerProperties.getAudit().isHistoryEnabled()) { return true; } AuditConfig config = auditConfigService.get(guild); return config == null || !config.isEnabled(); } private MessageHistory createHistory(Message message) { MessageHistory messageHistory = new MessageHistory(); messageHistory.setUserId(message.getAuthor().getId()); messageHistory.setMessage(encrypt(message.getTextChannel(), message.getId(), DiscordUtils.getContent(message))); messageHistory.setMessageId(message.getId()); messageHistory.setCreateDate(new Date()); messageHistory.setUpdateDate(messageHistory.getCreateDate()); return messageHistory; } private String encrypt(TextChannel channel, String iv, String content) { return workerProperties.getAudit().isHistoryEncryption() ? getEncryptor(channel, iv).encrypt(content) : content; } private String decrypt(TextChannel channel, MessageHistory history) { return workerProperties.getAudit().isHistoryEncryption() ? getEncryptor(channel, history.getMessageId()).decrypt(history.getMessage()) : history.getMessage(); } private StringEncryptor getEncryptor(TextChannel channel, String iv) { StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); encryptor.setAlgorithm("PBEWithHMACSHA512AndAES_256"); encryptor.setPassword(String.format("%s:%s", channel.getGuild().getId(), channel.getId())); encryptor.setIvGenerator(new StringFixedIvGenerator(iv)); return encryptor; } }