Skip to content

Commit 89e9559

Browse files
committed
add discord webhook and fix docs js
1 parent 641d819 commit 89e9559

7 files changed

Lines changed: 292 additions & 5 deletions

File tree

docs/public/.nojekyll

Whitespace-only changes.

simppay-paper/src/main/java/org/simpmc/simppay/SPPlugin.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.simpmc.simppay.listener.internal.player.SuccessHandlingListener;
2222
import org.simpmc.simppay.listener.internal.player.database.SuccessDatabaseHandlingListener;
2323
import org.simpmc.simppay.service.*;
24+
import org.simpmc.simppay.service.DiscordService;
2425
import org.simpmc.simppay.service.cache.CacheDataService;
2526
import xyz.xenondevs.invui.InvUI;
2627

@@ -99,6 +100,7 @@ public void onEnable() {
99100
services.add(new PaymentService());
100101
services.add(new MilestoneService());
101102
services.add(new WebhookService()); // Webhook server for Sepay
103+
services.add(new DiscordService());
102104

103105
registerServices();
104106

simppay-paper/src/main/java/org/simpmc/simppay/commands/sub/admin/DebugCardCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
public class DebugCardCommand {
1818

1919
public static CommandAPICommand commandCreate() {
20-
return new CommandAPICommand("debugcard")
21-
.withPermission("simppay.admin.debugcard")
20+
return new CommandAPICommand("napcard")
21+
.withPermission("simppay.admin.napcard")
2222
.withArguments(
2323
new BooleanArgument("wrongPrice")
2424
)

simppay-paper/src/main/java/org/simpmc/simppay/commands/sub/admin/RechargeBankCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
public class RechargeBankCommand {
1616
public static CommandAPICommand commandCreate() {
17-
return new CommandAPICommand("rechargebank")
18-
.withPermission("simppay.admin.rechargebank")
17+
return new CommandAPICommand("napbank")
18+
.withPermission("simppay.admin.napbank")
1919
.withArguments(
2020
new LongArgument("amount")
2121
)

simppay-paper/src/main/java/org/simpmc/simppay/config/ConfigManager.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.simpmc.simppay.config.serializers.KeySerializer;
1313
import org.simpmc.simppay.config.serializers.SoundComponentSerializer;
1414
import org.simpmc.simppay.config.types.*;
15+
import org.simpmc.simppay.config.types.DiscordConfig;
1516
import org.simpmc.simppay.config.types.banking.PayosConfig;
1617
import org.simpmc.simppay.config.types.banking.SepayConfig;
1718
import org.simpmc.simppay.config.types.banking.Web2mConfig;
@@ -66,7 +67,8 @@ public class ConfigManager {
6667
Card2KConfig.class,
6768
Web2mConfig.class,
6869
ThesieureConfig.class,
69-
Doithe1sConfig.class
70+
Doithe1sConfig.class,
71+
DiscordConfig.class
7072
);
7173
// holds file paths for each config type
7274
private final Map<Class<?>, Path> configPaths = new HashMap<>();
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package org.simpmc.simppay.config.types;
2+
3+
import de.exlll.configlib.Configuration;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
import java.util.List;
9+
10+
@Configuration
11+
public class DiscordConfig {
12+
public boolean enabled = false;
13+
public EventConfig cardPayment = new EventConfig(
14+
false, "", "SimpPay", "",
15+
new EmbedConfig(
16+
"Nạp thẻ thành công",
17+
"**{player}** vừa nạp thẻ thành công!\nCổng: **{gateway}** | Mệnh giá: **{amount}đ** | Thực nhận: **{trueAmount}đ**",
18+
"#57F287", "", "SimpPay",
19+
List.of(
20+
new FieldConfig("Người chơi", "{player}", true),
21+
new FieldConfig("Cổng nạp", "{gateway}", true),
22+
new FieldConfig("Mệnh giá", "{amount}đ", true),
23+
new FieldConfig("Thực nhận", "{trueAmount}đ", true)
24+
)
25+
)
26+
);
27+
public EventConfig bankPayment = new EventConfig(
28+
false, "", "SimpPay", "",
29+
new EmbedConfig(
30+
"Chuyển khoản thành công",
31+
"**{player}** vừa chuyển khoản thành công!\nCổng: **{gateway}** | Số tiền: **{amount}đ**",
32+
"#3498DB", "", "SimpPay",
33+
List.of(
34+
new FieldConfig("Người chơi", "{player}", true),
35+
new FieldConfig("Cổng nạp", "{gateway}", true),
36+
new FieldConfig("Số tiền", "{amount}đ", true)
37+
)
38+
)
39+
);
40+
public EventConfig playerMilestone = new EventConfig(
41+
false, "", "SimpPay", "",
42+
new EmbedConfig(
43+
"Mốc nạp cá nhân",
44+
"**{player}** đã đạt mốc nạp **{milestoneAmount}đ** ({milestoneType})!\nTổng nạp hiện tại: **{currentAmount}đ**",
45+
"#F1C40F", "", "SimpPay",
46+
List.of(
47+
new FieldConfig("Người chơi", "{player}", true),
48+
new FieldConfig("Loại mốc", "{milestoneType}", true),
49+
new FieldConfig("Mốc đạt được", "{milestoneAmount}đ", true),
50+
new FieldConfig("Tổng nạp", "{currentAmount}đ", true)
51+
)
52+
)
53+
);
54+
public EventConfig serverMilestone = new EventConfig(
55+
false, "", "SimpPay", "",
56+
new EmbedConfig(
57+
"Mốc nạp server",
58+
"Server đã đạt mốc nạp **{milestoneAmount}đ** ({milestoneType})!\nTổng nạp server: **{currentAmount}đ**",
59+
"#E74C3C", "", "SimpPay",
60+
List.of(
61+
new FieldConfig("Loại mốc", "{milestoneType}", true),
62+
new FieldConfig("Mốc đạt được", "{milestoneAmount}đ", true),
63+
new FieldConfig("Tổng nạp server", "{currentAmount}đ", true)
64+
)
65+
)
66+
);
67+
68+
@Configuration
69+
@Data
70+
@NoArgsConstructor
71+
@AllArgsConstructor
72+
public static class EventConfig {
73+
public boolean enabled = false;
74+
public String webhookUrl = "";
75+
public String botUsername = "SimpPay";
76+
public String avatarUrl = "";
77+
public EmbedConfig embed = new EmbedConfig();
78+
}
79+
80+
@Configuration
81+
@Data
82+
@NoArgsConstructor
83+
@AllArgsConstructor
84+
public static class EmbedConfig {
85+
public String title = "";
86+
public String description = "";
87+
public String color = "#57F287";
88+
public String thumbnailUrl = "";
89+
public String footerText = "SimpPay";
90+
public List<FieldConfig> fields = List.of();
91+
}
92+
93+
@Configuration
94+
@Data
95+
@NoArgsConstructor
96+
@AllArgsConstructor
97+
public static class FieldConfig {
98+
public String name = "";
99+
public String value = "";
100+
public boolean inline = true;
101+
}
102+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package org.simpmc.simppay.service;
2+
3+
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonObject;
5+
import okhttp3.MediaType;
6+
import okhttp3.OkHttpClient;
7+
import okhttp3.Request;
8+
import okhttp3.RequestBody;
9+
import okhttp3.Response;
10+
import org.bukkit.Bukkit;
11+
import org.bukkit.event.EventHandler;
12+
import org.bukkit.event.EventPriority;
13+
import org.bukkit.event.Listener;
14+
import org.simpmc.simppay.SPPlugin;
15+
import org.simpmc.simppay.config.ConfigManager;
16+
import org.simpmc.simppay.config.types.DiscordConfig;
17+
import org.simpmc.simppay.config.types.DiscordConfig.EventConfig;
18+
import org.simpmc.simppay.data.PaymentType;
19+
import org.simpmc.simppay.event.MilestoneCompleteEvent;
20+
import org.simpmc.simppay.event.PaymentSuccessEvent;
21+
import org.simpmc.simppay.model.detail.CardDetail;
22+
import org.simpmc.simppay.util.MessageUtil;
23+
24+
import java.io.IOException;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import java.util.Objects;
28+
import java.util.UUID;
29+
30+
public class DiscordService implements IService, Listener {
31+
32+
private OkHttpClient client;
33+
34+
@Override
35+
public void setup() {
36+
client = new OkHttpClient();
37+
}
38+
39+
@Override
40+
public void shutdown() {
41+
if (client != null) {
42+
client.dispatcher().executorService().shutdown();
43+
client.connectionPool().evictAll();
44+
}
45+
}
46+
47+
@EventHandler(priority = EventPriority.MONITOR)
48+
public void onPaymentSuccess(PaymentSuccessEvent event) {
49+
DiscordConfig config = ConfigManager.getInstance().getConfig(DiscordConfig.class);
50+
if (!config.enabled) return;
51+
52+
UUID playerUUID = event.getPlayerUUID();
53+
String playerName = Objects.requireNonNullElse(Bukkit.getOfflinePlayer(playerUUID).getName(), playerUUID.toString());
54+
55+
if (event.getPaymentType() == PaymentType.CARD) {
56+
CardDetail cardDetail = (CardDetail) event.getPaymentDetail();
57+
Map<String, String> placeholders = new HashMap<>();
58+
placeholders.put("{player}", playerName);
59+
placeholders.put("{amount}", String.valueOf((long) event.getAmount()));
60+
placeholders.put("{trueAmount}", String.valueOf((long) event.getTrueAmount()));
61+
placeholders.put("{gateway}", cardDetail.getType().toString());
62+
placeholders.put("{paymentId}", event.getPaymentID().toString());
63+
placeholders.put("{paymentType}", "CARD");
64+
dispatch(config.cardPayment, placeholders);
65+
} else if (event.getPaymentType() == PaymentType.BANKING) {
66+
Map<String, String> placeholders = new HashMap<>();
67+
placeholders.put("{player}", playerName);
68+
placeholders.put("{amount}", String.valueOf((long) event.getAmount()));
69+
placeholders.put("{trueAmount}", String.valueOf((long) event.getAmount()));
70+
placeholders.put("{gateway}", "BANKING");
71+
placeholders.put("{paymentId}", event.getPaymentID().toString());
72+
placeholders.put("{paymentType}", "BANKING");
73+
dispatch(config.bankPayment, placeholders);
74+
}
75+
}
76+
77+
@EventHandler
78+
public void onMilestoneComplete(MilestoneCompleteEvent event) {
79+
DiscordConfig config = ConfigManager.getInstance().getConfig(DiscordConfig.class);
80+
if (!config.enabled) return;
81+
82+
Map<String, String> placeholders = new HashMap<>();
83+
placeholders.put("{milestoneType}", event.getType().name());
84+
placeholders.put("{milestoneAmount}", String.valueOf(event.getMilestoneConfig().amount));
85+
placeholders.put("{currentAmount}", String.valueOf(event.getCurrentAmount()));
86+
87+
if (event.getPlayerUUID() == null) {
88+
dispatch(config.serverMilestone, placeholders);
89+
} else {
90+
String playerName = Objects.requireNonNullElse(
91+
Bukkit.getOfflinePlayer(event.getPlayerUUID()).getName(),
92+
event.getPlayerUUID().toString()
93+
);
94+
placeholders.put("{player}", playerName);
95+
dispatch(config.playerMilestone, placeholders);
96+
}
97+
}
98+
99+
private void dispatch(EventConfig eventConfig, Map<String, String> placeholders) {
100+
if (!eventConfig.enabled || eventConfig.webhookUrl.isBlank()) return;
101+
102+
String payload = buildPayload(eventConfig, placeholders);
103+
104+
SPPlugin.getInstance().getFoliaLib().getScheduler().runAsync(task -> {
105+
RequestBody body = RequestBody.create(payload, MediaType.parse("application/json"));
106+
Request request = new Request.Builder()
107+
.url(eventConfig.webhookUrl)
108+
.post(body)
109+
.build();
110+
try (Response response = client.newCall(request).execute()) {
111+
if (!response.isSuccessful()) {
112+
MessageUtil.warn("[DiscordService] Webhook returned non-2xx: " + response.code());
113+
}
114+
} catch (IOException e) {
115+
MessageUtil.warn("[DiscordService] Failed to send Discord webhook: " + e.getMessage());
116+
}
117+
});
118+
}
119+
120+
private String buildPayload(EventConfig eventConfig, Map<String, String> placeholders) {
121+
JsonObject root = new JsonObject();
122+
if (!eventConfig.botUsername.isBlank()) {
123+
root.addProperty("username", eventConfig.botUsername);
124+
}
125+
if (!eventConfig.avatarUrl.isBlank()) {
126+
root.addProperty("avatar_url", eventConfig.avatarUrl);
127+
}
128+
129+
JsonObject embed = new JsonObject();
130+
embed.addProperty("title", applyPlaceholders(eventConfig.embed.title, placeholders));
131+
embed.addProperty("description", applyPlaceholders(eventConfig.embed.description, placeholders));
132+
embed.addProperty("color", parseColor(eventConfig.embed.color));
133+
134+
if (!eventConfig.embed.thumbnailUrl.isBlank()) {
135+
JsonObject thumbnail = new JsonObject();
136+
thumbnail.addProperty("url", eventConfig.embed.thumbnailUrl);
137+
embed.add("thumbnail", thumbnail);
138+
}
139+
140+
if (!eventConfig.embed.footerText.isBlank()) {
141+
JsonObject footer = new JsonObject();
142+
footer.addProperty("text", applyPlaceholders(eventConfig.embed.footerText, placeholders));
143+
embed.add("footer", footer);
144+
}
145+
146+
if (eventConfig.embed.fields != null && !eventConfig.embed.fields.isEmpty()) {
147+
JsonArray fields = new JsonArray();
148+
for (DiscordConfig.FieldConfig field : eventConfig.embed.fields) {
149+
JsonObject fieldObj = new JsonObject();
150+
fieldObj.addProperty("name", applyPlaceholders(field.name, placeholders));
151+
fieldObj.addProperty("value", applyPlaceholders(field.value, placeholders));
152+
fieldObj.addProperty("inline", field.inline);
153+
fields.add(fieldObj);
154+
}
155+
embed.add("fields", fields);
156+
}
157+
158+
JsonArray embeds = new JsonArray();
159+
embeds.add(embed);
160+
root.add("embeds", embeds);
161+
162+
return root.toString();
163+
}
164+
165+
private String applyPlaceholders(String text, Map<String, String> placeholders) {
166+
if (text == null) return "";
167+
for (Map.Entry<String, String> entry : placeholders.entrySet()) {
168+
text = text.replace(entry.getKey(), entry.getValue());
169+
}
170+
return text;
171+
}
172+
173+
private int parseColor(String hex) {
174+
try {
175+
String clean = hex.startsWith("#") ? hex.substring(1) : hex;
176+
return Integer.parseInt(clean, 16);
177+
} catch (NumberFormatException e) {
178+
return 5763719; // default green
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)