From 4c2db6f87ed47303fc92b66d281c7ee0caadb803 Mon Sep 17 00:00:00 2001 From: minster586 <43217359+minster586@users.noreply.github.com> Date: Tue, 5 Aug 2025 23:52:08 -0400 Subject: [PATCH] done i think --- .gitignore | 1 + pom.xml | 97 +++++++++++ .../events/TikTokEventDispatcher.java | 102 ++++++++++++ .../tiktokstream/TikTokLiveCommand.java | 44 +++++ .../tiktokstream/TikTokStreamPlugin.java | 28 +++- .../tiktokstream/config/ConfigManager.java | 150 ++++++++++++++++++ .../tiktokstream/util/GiftMappingManager.java | 94 +++++++++++ .../websocket/StreamerBotWebSocketClient.java | 65 ++++++++ .../minster586/ui/NotificationManager.java | 54 +++++++ src/main/resources/config.yml | 33 ++++ src/main/resources/plugin.yml | 16 ++ 11 files changed, 683 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 src/main/java/com/minster586/events/TikTokEventDispatcher.java create mode 100644 src/main/java/com/minster586/tiktokstream/TikTokLiveCommand.java create mode 100644 src/main/java/com/minster586/tiktokstream/config/ConfigManager.java create mode 100644 src/main/java/com/minster586/tiktokstream/util/GiftMappingManager.java create mode 100644 src/main/java/com/minster586/tiktokstream/websocket/StreamerBotWebSocketClient.java create mode 100644 src/main/java/com/minster586/ui/NotificationManager.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/pom.xml b/pom.xml index e69de29..7573f27 100644 --- a/pom.xml +++ b/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + + com.minster586 + tiktok-stream-plugin + 1.0.0 + jar + + TikTokStreamPlugin + Integrates TikTok Live events with Minecraft 1.19 + + + 17 + ${java.version} + ${java.version} + + + + + + org.spigotmc + spigot-api + 1.19-R0.1-SNAPSHOT + provided + + + + + org.java-websocket + Java-WebSocket + 1.5.3 + + + + + com.google.code.gson + gson + 2.10.1 + + + + + org.yaml + snakeyaml + 2.2 + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + shade + + + + org.java_websocket + com.minster586.shaded.websocket + + + true + false + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/minster586/events/TikTokEventDispatcher.java b/src/main/java/com/minster586/events/TikTokEventDispatcher.java new file mode 100644 index 0000000..d293c79 --- /dev/null +++ b/src/main/java/com/minster586/events/TikTokEventDispatcher.java @@ -0,0 +1,102 @@ +package com.minster586.tiktokstream.events; + +import com.minster586.tiktokstream.TikTokStreamPlugin; +import com.minster586.tiktokstream.config.ConfigManager; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.ChatMessageType; +import com.minster586.ui.NotificationManager; + +import java.util.List; + +public class TikTokEventDispatcher { + + private final ConfigManager config; + + public TikTokEventDispatcher() { + this.config = TikTokStreamPlugin.getInstance().getConfigManager(); + } + + public void handleGift(String username, String giftName, int amount) { + if (!config.isGiftEnabled()) return; + + List locations = config.getGiftLocations(); + String prefix = config.getPrefix(); + + String chatMessage = config.getGiftChatMessage() + .replace("%prefix%", prefix) + .replace("%username%", username) + .replace("%giftName%", giftName) + .replace("%amount%", String.valueOf(amount)); + + String actionBarMessage = config.getGiftActionBarMessage() + .replace("%prefix%", prefix) + .replace("%username%", username) + .replace("%giftName%", giftName) + .replace("%amount%", String.valueOf(amount)); + + if (locations.contains("chat")) { + Bukkit.broadcastMessage(chatMessage); + } + + if (locations.contains("action-bar")) { + for (Player player : Bukkit.getOnlinePlayers()) { + player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(actionBarMessage)); + } + } + + // Toast notifications removed. Use NotificationManager for chat/action-bar only. + } + + public void handleJoin(String username) { + if (!config.isJoinEnabled()) return; + + String location = config.getJoinLocation(); + String message = config.getJoinActionBarMessage().replace("%username%", username); + + switch (location) { + case "chat" -> Bukkit.broadcastMessage(config.getJoinChatMessage().replace("%username%", username)); + case "action-bar" -> Bukkit.getOnlinePlayers().forEach(p -> p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(message))); + // Toast notifications removed + } + } + + public void handleChat(String username, String messageText) { + if (!config.isChatEnabled()) return; + + String location = config.getChatLocation(); + String formatted = config.getChatMessage() + .replace("%prefix%", config.getPrefix()) + .replace("%username%", username) + .replace("%message%", messageText); + + switch (location) { + case "chat" -> Bukkit.broadcastMessage(formatted); + case "action-bar" -> Bukkit.getOnlinePlayers().forEach(p -> p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent( + config.getChatActionBarMessage() + .replace("%username%", username) + .replace("%message%", messageText) + ))); + // Toast notifications removed + } + } + + public void handleFollow(String username) { + if (!config.isFollowEnabled()) return; + + List locations = config.getFollowLocations(); + + if (locations.contains("chat")) { + Bukkit.broadcastMessage(config.getFollowChatMessage().replace("%username%", username)); + } + + if (locations.contains("action-bar")) { + Bukkit.getOnlinePlayers().forEach(p -> p.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent( + config.getFollowActionBarMessage().replace("%username%", username) + ))); + } + + // Toast notifications removed. Use NotificationManager for chat/action-bar only. + } +} \ No newline at end of file diff --git a/src/main/java/com/minster586/tiktokstream/TikTokLiveCommand.java b/src/main/java/com/minster586/tiktokstream/TikTokLiveCommand.java new file mode 100644 index 0000000..35835bd --- /dev/null +++ b/src/main/java/com/minster586/tiktokstream/TikTokLiveCommand.java @@ -0,0 +1,44 @@ +package com.minster586.tiktokstream; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class TikTokLiveCommand implements CommandExecutor, TabCompleter { + private final TikTokStreamPlugin plugin; + private static final String PERMISSION = "tiktok.live"; + + public TikTokLiveCommand(TikTokStreamPlugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission(PERMISSION)) { + sender.sendMessage(ChatColor.RED + "You do not have permission to use this command."); + return true; + } + if (args.length != 1 || !(args[0].equalsIgnoreCase("enable") || args[0].equalsIgnoreCase("disable"))) { + sender.sendMessage(ChatColor.YELLOW + "Usage: /tiktok live "); + return true; + } + boolean enable = args[0].equalsIgnoreCase("enable"); + plugin.setTiktokLiveEnabled(enable); + sender.sendMessage(ChatColor.GREEN + "TikTok live notifications " + (enable ? "enabled" : "disabled") + "."); + return true; + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (args.length == 1) { + return Arrays.asList("enable", "disable"); + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/com/minster586/tiktokstream/TikTokStreamPlugin.java b/src/main/java/com/minster586/tiktokstream/TikTokStreamPlugin.java index 9189af9..d06f19a 100644 --- a/src/main/java/com/minster586/tiktokstream/TikTokStreamPlugin.java +++ b/src/main/java/com/minster586/tiktokstream/TikTokStreamPlugin.java @@ -2,15 +2,18 @@ package com.minster586.tiktokstream; import com.minster586.tiktokstream.config.ConfigManager; import com.minster586.tiktokstream.websocket.StreamerBotWebSocketClient; +import com.minster586.tiktokstream.util.GiftMappingManager; import org.bukkit.plugin.java.JavaPlugin; import java.net.URI; -public class TikTokStreamPlugin extends JavaPlugin { +public class TikTokStreamPlugin extends JavaPlugin { private static TikTokStreamPlugin instance; private ConfigManager configManager; private StreamerBotWebSocketClient webSocketClient; + private GiftMappingManager giftMappingManager; + private boolean tiktokLiveEnabled = true; @Override public void onEnable() { @@ -20,12 +23,31 @@ public class TikTokStreamPlugin extends JavaPlugin { configManager = new ConfigManager(this); configManager.load(); + // Initialize GiftMappingManager + String giftMappingUrl = getConfig().getString("config.giftMappingUrl"); + if (giftMappingUrl != null && !giftMappingUrl.isEmpty()) { + giftMappingManager = new GiftMappingManager(this, giftMappingUrl); + } else { + getLogger().warning("No giftMappingUrl found in config! Gift names will not be available."); + } + String websocketUrl = configManager.getWebSocketUrl(); webSocketClient = new StreamerBotWebSocketClient(URI.create(websocketUrl)); webSocketClient.connect(); + // Register /tiktok live command + getCommand("tiktok").setExecutor(new TikTokLiveCommand(this)); + getCommand("tiktok").setTabCompleter(new TikTokLiveCommand(this)); + getLogger().info("TikTokStreamPlugin enabled and connected to Streamer.bot."); } + public boolean isTiktokLiveEnabled() { + return tiktokLiveEnabled; + } + + public void setTiktokLiveEnabled(boolean enabled) { + this.tiktokLiveEnabled = enabled; + } @Override public void onDisable() { @@ -42,4 +64,8 @@ public class TikTokStreamPlugin extends JavaPlugin { public ConfigManager getConfigManager() { return configManager; } + + public GiftMappingManager getGiftMappingManager() { + return giftMappingManager; + } } \ No newline at end of file diff --git a/src/main/java/com/minster586/tiktokstream/config/ConfigManager.java b/src/main/java/com/minster586/tiktokstream/config/ConfigManager.java new file mode 100644 index 0000000..c18b27d --- /dev/null +++ b/src/main/java/com/minster586/tiktokstream/config/ConfigManager.java @@ -0,0 +1,150 @@ +package com.minster586.tiktokstream.config; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +public class ConfigManager { + + private final JavaPlugin plugin; + private FileConfiguration config; + + public ConfigManager(JavaPlugin plugin) { + this.plugin = plugin; + } + + public void load() { + File configFile = new File(plugin.getDataFolder(), "config.yml"); + + if (!configFile.exists()) { + plugin.saveResource("config.yml", false); + } + + config = YamlConfiguration.loadConfiguration(configFile); + + // Load defaults from resources/config.yml + InputStream defaultStream = plugin.getResource("config.yml"); + if (defaultStream != null) { + YamlConfiguration defaultConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(defaultStream)); + config.setDefaults(defaultConfig); + } + } + + // 🔧 Accessors (no hardcoded defaults — rely on config.yml) + public String getPrefix() { + return config.getString("config.prefix"); + } + + public String getTikTokUsername() { + return config.getString("config.tiktok_username"); + } + + public String getGiftMappingUrl() { + return config.getString("config.giftMappingUrl"); + } + + + public String getWebSocketUrl() { + return config.getString("config.websocket.url"); + } + + + public int getWebSocketRefreshDelay() { + return config.getInt("config.websocket.refresh-delay"); + } + + public boolean isJoinEnabled() { + return config.getBoolean("config.settings.join.joined-enabled"); + } + + public String getJoinLocation() { + return config.getString("config.settings.join.loctions"); + } + + public String getJoinChatMessage() { + return config.getString("config.settings.join.chat-message"); + } + + public String getJoinActionBarMessage() { + return config.getString("config.settings.join.action-bar-message"); + } + + public boolean isGiftEnabled() { + return config.getBoolean("config.settings.gift.gifts-enabled"); + } + + public List getGiftLocations() { + return parseLocationList("config.settings.gift.loctions"); + } + + public String getGiftChatMessage() { + return config.getString("config.settings.gift.chat-message"); + } + + public String getGiftActionBarMessage() { + return config.getString("config.settings.gift.action-bar-message"); + } + + public boolean isChatEnabled() { + return config.getBoolean("config.settings.chat.chat-enabled"); + } + + public String getChatLocation() { + return config.getString("config.settings.chat.loctions"); + } + + public String getChatMessage() { + return config.getString("config.settings.chat.chat-message"); + } + + public String getChatActionBarMessage() { + return config.getString("config.settings.chat.action-bar-message"); + } + + + public boolean isFollowEnabled() { + return config.getBoolean("config.settings.follows.follow-enabled"); + } + + public List getFollowLocations() { + return parseLocationList("config.settings.follows.loctions"); + } + + public String getFollowChatMessage() { + return config.getString("config.settings.follows.chat-message"); + } + + public String getFollowActionBarMessage() { + return config.getString("config.settings.follows.action-bar-message"); + } + + + + // 🔧 Helpers + private List parseLocationList(String path) { + String raw = config.getString(path); + if (raw == null) return Collections.emptyList(); + return Arrays.stream(raw.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .toList(); + } + + private Integer getNullableInt(String path) { + if (!config.contains(path) || config.get(path) == null) return null; + return config.getInt(path); + } + // Generic config accessors for plugin internals + public int getIntOrDefault(String path, int def) { + if (!config.contains(path) || config.get(path) == null) return def; + return config.getInt(path); + } + + public String getStringOrDefault(String path, String def) { + if (!config.contains(path) || config.get(path) == null) return def; + return config.getString(path); + } +} \ No newline at end of file diff --git a/src/main/java/com/minster586/tiktokstream/util/GiftMappingManager.java b/src/main/java/com/minster586/tiktokstream/util/GiftMappingManager.java new file mode 100644 index 0000000..c46dd40 --- /dev/null +++ b/src/main/java/com/minster586/tiktokstream/util/GiftMappingManager.java @@ -0,0 +1,94 @@ +package com.minster586.tiktokstream.util; + +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.*; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import org.yaml.snakeyaml.Yaml; + +public class GiftMappingManager { + private final JavaPlugin plugin; + private final File mappingFile; + private Map giftMap = new HashMap<>(); + + public GiftMappingManager(JavaPlugin plugin, String mappingUrl) { + this.plugin = plugin; + this.mappingFile = new File(plugin.getDataFolder(), "gift-mapping.yaml"); + checkAndUpdateMapping(mappingUrl); + loadMapping(); + } + + private void checkAndUpdateMapping(String urlStr) { + try { + // Download remote file to temp + File tempFile = File.createTempFile("gift-mapping", ".yaml"); + try (InputStream in = new URL(urlStr).openStream(); + FileOutputStream out = new FileOutputStream(tempFile)) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + // Compare contents + boolean shouldReplace = !mappingFile.exists() || !filesEqual(mappingFile, tempFile); + if (shouldReplace) { + try (InputStream in = new FileInputStream(tempFile); + OutputStream out = new FileOutputStream(mappingFile)) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + } + } + plugin.getLogger().info("Gift mapping updated from remote."); + } else { + plugin.getLogger().info("Gift mapping is up to date."); + } + tempFile.delete(); + } catch (IOException e) { + plugin.getLogger().warning("Failed to check/update gift mapping: " + e.getMessage()); + } + } + + private boolean filesEqual(File f1, File f2) throws IOException { + if (f1.length() != f2.length()) return false; + try (InputStream in1 = new FileInputStream(f1); InputStream in2 = new FileInputStream(f2)) { + int b1, b2; + do { + b1 = in1.read(); + b2 = in2.read(); + if (b1 != b2) return false; + } while (b1 != -1); + } + return true; + } + + // downloadMapping is now handled by checkAndUpdateMapping + + public void loadMapping() { + if (!mappingFile.exists()) return; + try (InputStream input = new FileInputStream(mappingFile)) { + Yaml yaml = new Yaml(); + Map data = yaml.load(input); + giftMap.clear(); + if (data != null) { + for (Map.Entry entry : data.entrySet()) { + try { + int id = Integer.parseInt(entry.getKey()); + giftMap.put(id, String.valueOf(entry.getValue())); + } catch (NumberFormatException ignored) {} + } + } + } catch (IOException e) { + plugin.getLogger().warning("Failed to load gift mapping: " + e.getMessage()); + } + } + + public String getGiftName(int id) { + return giftMap.getOrDefault(id, "Unknown Gift"); + } +} diff --git a/src/main/java/com/minster586/tiktokstream/websocket/StreamerBotWebSocketClient.java b/src/main/java/com/minster586/tiktokstream/websocket/StreamerBotWebSocketClient.java new file mode 100644 index 0000000..c731b50 --- /dev/null +++ b/src/main/java/com/minster586/tiktokstream/websocket/StreamerBotWebSocketClient.java @@ -0,0 +1,65 @@ +package com.minster586.tiktokstream.websocket; + + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; +import java.net.URI; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import com.minster586.tiktokstream.TikTokStreamPlugin; +import com.minster586.tiktokstream.config.ConfigManager; + +public class StreamerBotWebSocketClient extends WebSocketClient { + private int retryCount = 0; + private final ConfigManager configManager; + private final int maxRetries; + private final String notifyPermission; + private final String notifyMessage; + + public StreamerBotWebSocketClient(URI serverUri) { + super(serverUri); + this.configManager = TikTokStreamPlugin.getInstance().getConfigManager(); + this.maxRetries = configManager != null ? configManager.getIntOrDefault("config.websocket.retry-count", 3) : 3; + this.notifyPermission = configManager != null ? configManager.getStringOrDefault("config.websocket.notify-permission", "tiktok.live") : "tiktok.live"; + this.notifyMessage = configManager != null ? configManager.getStringOrDefault("config.websocket.notify-message", "§c[StreamerBot] Could not connect after %retries% attempts. Check your config or Streamer.bot status.") : "§c[StreamerBot] Could not connect after %retries% attempts. Check your config or Streamer.bot status."; + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + retryCount = 0; // Reset on successful connection + } + + @Override + public void onMessage(String message) { + // Handle incoming messages from Streamer.bot here + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (retryCount < maxRetries) { + retryCount++; + try { + Thread.sleep(2000L * retryCount); // Exponential backoff + } catch (InterruptedException ignored) {} + this.reconnect(); + } else { + String msg = notifyMessage.replace("%retries%", String.valueOf(maxRetries)); + notifyOps(msg); + } + } + + @Override + public void onError(Exception ex) { + // Optionally log errors + } + + private void notifyOps(String message) { + Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("TikTokStreamPlugin"), () -> { + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.isOp() || player.hasPermission(notifyPermission)) { + player.sendMessage(message); + } + } + }); + } +} diff --git a/src/main/java/com/minster586/ui/NotificationManager.java b/src/main/java/com/minster586/ui/NotificationManager.java new file mode 100644 index 0000000..85bccba --- /dev/null +++ b/src/main/java/com/minster586/ui/NotificationManager.java @@ -0,0 +1,54 @@ +package com.minster586.ui; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.ChatMessageType; + +import java.util.Map; + +public class NotificationManager { + private static final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(NotificationManager.class); + + public static void sendNotification(Player player, ConfigurationSection section, Map placeholders) { + if (section == null) return; + + String locationsRaw = section.getString("locations", "").toLowerCase(); + String[] locations = locationsRaw.split(","); + + for (String loc : locations) { + switch (loc.trim()) { + case "chat": + sendChat(player, section.getString("chat-message", ""), placeholders); + break; + case "action-bar": + sendActionBar(player, section.getString("action-bar-message", ""), placeholders); + break; + default: + Bukkit.getLogger().warning("Unknown notification location: " + loc); + } + } + } + + private static void sendChat(Player player, String message, Map placeholders) { + if (message == null || message.isEmpty()) return; + player.sendMessage(ChatColor.translateAlternateColorCodes('&', replacePlaceholders(message, placeholders))); + } + + private static void sendActionBar(Player player, String message, Map placeholders) { + if (message == null || message.isEmpty()) return; + player.spigot().sendMessage(ChatMessageType.ACTION_BAR, new TextComponent(replacePlaceholders(message, placeholders))); + } + + private static String replacePlaceholders(String input, Map placeholders) { + if (input == null) return ""; + for (Map.Entry entry : placeholders.entrySet()) { + input = input.replace("%" + entry.getKey() + "%", entry.getValue()); + } + return input; + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..ce6cfb4 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,33 @@ +config: + prefix: [TikTok] + tiktok_username: Change-me #this is the username of your tiktok account with out the "@" symbol + giftMappingUrl: "https://yourdomain.com/gift-mapping.yaml" #you can change if need but might want to make sure it follows same scheam + + websocket: + url: "ws://localhost:8080" # change IP if need + refresh-delay: 6 # this is in seconds + retry-count: 3 # Number of times to retry connecting to Streamer.bot + notify-permission: "tiktok.live" # Permission required to receive connection failure notifications + notify-message: "§c[StreamerBot] Could not connect after %retries% attempts. Check your config or Streamer.bot status." + + settings: + join: + joined-enabled: true + loctions: action-bar #this can be chat, action-bar + chat-message: "" + action-bar-message: "%username% joined the stream!" + gift: + gifts-enabled: true + loctions: chat #this can be chat, action-bar + chat-message: "%prefix% - %username% sent a %giftName%!" + action-bar-message: "" + chat: + chat-enabled: true + loctions: chat #this can be chat, action-bar + chat-message: "%prefix% - [%username%] > %message%" + action-bar-message: "" + follows: + follow-enabled: true + loctions: chat #this can be chat, action-bar + chat-message: "%prefix% - [%username%] Followed" + action-bar-message: "" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..2c80df9 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,16 @@ +name: TikTokStreamPlugin +main: com.minster586.tiktokstream.TikTokStreamPlugin +version: 1.0.0 +author: minster586 +description: Integrates TikTok Live events with Minecraft 1.19 +api-version: 1.19 + +commands: + tiktok: + description: TikTok live control command + usage: /tiktok live + permission: tiktok.live +permissions: + tiktok.live: + description: Allows control of TikTok live notifications + default: op