Skip to content
Merged
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
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
# Overview
PaperTrail is a free and open source, self-hostable and privacy friendly Discord bot designed to deliver structured, reliable logging across all major audit and runtime events. It hooks into Discord's audit logs to cover for most of the audit log events and for events not covered by Audit Logs, it supplements them with real-time listeners to bridge gaps in native coverage (e.g. voice state, boosts, message edits and deletions, custom triggers).
PaperTrail is a free and open source, self-hostable and privacy-friendly Discord bot designed to deliver structured, reliable logging across all major audit and runtime events. It hooks into Discord's audit logs to cover for most of the audit log events and for events not covered by Audit Logs, it supplements them with real-time listeners to bridge gaps in native coverage (e.g. voice state, boosts, message edits and deletions, custom triggers).

Key Features:

- 🔍 Full audit log integration (supports over 50+ event types) and generic support for unknown types
- 💬 Encrypted Message logging (edit, delete)
- 👤 Member activity tracking (joins, leaves, kicks, bans, updates)
- 🔊 Voice activity logging (join/leave, move)
- 🚀 Server boost tracking
- 🧱 Auto-deletion of logged messages after 30 days
- Full audit log integration (supports over 50+ event types) and generic support for unknown types
- Encrypted Message logging (edit, delete)
- Member activity tracking (joins, leaves, kicks, bans, updates)
- Voice activity logging (join/leave, move)
- Auto-deletion of logged messages after 30 days

> 📎 Get it from here: https://discord.com/discovery/applications/1381658412550590475


> [!IMPORTANT]
> PaperTrail is currently in maintenance mode. Existing bugs will be fixed, dependency updates will be provided
> but large new features will likely not be added.

# Self-Hosting Guide
> [!IMPORTANT]
> Please note that this is only for advanced users who want to self host this bot
> Please note that this is only for advanced users who want to self-host this bot
>
> A pre-hosted instance is already available: https://discord.com/discovery/applications/1381658412550590475
>
> It is recommended that you deploy the [Persistence API](https://github.com/Egg-03/PaperTrail-PersistenceAPI?tab=readme-ov-file#papertrail-persistenceapi) service before deploying the bot itself since the bot relies on the URL of the service to communicate

## v1.2.2 Legacy
Checkout the guide for hosting the legacy version [here](https://github.com/Egg-03/PaperTrailBot/blob/legacy-v1.2.2/README.md).
v1.2.2 has unresolved bugs and runs on JDA 5. It is recommended to not use that version anymore.

## V2 and above
Since v2, the project has been split into two services:
Expand Down
30 changes: 25 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.egg.papertrail</groupId>
<artifactId>paper-trail-bot</artifactId>
<version>2.0.3</version>
<version>2.1.0</version>
<name>PaperTrail</name>
<description>A simple bot for logging all user actions</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<lombok.version>1.18.42</lombok.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -41,7 +42,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
<version>3.19.0</version>
</dependency>
<dependency>
<groupId>org.jasypt</groupId>
Expand Down Expand Up @@ -75,19 +76,38 @@
<artifactId>gson</artifactId>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>

<build> <!-- use mvn clean package spring-boot:repackage for building -->
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<plugin> <!-- use mvn clean package spring-boot:repackage for building -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.5.4</version>
<version>3.5.7</version>
<configuration>
<mainClass>org.papertrail.main.FireRun</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
4 changes: 0 additions & 4 deletions src/main/java/org/papertrail/cleanup/package-info.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.papertrail.sdk.client;
package org.papertrail.commons.sdk.client;

import io.vavr.control.Either;
import org.jetbrains.annotations.NotNull;
import org.papertrail.sdk.http.HttpServiceEngine;
import org.papertrail.sdk.model.AuditLogObject;
import org.papertrail.sdk.model.ErrorObject;
import org.papertrail.utilities.EnvConfig;
import org.papertrail.commons.sdk.http.HttpServiceEngine;
import org.papertrail.commons.sdk.model.AuditLogObject;
import org.papertrail.commons.sdk.model.ErrorObject;
import org.papertrail.commons.utilities.EnvConfig;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.papertrail.sdk.client;
package org.papertrail.commons.sdk.client;

import io.vavr.control.Either;
import org.jetbrains.annotations.NotNull;
import org.papertrail.sdk.http.HttpServiceEngine;
import org.papertrail.sdk.model.ErrorObject;
import org.papertrail.sdk.model.MessageContentObject;
import org.papertrail.utilities.EnvConfig;
import org.papertrail.commons.sdk.http.HttpServiceEngine;
import org.papertrail.commons.sdk.model.ErrorObject;
import org.papertrail.commons.sdk.model.MessageContentObject;
import org.papertrail.commons.utilities.EnvConfig;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.papertrail.sdk.client;
package org.papertrail.commons.sdk.client;

import io.vavr.control.Either;
import org.jetbrains.annotations.NotNull;
import org.papertrail.sdk.http.HttpServiceEngine;
import org.papertrail.sdk.model.ErrorObject;
import org.papertrail.sdk.model.MessageLogObject;
import org.papertrail.utilities.EnvConfig;
import org.papertrail.commons.sdk.http.HttpServiceEngine;
import org.papertrail.commons.sdk.model.ErrorObject;
import org.papertrail.commons.sdk.model.MessageLogObject;
import org.papertrail.commons.utilities.EnvConfig;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package org.papertrail.sdk.http;
package org.papertrail.commons.sdk.http;

import io.vavr.control.Either;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.papertrail.sdk.model.ErrorObject;
import org.papertrail.commons.sdk.model.ErrorObject;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestClient;
import org.tinylog.Logger;

import java.net.URI;
import java.time.Instant;

@Slf4j
public class HttpServiceEngine {

private final RestClient client;
Expand Down Expand Up @@ -43,15 +44,15 @@ public <S> Either<ErrorObject, S> makeRequest (

return Either.right(body);
} catch (HttpClientErrorException e) {
Logger.trace("Client error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
log.trace("Client error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
ErrorObject error = e.getResponseBodyAs(ErrorObject.class);
return Either.left(error);
} catch (HttpServerErrorException e) {
Logger.error("Server error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
log.error("Server error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
ErrorObject error = e.getResponseBodyAs(ErrorObject.class);
return Either.left(error);
} catch (ResourceAccessException e) {
Logger.error("Resource access error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
log.error("Resource access error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
ErrorObject error = new ErrorObject(503, "API Unreachable", e.getMessage(), Instant.now().toString(), url);
return Either.left(error);
}
Expand All @@ -76,15 +77,15 @@ public <S> Either<ErrorObject, S> makeRequestWithBody (
return Either.right(body);

} catch (HttpClientErrorException e) {
Logger.trace("Client error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
log.trace("Client error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
ErrorObject error = e.getResponseBodyAs(ErrorObject.class);
return Either.left(error);
} catch (HttpServerErrorException e) {
Logger.error("Server error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
log.error("Server error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
ErrorObject error = e.getResponseBodyAs(ErrorObject.class);
return Either.left(error);
} catch (ResourceAccessException e) {
Logger.error("Resource access error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
log.error("Resource access error when calling {} {}: {}", httpMethod, url, e.getMessage(), e);
ErrorObject error = new ErrorObject(503, "API Unreachable", e.getMessage(), Instant.now().toString(), url);
return Either.left(error);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.papertrail.sdk.model;
package org.papertrail.commons.sdk.model;

public record AuditLogObject(String guildId, String channelId) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.papertrail.sdk.model;
package org.papertrail.commons.sdk.model;

public record ErrorObject(
int status,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.papertrail.sdk.model;
package org.papertrail.commons.sdk.model;

public record MessageContentObject(String messageId, String messageContent, String authorId) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.papertrail.sdk.model;
package org.papertrail.commons.sdk.model;

public record MessageLogObject(String guildId, String channelId) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
* Embedded SDK for resolving calls to the PaperTrail PersistenceAPI
*/
package org.papertrail.sdk;
package org.papertrail.commons.sdk;
43 changes: 43 additions & 0 deletions src/main/java/org/papertrail/commons/utilities/ColorFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.papertrail.commons.utilities;

import lombok.experimental.UtilityClass;

import java.util.IllegalFormatException;
import java.util.Map;

@UtilityClass
public class ColorFormatter {

public static String formatToHex(Object colorValue) {

if(String.valueOf(colorValue).equals("null")) {
return "N/A";
}

try {
int color = Integer.parseInt(String.valueOf(colorValue));
return String.format("#%06X", color);
} catch (NumberFormatException e ) {
return "No Parsable Color Integer Detected";
} catch (IllegalFormatException e) {
return "Cannot parse color to hex";
}
}

// gradient is returned as a hash map in the following structure
// {"primary_color" = 123456789, "secondary_color" = 123456789, "tertiary_color" = 123456789}
public static String formatGradientColorSystemToHex(Object gradientValue) {

if(gradientValue instanceof Map<?, ?> colorMap){

String primaryColor = "Primary Color: "+ formatToHex(colorMap.get("primary_color"));
String secondaryColor = "Secondary Color: "+ formatToHex(colorMap.get("secondary_color"));
String tertiaryColor = "Tertiary Color: "+ formatToHex(colorMap.get("tertiary_color"));

return primaryColor+System.lineSeparator()+secondaryColor+System.lineSeparator()+tertiaryColor;
}

return "N/A";

}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package org.papertrail.utilities;
package org.papertrail.commons.utilities;

import lombok.experimental.UtilityClass;

import java.time.Duration;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

@UtilityClass
public class DurationFormatter {

private DurationFormatter() {
throw new IllegalStateException("Utility Class");
}


public static String formatSeconds(Object seconds) {
if (seconds == null) {
return "ERROR: Value Returned Null";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package org.papertrail.utilities;
package org.papertrail.commons.utilities;

import io.github.cdimascio.dotenv.Dotenv;
import lombok.experimental.UtilityClass;

@UtilityClass
public class EnvConfig {

private static final Dotenv dotenv = Dotenv.configure().ignoreIfMalformed().ignoreIfMissing().load();

private EnvConfig() {
throw new IllegalStateException("Utility Class");
}

public static String get(String key) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.papertrail.utilities;
package org.papertrail.commons.utilities;

import lombok.experimental.UtilityClass;

import java.util.Map;

@UtilityClass
public class GuildSystemChannelFlagResolver {

private static final Map<Long, String> SYSTEM_CHANNEL_FLAG_MAP = Map.ofEntries(
Expand All @@ -12,11 +15,7 @@ public class GuildSystemChannelFlagResolver {
Map.entry(1L << 4, "Suppress role subscription purchase and renewal notifications"),
Map.entry(1L << 5, "Hide role subscription sticker reply buttons")
);

private GuildSystemChannelFlagResolver() {
throw new IllegalStateException("Utility Class");
}


private static String parseFlags(long bitfield){
StringBuilder flags = new StringBuilder();
for(Map.Entry<Long, String> entry: SYSTEM_CHANNEL_FLAG_MAP.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package org.papertrail.utilities;
package org.papertrail.commons.utilities;

import lombok.experimental.UtilityClass;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.tinylog.Logger;

@UtilityClass
public class MessageEncryption {

private static final String SECRET = EnvConfig.get("MESSAGE_SECRET");
private static final String ALGORITHM = "PBEWITHSHA256AND256BITAES-CBC-BC";

private MessageEncryption() {
throw new IllegalStateException("UtilityClass");
}


private static StandardPBEStringEncryptor getEncryptor() {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(SECRET);
Expand Down
Loading
Loading