Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
803b763198 | |||
fec32230fe | |||
68591c90c9 | |||
5133ab688f | |||
36673af4d0 | |||
3038e14b81 | |||
2920a42d76 | |||
c350138caa | |||
cf932e2d90 | |||
6b6958640f | |||
e3b6507a6c | |||
1b482b93e2 | |||
767646feae | |||
c28694c878 | |||
18ce6548f6 | |||
b41505a2b6 | |||
091bb8ac4e | |||
d6f8e2cbcf | |||
9c8fec5fd6 | |||
2c08a08003 | |||
1bcdae67f2 | |||
2a5351484a | |||
982ff08f11 | |||
05cc42b240 |
@ -16,6 +16,9 @@ import oshi.hardware.HardwareAbstractionLayer;
|
||||
import oshi.software.os.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -85,14 +88,21 @@ public final class LicenseExample {
|
||||
JsonElement description = json.get("description");
|
||||
JsonElement ownerSnowflake = json.get("ownerSnowflake");
|
||||
JsonElement ownerName = json.get("ownerName");
|
||||
JsonElement duration = json.get("duration");
|
||||
|
||||
// Parsing the expiration date if we have one
|
||||
JsonElement expires = json.get("expires");
|
||||
Date expiresDate = null;
|
||||
if (!expires.isJsonNull()) {
|
||||
OffsetDateTime offsetDateTime = OffsetDateTime.parse(expires.getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
||||
expiresDate = Date.from(offsetDateTime.toInstant());
|
||||
}
|
||||
|
||||
// Return the license response
|
||||
return new LicenseResponse(200, null,
|
||||
description.isJsonNull() ? null : description.getAsString(),
|
||||
ownerSnowflake.isJsonNull() ? -1 : ownerSnowflake.getAsLong(),
|
||||
ownerName.isJsonNull() ? null : ownerName.getAsString(),
|
||||
duration.isJsonNull() ? -1 : duration.getAsLong()
|
||||
expires.isJsonNull() ? null : expiresDate
|
||||
);
|
||||
} else {
|
||||
ResponseBody errorBody = response.body(); // Get the error body
|
||||
@ -177,12 +187,9 @@ public final class LicenseExample {
|
||||
private String ownerName;
|
||||
|
||||
/**
|
||||
* The duration of the license, present if valid.
|
||||
* <p>
|
||||
* If -1, the license will be permanent.
|
||||
* </p>
|
||||
* The optional expiration {@link Date} of the license.
|
||||
*/
|
||||
private long duration;
|
||||
private Date expires;
|
||||
|
||||
public LicenseResponse(long status, @NonNull String error) {
|
||||
this.status = status;
|
||||
@ -204,7 +211,7 @@ public final class LicenseExample {
|
||||
* @return true if permanent, otherwise false
|
||||
*/
|
||||
public boolean isPermanent() {
|
||||
return duration == -1;
|
||||
return expires == null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package me.braydon.example;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@ -22,9 +20,8 @@ public final class Main {
|
||||
}
|
||||
if (response.isPermanent()) { // License is permanent
|
||||
System.out.println("Your license is permanent");
|
||||
} else { // License has a duration
|
||||
long durationSeconds = TimeUnit.SECONDS.toMillis(response.getDuration()); // The duration in seconds
|
||||
System.out.println("Your license will expire in " + durationSeconds + " seconds");
|
||||
} else { // License has an expiration date
|
||||
System.out.printf("Your license will expire at: %s%n", response.getExpires().toInstant());
|
||||
}
|
||||
}
|
||||
}
|
66
README.md
66
README.md
@ -1,3 +1,67 @@
|
||||
# LicenseServer
|
||||
|
||||
A simple open-source licensing server for your products.
|
||||
A simple open-source licensing server for your products.
|
||||
|
||||
## Discord Preview
|
||||
|
||||
![License Global Log](https://cdn.rainnny.club/SagsCD0I.png)
|
||||
![License Owner Log](https://cdn.rainnny.club/JZdFxTCy.png)
|
||||
![License Owner Lookup](https://cdn.rainnny.club/EU0g1iLZ.png)
|
||||
|
||||
## API Reference
|
||||
|
||||
### Check License
|
||||
|
||||
```http
|
||||
POST /check
|
||||
```
|
||||
|
||||
#### Body
|
||||
|
||||
| Key | Type | Description |
|
||||
|:----------|:---------|:-----------------------------------------------|
|
||||
| `key` | `string` | **Required**. Your license key |
|
||||
| `product` | `string` | **Required**. The product the license is for |
|
||||
| `hwid` | `string` | **Required**. The hardware id of the requester |
|
||||
|
||||
#### Response
|
||||
|
||||
##### Error
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Error message"
|
||||
}
|
||||
```
|
||||
|
||||
##### Success
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Testing",
|
||||
"ownerSnowflake": 504147739131641857,
|
||||
"ownerName": "Braydon#2712",
|
||||
"expires": "2023-06-02T06:00:47.270+00:00"
|
||||
}
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker run -d -p 7500:7500 -v "$(pwd)/data/application.yml:/usr/local/app/application.yml" git.rainnny.club/rainnny/licenseserver:latest
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```yml
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
image: git.rainnny.club/rainnny/licenseserver:latest
|
||||
volumes:
|
||||
- ./data/application.yml:/usr/local/app/application.yml
|
||||
ports:
|
||||
- "7500:7500"
|
||||
```
|
@ -1,8 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
image: git.rainnny.club/rainnny/licenseserver:latest
|
||||
volumes:
|
||||
- ./data:/usr/local/app
|
||||
ports:
|
||||
- "7500:7500"
|
10
pom.xml
10
pom.xml
@ -12,7 +12,7 @@
|
||||
|
||||
<groupId>me.braydon</groupId>
|
||||
<artifactId>LicenseServer</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.0.3</version>
|
||||
<description>A simple open-source licensing server for your products.</description>
|
||||
|
||||
<properties>
|
||||
@ -92,6 +92,14 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Guava -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>32.0.0-jre</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
@ -61,20 +61,33 @@ public final class LicenseController {
|
||||
if (IPUtils.getIpType(ip) == -1) {
|
||||
throw new APIException(HttpStatus.BAD_REQUEST, "Invalid IP address");
|
||||
}
|
||||
// Ensure the HWID is valid
|
||||
// TODO: improve :)
|
||||
String hwidString = hwid.getAsString();
|
||||
boolean invalidHwid = true;
|
||||
if (hwidString.contains("-")) {
|
||||
int segments = hwidString.substring(0, hwidString.lastIndexOf("-")).split("-").length;
|
||||
if (segments == 4) {
|
||||
invalidHwid = false;
|
||||
}
|
||||
}
|
||||
if (invalidHwid) {
|
||||
throw new APIException(HttpStatus.BAD_REQUEST, "Invalid HWID");
|
||||
}
|
||||
|
||||
// Check the license
|
||||
License license = service.check(
|
||||
key.getAsString(),
|
||||
product.getAsString(),
|
||||
ip,
|
||||
hwid.getAsString()
|
||||
hwidString
|
||||
);
|
||||
// Return OK with the license DTO
|
||||
return ResponseEntity.ok(new LicenseDTO(
|
||||
license.getDescription(),
|
||||
license.getOwnerSnowflake(),
|
||||
license.getOwnerName(),
|
||||
license.getDuration()
|
||||
license.getExpires()
|
||||
));
|
||||
} catch (APIException ex) { // Handle the exception
|
||||
return ResponseEntity.status(ex.getStatus())
|
||||
|
@ -5,6 +5,8 @@ import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import me.braydon.license.model.License;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A data transfer object for a {@link License}.
|
||||
*
|
||||
@ -34,10 +36,7 @@ public class LicenseDTO {
|
||||
private String ownerName;
|
||||
|
||||
/**
|
||||
* The duration that this licensee is valid for.
|
||||
* <p>
|
||||
* If -1, the license will be permanent.
|
||||
* </p>
|
||||
* The optional expiration {@link Date} of this license.
|
||||
*/
|
||||
private long duration;
|
||||
private Date expires;
|
||||
}
|
||||
|
@ -80,12 +80,9 @@ public class License {
|
||||
private int hwidLimit;
|
||||
|
||||
/**
|
||||
* The duration that this licensee is valid for.
|
||||
* <p>
|
||||
* If -1, the license will be permanent.
|
||||
* </p>
|
||||
* The optional expiration {@link Date} of this license.
|
||||
*/
|
||||
private long duration;
|
||||
private Date expires;
|
||||
|
||||
/**
|
||||
* The {@link Date} this license was last used.
|
||||
@ -97,6 +94,18 @@ public class License {
|
||||
*/
|
||||
@NonNull private Date created;
|
||||
|
||||
/**
|
||||
* Check if the Discord user
|
||||
* with the given snowflake
|
||||
* owns this license.
|
||||
*
|
||||
* @param snowflake the snowflake
|
||||
* @return true if owns, otherwise false
|
||||
*/
|
||||
public boolean isOwner(long snowflake) {
|
||||
return ownerSnowflake == snowflake;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this license has expired.
|
||||
* <p>
|
||||
@ -113,7 +122,7 @@ public class License {
|
||||
return false;
|
||||
}
|
||||
// Check if the license has expired
|
||||
return System.currentTimeMillis() - created.getTime() >= duration;
|
||||
return expires.before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,7 +131,7 @@ public class License {
|
||||
* @return true if permanent, otherwise false
|
||||
*/
|
||||
public boolean isPermanent() {
|
||||
return duration == -1L;
|
||||
return expires == null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,38 +1,78 @@
|
||||
package me.braydon.license.service;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import jakarta.annotation.Nonnull;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.braydon.license.common.MiscUtils;
|
||||
import me.braydon.license.common.TimeUtils;
|
||||
import me.braydon.license.model.License;
|
||||
import me.braydon.license.repository.LicenseRepository;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.emoji.Emoji;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.exceptions.ErrorResponseException;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
import net.dv8tion.jda.api.requests.ErrorResponse;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.info.BuildProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Braydon
|
||||
*/
|
||||
@Service
|
||||
@Slf4j(topic = "Discord")
|
||||
public final class DiscordService {
|
||||
private static final String CLEAR_IPS_BUTTON_ID = "clearIps";
|
||||
private static final String CLEAR_HWIDS_BUTTON_ID = "clearHwids";
|
||||
|
||||
/**
|
||||
* The {@link LicenseRepository} to use.
|
||||
*/
|
||||
@Nonnull private final LicenseRepository licenseRepository;
|
||||
|
||||
/**
|
||||
* The version of this Springboot application.
|
||||
*/
|
||||
@NonNull private final String applicationVersion;
|
||||
|
||||
/**
|
||||
* The salt to use for hashing license keys.
|
||||
*/
|
||||
@Value("${salts.licenses}")
|
||||
@NonNull private String licensesSalt;
|
||||
|
||||
/**
|
||||
* The name of this Springboot application.
|
||||
*/
|
||||
@Value("${spring.application.name}")
|
||||
@NonNull private String applicationName;
|
||||
|
||||
/**
|
||||
* The token to the Discord bot.
|
||||
*/
|
||||
@ -69,13 +109,39 @@ public final class DiscordService {
|
||||
@Value("${discord.logs.expired}") @Getter
|
||||
private boolean logHwidLimitExceeded;
|
||||
|
||||
/**
|
||||
* Should new IPs be sent to the license owner?
|
||||
*/
|
||||
@Value("${discord.owner-logs.newIp}") @Getter
|
||||
private boolean logNewIpsToOwner;
|
||||
|
||||
/**
|
||||
* Should new HWIDs be sent to the license owner?
|
||||
*/
|
||||
@Value("${discord.owner-logs.newHwid}") @Getter
|
||||
private boolean logNewHwidsToOwner;
|
||||
|
||||
/**
|
||||
* The {@link JDA} instance of the bot.
|
||||
*/
|
||||
private JDA jda;
|
||||
|
||||
/**
|
||||
* Cached licenses for messages.
|
||||
* <p>
|
||||
* When a license is looked up by it's owner, the
|
||||
* response message is cached (key is the message snowflake)
|
||||
* for 5 minutes. This is so we're able to get the message
|
||||
* an action was performed on, as well as action timeouts.
|
||||
* </p>
|
||||
*/
|
||||
private final Cache<Long, License> cachedLicenses = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(5L, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
@Autowired
|
||||
public DiscordService(@NonNull BuildProperties buildProperties) {
|
||||
public DiscordService(@NonNull LicenseRepository licenseRepository, @NonNull BuildProperties buildProperties) {
|
||||
this.licenseRepository = licenseRepository;
|
||||
this.applicationVersion = buildProperties.getVersion();
|
||||
}
|
||||
|
||||
@ -94,6 +160,7 @@ public final class DiscordService {
|
||||
GatewayIntent.GUILD_MEMBERS
|
||||
).setStatus(OnlineStatus.DO_NOT_DISTURB)
|
||||
.setActivity(Activity.watching("your licenses"))
|
||||
.addEventListeners(new EventHandler())
|
||||
.build();
|
||||
jda.awaitReady(); // Await JDA to be ready
|
||||
|
||||
@ -101,6 +168,13 @@ public final class DiscordService {
|
||||
log.info("Logged into {} in {}ms",
|
||||
jda.getSelfUser().getAsTag(), System.currentTimeMillis() - before
|
||||
);
|
||||
|
||||
// Registering slash commands
|
||||
jda.updateCommands().addCommands(
|
||||
Commands.slash("license", "Manage one of your licenses")
|
||||
.addOption(OptionType.STRING, "key", "The license key", true)
|
||||
.addOption(OptionType.STRING, "product", "The product the license is for", true)
|
||||
).queue();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,9 +199,46 @@ public final class DiscordService {
|
||||
throw new IllegalArgumentException("Log channel %s wasn't found".formatted(logsChannel));
|
||||
}
|
||||
// Send the log
|
||||
textChannel.sendMessageEmbeds(embed.setFooter("%s v%s - %s".formatted(
|
||||
applicationName, applicationVersion, TimeUtils.dateTime()
|
||||
)).build()).queue();
|
||||
textChannel.sendMessageEmbeds(buildEmbed(embed)).queue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an embed to the owner
|
||||
* of the given license.
|
||||
*
|
||||
* @param license the license
|
||||
* @param embed the embed to send
|
||||
* @see License for license
|
||||
* @see EmbedBuilder for embed
|
||||
*/
|
||||
public void sendOwnerLog(@NonNull License license, @NonNull EmbedBuilder embed) {
|
||||
// JDA must be ready to send logs
|
||||
if (!isReady()) {
|
||||
return;
|
||||
}
|
||||
// We need an owner for the license
|
||||
if (license.getOwnerSnowflake() <= 0L) {
|
||||
return;
|
||||
}
|
||||
// Lookup the owner of the license
|
||||
jda.retrieveUserById(license.getOwnerSnowflake()).queue(owner -> {
|
||||
if (owner == null) { // Couldn't locate the owner of the license
|
||||
return;
|
||||
}
|
||||
owner.openPrivateChannel().queue(channel -> {
|
||||
channel.sendMessageEmbeds(buildEmbed(embed)).queue(null, ex -> {
|
||||
// Ignore the ex if the owner has priv msgs turned off, we don't care
|
||||
if (((ErrorResponseException) ex).getErrorResponse() != ErrorResponse.CANNOT_SEND_TO_USER) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, ex -> {
|
||||
// Ignore the ex if the owner isn't found, we don't care
|
||||
if (((ErrorResponseException) ex).getErrorResponse() != ErrorResponse.UNKNOWN_USER) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,4 +249,136 @@ public final class DiscordService {
|
||||
public boolean isReady() {
|
||||
return jda != null && (jda.getStatus() == JDA.Status.CONNECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the given embed.
|
||||
*
|
||||
* @param embedBuilder the embed builder
|
||||
* @return the built embed
|
||||
*/
|
||||
@NonNull
|
||||
private MessageEmbed buildEmbed(@NonNull EmbedBuilder embedBuilder) {
|
||||
return embedBuilder.setFooter("%s v%s - %s".formatted(
|
||||
applicationName, applicationVersion, TimeUtils.dateTime()
|
||||
)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* The event handler for the bot.
|
||||
*/
|
||||
public class EventHandler extends ListenerAdapter {
|
||||
@Override
|
||||
public void onSlashCommandInteraction(@NonNull SlashCommandInteractionEvent event) {
|
||||
User user = event.getUser(); // The command executor
|
||||
|
||||
// Handle the license command
|
||||
if (event.getName().equals("license")) {
|
||||
String key = Objects.requireNonNull(event.getOption("key")).getAsString();
|
||||
String product = Objects.requireNonNull(event.getOption("product")).getAsString();
|
||||
event.deferReply(true).queue(); // Send thinking...
|
||||
|
||||
// License lookup
|
||||
try {
|
||||
Optional<License> optionalLicense = licenseRepository.getLicense(BCrypt.hashpw(key, licensesSalt), product);
|
||||
if (optionalLicense.isEmpty() // License not found or owned by someone else
|
||||
|| (!optionalLicense.get().isOwner(user.getIdLong()))) {
|
||||
event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License not found")
|
||||
.setDescription("Could not locate the license you were looking for")
|
||||
)).queue(); // Send the error message
|
||||
return;
|
||||
}
|
||||
License license = optionalLicense.get(); // The found license
|
||||
String obfuscateKey = MiscUtils.obfuscateKey(key); // Obfuscate the key
|
||||
long expires = license.isPermanent() ? -1L : license.getExpires().getTime() / 1000L;
|
||||
long lastUsed = license.getLastUsed() == null ? -1L : license.getExpires().getTime() / 1000L;
|
||||
event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder()
|
||||
.setColor(Color.BLUE)
|
||||
.setTitle("Your License")
|
||||
.addField("License", "`" + obfuscateKey + "`", true)
|
||||
.addField("Product", license.getProduct(), true)
|
||||
.addField("Description", license.getDescription(), true)
|
||||
.addField("Expiration",
|
||||
expires == -1L ? "Never" : "<t:" + expires + ":R>",
|
||||
true
|
||||
)
|
||||
.addField("Uses", String.valueOf(license.getUses()), true)
|
||||
.addField("Last Used",
|
||||
lastUsed == -1L ? "Never" : "<t:" + lastUsed + ":R>",
|
||||
true
|
||||
)
|
||||
.addField("IPs",
|
||||
license.getIps().size() + "/" + license.getIpLimit(),
|
||||
true
|
||||
)
|
||||
.addField("HWIDs",
|
||||
license.getHwids().size() + "/" + license.getHwidLimit(),
|
||||
true
|
||||
)
|
||||
.addField("Created",
|
||||
"<t:" + (license.getCreated().getTime() / 1000L) + ":R>",
|
||||
true
|
||||
)
|
||||
)).addActionRow( // Buttons
|
||||
Button.danger(CLEAR_IPS_BUTTON_ID, "Clear IPs")
|
||||
.withEmoji(Emoji.fromUnicode("🗑️")),
|
||||
Button.danger(CLEAR_HWIDS_BUTTON_ID, "Clear HWIDs")
|
||||
.withEmoji(Emoji.fromUnicode("🗑️"))
|
||||
).queue(message -> cachedLicenses.put(message.getIdLong(), license)); // Cache the license for the message
|
||||
} catch (Exception ex) {
|
||||
event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("Lookup Failed")
|
||||
.setDescription("More information has been logged")
|
||||
)).queue(); // Send the error message
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonInteraction(@NonNull ButtonInteractionEvent event) {
|
||||
User user = event.getUser(); // The user who clicked the button
|
||||
String componentId = event.getComponentId(); // The button id
|
||||
|
||||
// License Actions
|
||||
boolean clearIps = componentId.equals(CLEAR_IPS_BUTTON_ID);
|
||||
boolean clearHwids = componentId.equals(CLEAR_HWIDS_BUTTON_ID);
|
||||
if (clearIps || clearHwids) {
|
||||
event.deferReply(true).queue(); // Send thinking...
|
||||
License license = cachedLicenses.getIfPresent(event.getMessageIdLong()); // Get the cached license
|
||||
if (license == null || (!license.isOwner(user.getIdLong()))) { // License not found or owned by someone else
|
||||
event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License Action Failed")
|
||||
.setDescription("The license couldn't be found or the action timed out")
|
||||
)).queue(); // Send the error message
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Clear IPs
|
||||
if (clearIps) {
|
||||
license.setIps(new HashSet<>());
|
||||
}
|
||||
// Clear HWIDs
|
||||
if (clearHwids) {
|
||||
license.setHwids(new HashSet<>());
|
||||
}
|
||||
licenseRepository.save(license); // Save the license
|
||||
event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder()
|
||||
.setColor(Color.GREEN)
|
||||
.setTitle("Cleared " + (clearIps ? "IP" : "HWID") + "s")
|
||||
)).queue(); // Inform action success
|
||||
} catch (Exception ex) {
|
||||
event.getHook().sendMessageEmbeds(buildEmbed(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License Action Failed")
|
||||
.setDescription("More information has been logged")
|
||||
)).queue(); // Send the error message
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -63,12 +63,12 @@ public final class LicenseService {
|
||||
* @param ownerName the optional owner name of the license
|
||||
* @param ipLimit the IP limit of the license
|
||||
* @param hwidLimit the HWID limit of the license
|
||||
* @param duration the duration of the license, -1 for permanent
|
||||
* @param expires the optional expiration date of the license
|
||||
* @return the created license
|
||||
* @see License for license
|
||||
*/
|
||||
public License create(@NonNull String key, @NonNull String product, String description, long ownerSnowflake,
|
||||
String ownerName, int ipLimit, int hwidLimit, long duration) {
|
||||
String ownerName, int ipLimit, int hwidLimit, Date expires) {
|
||||
// Create the new license
|
||||
License license = new License();
|
||||
license.setKey(BCrypt.hashpw(key, licensesSalt)); // Hash the key
|
||||
@ -80,7 +80,7 @@ public final class LicenseService {
|
||||
license.setHwids(new HashSet<>());
|
||||
license.setIpLimit(ipLimit); // Use the given IP limit
|
||||
license.setHwidLimit(hwidLimit); // Use the given HWID limit
|
||||
license.setDuration(duration);
|
||||
license.setExpires(expires);
|
||||
license.setCreated(new Date());
|
||||
repository.insert(license); // Insert the newly created license
|
||||
return license;
|
||||
@ -107,12 +107,13 @@ public final class LicenseService {
|
||||
}
|
||||
License license = optionalLicense.get(); // The license found
|
||||
String hashedIp = BCrypt.hashpw(ip, ipsSalt); // Hash the IP
|
||||
String obfuscateKey = MiscUtils.obfuscateKey(key); // Obfuscate the key
|
||||
boolean newIp = !license.getIps().contains(hashedIp); // Is the IP new?
|
||||
boolean newHwid = !license.getHwids().contains(hwid); // Is the HWID new?
|
||||
|
||||
// Log the license being used, if enabled
|
||||
if (discordService.isLogUses()) {
|
||||
// god i hate sending discord embeds, it's so big and ugly :(
|
||||
boolean newIp = !license.getIps().contains(hashedIp); // Is the IP new?
|
||||
boolean newHwid = !license.getHwids().contains(hwid); // Is the HWID new?
|
||||
|
||||
// Constructing tags
|
||||
StringBuilder tags = new StringBuilder();
|
||||
@ -125,22 +126,15 @@ public final class LicenseService {
|
||||
}
|
||||
tags.append("HWID");
|
||||
}
|
||||
long expirationDate = (license.getCreated().getTime() + license.getDuration()) / 1000L;
|
||||
long expires = license.isPermanent() ? -1L : license.getExpires().getTime() / 1000L;
|
||||
int ipCount = license.getIps().size();
|
||||
int hwidCount = license.getHwids().size();
|
||||
discordService.sendLog(new EmbedBuilder()
|
||||
.setColor(Color.BLUE)
|
||||
.setTitle("License Used" + (!tags.isEmpty() ? " (" + tags + ")" : ""))
|
||||
.addField("License",
|
||||
"`" + MiscUtils.obfuscateKey(key) + "`",
|
||||
true
|
||||
)
|
||||
.addField("Product",
|
||||
license.getProduct(),
|
||||
true
|
||||
)
|
||||
.addField("Description",
|
||||
license.getDescription(),
|
||||
true
|
||||
)
|
||||
.addField("License", "`" + obfuscateKey + "`", true)
|
||||
.addField("Product", license.getProduct(), true)
|
||||
.addField("Description", license.getDescription(), true)
|
||||
.addField("Owner ID",
|
||||
license.getOwnerSnowflake() <= 0L ? "N/A" : String.valueOf(license.getOwnerSnowflake()),
|
||||
true
|
||||
@ -149,24 +143,18 @@ public final class LicenseService {
|
||||
license.getOwnerName() == null ? "N/A" : license.getOwnerName(),
|
||||
true
|
||||
)
|
||||
.addField("Expires",
|
||||
license.isPermanent() ? "Never" : "<t:" + expirationDate + ":R>",
|
||||
.addField("Expiration",
|
||||
expires == -1L ? "Never" : "<t:" + expires + ":R>",
|
||||
true
|
||||
)
|
||||
.addField("IP",
|
||||
ip,
|
||||
true
|
||||
)
|
||||
.addField("HWID",
|
||||
"```" + hwid + "```",
|
||||
false
|
||||
)
|
||||
.addField("IP", ip, true)
|
||||
.addField("HWID", "```" + hwid + "```", false)
|
||||
.addField("IPs",
|
||||
license.getIps().size() + "/" + license.getIpLimit(),
|
||||
(newIp ? ipCount + 1 : ipCount) + "/" + license.getIpLimit(),
|
||||
true
|
||||
)
|
||||
.addField("HWIDs",
|
||||
license.getHwids().size() + "/" + license.getHwidLimit(),
|
||||
(newHwid ? hwidCount + 1 : hwidCount) + "/" + license.getHwidLimit(),
|
||||
true
|
||||
)
|
||||
);
|
||||
@ -178,7 +166,7 @@ public final class LicenseService {
|
||||
discordService.sendLog(new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License Expired")
|
||||
.setDescription("License `%s` is expired".formatted(MiscUtils.obfuscateKey(key)))
|
||||
.setDescription("License `%s` is expired".formatted(obfuscateKey))
|
||||
);
|
||||
}
|
||||
throw new LicenseExpiredException();
|
||||
@ -186,6 +174,31 @@ public final class LicenseService {
|
||||
try {
|
||||
license.use(hashedIp, hwid); // Use the license
|
||||
repository.save(license); // Save the used license
|
||||
|
||||
// Sending new IP log to the license owner
|
||||
if (newIp && discordService.isLogNewIpsToOwner()) {
|
||||
discordService.sendOwnerLog(license, new EmbedBuilder()
|
||||
.setColor(0xF2781B)
|
||||
.setTitle("New IP")
|
||||
.setDescription("One of your licenses has been used on a new IP:")
|
||||
.addField("License", "`" + obfuscateKey + "`", true)
|
||||
.addField("Product", license.getProduct(), true)
|
||||
.addField("IP", "```" + ip + "```", false)
|
||||
);
|
||||
}
|
||||
// Sending new HWID log to the license owner
|
||||
if (newHwid && discordService.isLogNewHwidsToOwner()) {
|
||||
discordService.sendOwnerLog(license, new EmbedBuilder()
|
||||
.setColor(0xF2781B)
|
||||
.setTitle("New HWID")
|
||||
.setDescription("One of your licenses has been used on a new HWID:")
|
||||
.addField("License", "`" + obfuscateKey + "`", true)
|
||||
.addField("Product", license.getProduct(), true)
|
||||
.addField("HWID", "```" + hwid + "```", false)
|
||||
);
|
||||
}
|
||||
|
||||
// Logging the license use
|
||||
log.info("License key '{}' for product '{}' was used by {} (HWID: {})", key, product, ip, hwid);
|
||||
return license;
|
||||
} catch (APIException ex) {
|
||||
@ -195,7 +208,7 @@ public final class LicenseService {
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License IP Limit Reached")
|
||||
.setDescription("License `%s` has reached it's IP limit: **%s**".formatted(
|
||||
MiscUtils.obfuscateKey(key),
|
||||
obfuscateKey,
|
||||
license.getIpLimit()
|
||||
))
|
||||
);
|
||||
@ -204,7 +217,7 @@ public final class LicenseService {
|
||||
.setColor(Color.RED)
|
||||
.setTitle("License HWID Limit Reached")
|
||||
.setDescription("License `%s` has reached it's HWID limit: **%s**".formatted(
|
||||
MiscUtils.obfuscateKey(key),
|
||||
obfuscateKey,
|
||||
license.getHwidLimit()
|
||||
))
|
||||
);
|
||||
|
@ -12,6 +12,8 @@ salts:
|
||||
# Discord Bot Configuration
|
||||
discord:
|
||||
token: ""
|
||||
|
||||
# Global Logs
|
||||
logs:
|
||||
channel: 0 # The channel ID to log to, leave as 0 to disable
|
||||
uses: true # Should used licenses be logged?
|
||||
@ -19,6 +21,11 @@ discord:
|
||||
ipLimitExceeded: true # Should IP limited licenses be logged when used?
|
||||
hwidLimitExceeded: true # Should HWID limited licenses be logged when used?
|
||||
|
||||
# License Owner Logs
|
||||
owner-logs:
|
||||
newIp: true # Should new IPs be sent to the license owner?
|
||||
newHwid: true # Should new HWIDs be sent to the license owner?
|
||||
|
||||
# Log Configuration
|
||||
logging:
|
||||
file:
|
||||
|
Reference in New Issue
Block a user