Added embedded upload service feature

This commit is contained in:
LOOHP committed 2025-02-11 16:17:07 +00:00
1 parent 6af8fd7b76
commit 8a89a33b42
112 files changed
+1164 -222

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_16</artifactId>
@@ -200,7 +200,7 @@ public class V1_16 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_16_2</artifactId>
@@ -200,7 +200,7 @@ public class V1_16_2 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_16_4</artifactId>
@@ -200,7 +200,7 @@ public class V1_16_4 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_17</artifactId>
@@ -194,7 +194,7 @@ public class V1_17 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_18</artifactId>
@@ -58,7 +58,6 @@ import net.minecraft.world.level.saveddata.maps.WorldMap;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_18_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftEntity;
@@ -195,7 +194,7 @@ public class V1_18 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_18_2</artifactId>
@@ -194,7 +194,7 @@ public class V1_18_2 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_19</artifactId>
@@ -193,7 +193,7 @@ public class V1_19 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_19_3</artifactId>
@@ -188,7 +188,7 @@ public class V1_19_3 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_19_4</artifactId>
@@ -188,7 +188,7 @@ public class V1_19_4 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_20</artifactId>
@@ -188,7 +188,7 @@ public class V1_20 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_20_2</artifactId>
@@ -188,7 +188,7 @@ public class V1_20_2 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_20_3</artifactId>
@@ -183,7 +183,7 @@ public class V1_20_3 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_20_5</artifactId>
@@ -189,7 +189,7 @@ public class V1_20_5 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_20_6</artifactId>
@@ -189,7 +189,7 @@ public class V1_20_6 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_21</artifactId>
@@ -189,7 +189,7 @@ public class V1_21 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_21_1</artifactId>
@@ -189,7 +189,7 @@ public class V1_21_1 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_21_2</artifactId>
@@ -189,7 +189,7 @@ public class V1_21_2 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_21_3</artifactId>
@@ -189,7 +189,7 @@ public class V1_21_3 extends NMSWrapper {
}
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-V1_21_4</artifactId>
@@ -91,12 +91,14 @@ public class V1_21_4 extends NMSWrapper {
private final Field nmsEntityByteDataWatcherField;
private final Field craftMapViewWorldMapField;
private final Field persistentIdCountsUsedAuxIdsField;
private final Field renderDataCursorsField;
public V1_21_4() {
try {
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "am");
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
renderDataCursorsField = RenderData.class.getField("cursors");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
@@ -188,12 +190,17 @@ public class V1_21_4 extends NMSWrapper {
}
}
@SuppressWarnings("unchecked")
@Override
public MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
return new MutablePair<>(renderData.buffer, renderData.cursors);
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
try {
CraftMapView craftMapView = (CraftMapView) mapView;
CraftPlayer craftPlayer = (CraftPlayer) player;
RenderData renderData = craftMapView.render(craftPlayer);
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.loohp</groupId>
<artifactId>ImageFrame-Parent</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
</parent>
<artifactId>ImageFrame-Abstraction</artifactId>
@@ -30,7 +30,6 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.map.MapCursor;
import org.bukkit.map.MapView;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -54,7 +53,7 @@ public abstract class NMSWrapper {
public abstract MapView getMapOrCreateMissing(World world, int id);
public abstract MutablePair<byte[], ArrayList<MapCursor>> bukkitRenderMap(MapView mapView, Player player);
public abstract MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player);
public abstract Set<Player> getEntityTrackers(Entity entity);
+35 -1
View File
@@ -1,4 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of ImageFrame.
~
~ Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
~ Copyright (C) 2025. Contributors
~
~ This program 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.
~
~ This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
@@ -6,7 +26,7 @@
<groupId>com.loohp</groupId>
<artifactId>ImageFrame</artifactId>
<version>1.7.14.3</version>
<version>1.7.14.4</version>
<packaging>jar</packaging>
<name>ImageFrame</name>
@@ -30,8 +50,16 @@
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
<exclude>upload/**</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>upload/**</include>
</includes>
</resource>
</resources>
<plugins>
@@ -235,6 +263,12 @@
<version>1.8.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -41,6 +41,8 @@ import com.loohp.imageframe.objectholders.URLAnimatedImageMap;
import com.loohp.imageframe.objectholders.URLImageMap;
import com.loohp.imageframe.objectholders.URLStaticImageMap;
import com.loohp.imageframe.updater.Updater;
import com.loohp.imageframe.upload.ImageUploadManager;
import com.loohp.imageframe.upload.PendingUpload;
import com.loohp.imageframe.utils.ChatColorUtils;
import com.loohp.imageframe.utils.HTTPRequestUtils;
import com.loohp.imageframe.utils.ImageMapUtils;
@@ -197,6 +199,12 @@ public class Commands implements CommandExecutor, TabCompleter {
String url = "Pending...";
try {
url = args[2];
if (ImageFrame.uploadServiceEnabled && url.equalsIgnoreCase("upload")) {
UUID user = isConsole ? ImageMap.CONSOLE_CREATOR : player.getUniqueId();
PendingUpload pendingUpload = ImageFrame.imageUploadManager.newPendingUpload(user);
Scheduler.runTaskLaterAsynchronously(ImageFrame.plugin, () -> sender.sendMessage(ImageFrame.messageUploadLink.replace("{URL}", pendingUpload.getUrl(ImageFrame.uploadServiceHost, user))), 2);
url = pendingUpload.getFileBlocking().toURI().toURL().toString();
}
if (HTTPRequestUtils.getContentSize(url) > ImageFrame.maxImageFileSize) {
sender.sendMessage(ImageFrame.messageImageOverMaxFileSize.replace("{Size}", ImageFrame.maxImageFileSize + ""));
throw new IOException("Image over max file size");
@@ -245,6 +253,8 @@ public class Commands implements CommandExecutor, TabCompleter {
}
sender.sendMessage(ImageFrame.messageImageMapCreated);
creationTask.complete(ImageFrame.messageImageMapCreated);
} catch (ImageUploadManager.LinkTimeoutException e) {
sender.sendMessage(ImageFrame.messageUploadExpired);
} catch (ImageMapCreationTaskManager.EnqueueRejectedException e) {
sender.sendMessage(ImageFrame.messageImageMapAlreadyQueued);
} catch (Exception e) {
@@ -336,18 +346,32 @@ public class Commands implements CommandExecutor, TabCompleter {
sender.sendMessage(ImageFrame.messageDuplicateMapName);
return true;
}
if (!ImageFrame.isURLAllowed(args[2])) {
sender.sendMessage(ImageFrame.messageURLRestricted);
return true;
}
Scheduler.runTaskAsynchronously(ImageFrame.plugin, () -> {
ImageMapCreationTask<ImageMap> creationTask = null;
try {
creationTask = ImageFrame.imageMapCreationTaskManager.enqueue(owner, name, width, height, () -> MinecraftURLOverlayImageMap.create(ImageFrame.imageMapManager, name, args[2], mapViews, width, height, ditheringType, player.getUniqueId()).get());
String url = args[2];
if (ImageFrame.uploadServiceEnabled && url.equalsIgnoreCase("upload")) {
UUID user = player.getUniqueId();
PendingUpload pendingUpload = ImageFrame.imageUploadManager.newPendingUpload(user);
Scheduler.runTaskLaterAsynchronously(ImageFrame.plugin, () -> sender.sendMessage(ImageFrame.messageUploadLink.replace("{URL}", pendingUpload.getUrl(ImageFrame.uploadServiceHost, user))), 2);
url = pendingUpload.getFileBlocking().toURI().toURL().toString();
}
if (!ImageFrame.isURLAllowed(url)) {
sender.sendMessage(ImageFrame.messageURLRestricted);
return;
}
if (HTTPRequestUtils.getContentSize(url) > ImageFrame.maxImageFileSize) {
sender.sendMessage(ImageFrame.messageImageOverMaxFileSize.replace("{Size}", ImageFrame.maxImageFileSize + ""));
throw new IOException("Image over max file size");
}
String finalUrl = url;
creationTask = ImageFrame.imageMapCreationTaskManager.enqueue(owner, name, width, height, () -> MinecraftURLOverlayImageMap.create(ImageFrame.imageMapManager, name, finalUrl, mapViews, width, height, ditheringType, player.getUniqueId()).get());
ImageMap imageMap = creationTask.get();
ImageFrame.imageMapManager.addMap(imageMap);
sender.sendMessage(ImageFrame.messageImageMapCreated);
creationTask.complete(ImageFrame.messageImageMapCreated);
} catch (ImageUploadManager.LinkTimeoutException e) {
sender.sendMessage(ImageFrame.messageUploadExpired);
} catch (ImageMapCreationTaskManager.EnqueueRejectedException e) {
sender.sendMessage(ImageFrame.messageImageMapAlreadyQueued);
} catch (Exception e) {
@@ -755,11 +779,26 @@ public class Commands implements CommandExecutor, TabCompleter {
} else {
imageType = imageType.trim();
}
if (!ImageFrame.isURLAllowed(url)) {
sender.sendMessage(ImageFrame.messageURLRestricted);
return;
}
if (imageType.equals(MapUtils.GIF_CONTENT_TYPE) == urlImageMap.requiresAnimationService()) {
String oldUrl = urlImageMap.getUrl();
if (url != null) {
urlImageMap.setUrl(url);
}
if (ImageFrame.uploadServiceEnabled && urlImageMap.getUrl().equalsIgnoreCase("upload")) {
UUID user = !(sender instanceof Player) ? ImageMap.CONSOLE_CREATOR : ((Player) sender).getUniqueId();
PendingUpload pendingUpload = ImageFrame.imageUploadManager.newPendingUpload(user);
Scheduler.runTaskLaterAsynchronously(ImageFrame.plugin, () -> sender.sendMessage(ImageFrame.messageUploadLink.replace("{URL}", pendingUpload.getUrl(ImageFrame.uploadServiceHost, user))), 2);
String newUrl = pendingUpload.getFileBlocking().toURI().toURL().toString();
if (HTTPRequestUtils.getContentSize(newUrl) > ImageFrame.maxImageFileSize) {
sender.sendMessage(ImageFrame.messageImageOverMaxFileSize.replace("{Size}", ImageFrame.maxImageFileSize + ""));
throw new IOException("Image over max file size");
}
urlImageMap.setUrl(newUrl);
}
if (ditheringType != null) {
urlImageMap.setDitheringType(ditheringType);
}
@@ -781,6 +820,8 @@ public class Commands implements CommandExecutor, TabCompleter {
imageMap.update();
sender.sendMessage(ImageFrame.messageImageMapRefreshed);
}
} catch (ImageUploadManager.LinkTimeoutException e) {
sender.sendMessage(ImageFrame.messageUploadExpired);
} catch (Exception e) {
sender.sendMessage(ImageFrame.messageUnableToLoadMap);
e.printStackTrace();
@@ -1543,6 +1584,9 @@ public class Commands implements CommandExecutor, TabCompleter {
if (sender.hasPermission("imageframe.refresh")) {
if ("refresh".equalsIgnoreCase(args[0])) {
tab.add("[url]");
if (ImageFrame.uploadServiceEnabled && "upload".startsWith(args[1].toLowerCase())) {
tab.add("upload");
}
tab.addAll(ImageMapUtils.getImageMapNameSuggestions(sender, args[1]));
for (String ditheringType : DitheringType.values().keySet()) {
if (ditheringType.startsWith(args[1].toLowerCase())) {
@@ -1660,11 +1704,17 @@ public class Commands implements CommandExecutor, TabCompleter {
if (sender.hasPermission("imageframe.create")) {
if ("create".equalsIgnoreCase(args[0])) {
tab.add("<url>");
if (ImageFrame.uploadServiceEnabled && "upload".startsWith(args[2].toLowerCase())) {
tab.add("upload");
}
}
}
if (sender.hasPermission("imageframe.overlay")) {
if ("overlay".equalsIgnoreCase(args[0])) {
tab.add("<url>");
if (ImageFrame.uploadServiceEnabled && "upload".startsWith(args[2].toLowerCase())) {
tab.add("upload");
}
}
}
if (sender.hasPermission("imageframe.clone")) {
@@ -1687,6 +1737,9 @@ public class Commands implements CommandExecutor, TabCompleter {
ImageMap imageMap = ImageMapUtils.getFromPlayerPrefixedName(sender, args[1]);
if (imageMap != null) {
tab.add("[url]");
if (ImageFrame.uploadServiceEnabled && "upload".startsWith(args[2].toLowerCase())) {
tab.add("upload");
}
for (String ditheringType : DitheringType.values().keySet()) {
if (ditheringType.startsWith(args[2].toLowerCase())) {
tab.add(ditheringType);
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -46,6 +46,7 @@ import com.loohp.imageframe.updater.Updater;
import com.loohp.imageframe.utils.ChatColorUtils;
import com.loohp.imageframe.utils.MCVersion;
import com.loohp.imageframe.utils.ModernEventsUtils;
import com.loohp.imageframe.upload.ImageUploadManager;
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@@ -138,6 +139,8 @@ public class ImageFrame extends JavaPlugin {
public static String messageMarkersLimitReached;
public static String messageInvalidOverlayMap;
public static String messageGivenInvisibleFrame;
public static String messageUploadLink;
public static String messageUploadExpired;
public static SimpleDateFormat dateFormat;
@@ -167,6 +170,10 @@ public class ImageFrame extends JavaPlugin {
public static boolean handleAnimatedMapsOnMainThread;
public static boolean sendAnimatedMapsOnMainThread;
public static boolean uploadServiceEnabled;
public static String uploadServiceHost;
public static int uploadServicePort;
public static int invisibleFrameMaxConversionsPerSplash;
public static boolean invisibleFrameGlowEmptyFrames;
@@ -179,11 +186,15 @@ public class ImageFrame extends JavaPlugin {
public static RateLimitedPacketSendingManager rateLimitedPacketSendingManager;
public static InvisibleFrameManager invisibleFrameManager;
public static ImageMapCreationTaskManager imageMapCreationTaskManager;
public static ImageUploadManager imageUploadManager;
public static boolean isURLAllowed(String link) {
if (!restrictImageUrlEnabled) {
return true;
}
if ("upload".equals(link) || imageUploadManager.wasUploaded(link)) {
return true;
}
try {
URL url = new URL(link);
return restrictImageUrls.stream().anyMatch(whitelisted -> {
@@ -315,6 +326,7 @@ public class ImageFrame extends JavaPlugin {
Scheduler.runTaskAsynchronously(this, () -> imageMapManager.loadMaps());
invisibleFrameManager = new InvisibleFrameManager();
imageMapCreationTaskManager = new ImageMapCreationTaskManager(ImageFrame.parallelProcessingLimit);
imageUploadManager = new ImageUploadManager(updaterEnabled, uploadServicePort);
if (isPluginEnabled("PlaceholderAPI")) {
new Placeholders().register();
@@ -337,6 +349,9 @@ public class ImageFrame extends JavaPlugin {
if (combinedMapItemHandler != null) {
combinedMapItemHandler.close();
}
if (imageUploadManager != null) {
imageUploadManager.close();
}
getServer().getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] ImageFrame has been Disabled!");
}
@@ -405,6 +420,8 @@ public class ImageFrame extends JavaPlugin {
messageMarkersLimitReached = ChatColorUtils.translateAlternateColorCodes('&', config.getConfiguration().getString("Messages.Markers.LimitReached"));
messageInvalidOverlayMap = ChatColorUtils.translateAlternateColorCodes('&', config.getConfiguration().getString("Messages.InvalidOverlayMap"));
messageGivenInvisibleFrame = ChatColorUtils.translateAlternateColorCodes('&', config.getConfiguration().getString("Messages.GivenInvisibleFrame"));
messageUploadLink = ChatColorUtils.translateAlternateColorCodes('&', config.getConfiguration().getString("Messages.UploadLink"));
messageUploadExpired = ChatColorUtils.translateAlternateColorCodes('&', config.getConfiguration().getString("Messages.UploadExpired"));
dateFormat = new SimpleDateFormat(config.getConfiguration().getString("Messages.DateFormat"));
@@ -462,6 +479,10 @@ public class ImageFrame extends JavaPlugin {
handleAnimatedMapsOnMainThread = config.getConfiguration().getBoolean("Settings.HandleAnimatedMapsOnMainThread");
sendAnimatedMapsOnMainThread = config.getConfiguration().getBoolean("Settings.SendAnimatedMapsOnMainThread");
uploadServiceEnabled = config.getConfiguration().getBoolean("UploadService.Enabled");
uploadServiceHost = config.getConfiguration().getString("UploadService.Host");
uploadServicePort = config.getConfiguration().getInt("UploadService.Port");
invisibleFrameMaxConversionsPerSplash = config.getConfiguration().getInt("InvisibleFrame.MaxConversionsPerSplash");
invisibleFrameGlowEmptyFrames = config.getConfiguration().getBoolean("InvisibleFrame.GlowEmptyFrames");
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of InteractiveChatDiscordSrvAddon.
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -84,14 +84,24 @@ public class Charts {
metrics.addCustomChart(new Metrics.SingleLineChart("item_frames_made_invisible_in_last_interval", new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return (int) ImageFrame.invisibleFrameManager.getItemFramesMadeInvisible().getAndSet(0);
long value = ImageFrame.invisibleFrameManager.getItemFramesMadeInvisible().getAndSet(0);
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
}
}));
metrics.addCustomChart(new Metrics.SingleLineChart("invisible_item_frames_placed_in_last_interval", new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return (int) ImageFrame.invisibleFrameManager.getInvisibleItemFramesPlaced().getAndSet(0);
long value = ImageFrame.invisibleFrameManager.getInvisibleItemFramesPlaced().getAndSet(0);
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
}
}));
metrics.addCustomChart(new Metrics.SingleLineChart("embedded_service_image_uploaded_in_last_interval", new Callable<Integer>() {
@Override
public Integer call() throws Exception {
long value = ImageFrame.imageUploadManager.getImagesUploadedCounter().getAndSet(0);
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
}
}));
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame (1) (com.loohp).
* This file is part of ImageFrame.
*
* Copyright (C) 2024. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2024. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -273,47 +273,46 @@ public class CombinedMapItemHandler implements Listener, AutoCloseable {
if (player != null && !entityDamageChecking.add(player)) {
return;
}
ItemStack itemStack = itemFrame.getItem();
if (itemStack == null || !itemStack.getType().equals(Material.FILLED_MAP)) {
entityDamageChecking.remove(player);
return;
}
CombinedMapItemInfo mapItemInfo = NMS.getInstance().getCombinedMapItemInfo(itemStack);
if (mapItemInfo == null || !mapItemInfo.hasPlacement()) {
entityDamageChecking.remove(player);
return;
}
CombinedMapItemInfo.PlacementInfo placement = mapItemInfo.getPlacement();
int id = mapItemInfo.getImageMapIndex();
float yaw = placement.getYaw();
UUID uuid = placement.getUniqueId();
ImageMap imageMap = ImageFrame.imageMapManager.getFromImageId(id);
if (imageMap == null) {
entityDamageChecking.remove(player);
return;
}
ItemFrameSelectionManager.SelectedItemFrameResult selection = findItemFrames(itemFrame, yaw, imageMap.getWidth(), imageMap.getHeight(), item -> {
if (item == null || !item.getType().equals(Material.FILLED_MAP)) {
return false;
try {
ItemStack itemStack = itemFrame.getItem();
if (itemStack == null || !itemStack.getType().equals(Material.FILLED_MAP)) {
return;
}
CombinedMapItemInfo info = NMS.getInstance().getCombinedMapItemInfo(item);
if (info == null || !info.hasPlacement()) {
return false;
CombinedMapItemInfo mapItemInfo = NMS.getInstance().getCombinedMapItemInfo(itemStack);
if (mapItemInfo == null || !mapItemInfo.hasPlacement()) {
return;
}
return info.getImageMapIndex() == id && info.getPlacement().getUniqueId().equals(uuid);
});
if (selection == null) {
CombinedMapItemInfo.PlacementInfo placement = mapItemInfo.getPlacement();
int id = mapItemInfo.getImageMapIndex();
float yaw = placement.getYaw();
UUID uuid = placement.getUniqueId();
ImageMap imageMap = ImageFrame.imageMapManager.getFromImageId(id);
if (imageMap == null) {
return;
}
ItemFrameSelectionManager.SelectedItemFrameResult selection = findItemFrames(itemFrame, yaw, imageMap.getWidth(), imageMap.getHeight(), item -> {
if (item == null || !item.getType().equals(Material.FILLED_MAP)) {
return false;
}
CombinedMapItemInfo info = NMS.getInstance().getCombinedMapItemInfo(item);
if (info == null || !info.hasPlacement()) {
return false;
}
return info.getImageMapIndex() == id && info.getPlacement().getUniqueId().equals(uuid);
});
if (selection == null) {
return;
}
List<ItemFrame> itemFrames = selection.getItemFrames();
if (player == null || itemFrames.stream().allMatch(each -> PlayerUtils.isDamageAllowed(player, each))) {
itemFrames.forEach(each -> each.setItem(null, false));
itemFrame.setItem(getCombinedMap(imageMap), false);
} else {
player.sendMessage(ImageFrame.messageItemFrameOccupied);
}
} finally {
entityDamageChecking.remove(player);
return;
}
List<ItemFrame> itemFrames = selection.getItemFrames();
if (player == null || itemFrames.stream().allMatch(each -> PlayerUtils.isDamageAllowed(player, each))) {
itemFrames.forEach(each -> each.setItem(null, false));
itemFrame.setItem(getCombinedMap(imageMap), false);
} else {
player.sendMessage(ImageFrame.messageItemFrameOccupied);
}
entityDamageChecking.remove(player);
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2024. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2024. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2024. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2024. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -137,6 +137,7 @@ public class ImageMapManager implements AutoCloseable {
maps.put(map.getImageIndex(), map);
for (MapView mapView : map.getMapViews()) {
mapsByView.put(mapView, map);
deletedMapIds.remove(mapView.getId());
}
try {
map.save();
@@ -147,6 +148,7 @@ public class ImageMapManager implements AutoCloseable {
}
throw e;
}
saveDeletedMaps();
}
public boolean hasMap(int imageIndex) {
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of InteractionVisualizer.
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -0,0 +1,303 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.loohp.imageframe.upload;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalNotification;
import com.loohp.imageframe.ImageFrame;
import com.loohp.imageframe.objectholders.Scheduler;
import com.loohp.imageframe.utils.FileUtils;
import com.loohp.imageframe.utils.JarUtils;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.apache.commons.fileupload.MultipartStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
public class ImageUploadManager implements AutoCloseable {
public static final Duration EXPIRATION = Duration.ofMinutes(5);
private final HttpServer server;
private final File webRootDir;
private final File uploadDir;
private final Map<UUID, PendingUpload> pendingUploads;
private final AtomicLong imagesUploadedCounter;
public ImageUploadManager(boolean enabled, int port) {
try {
this.webRootDir = new File(ImageFrame.plugin.getDataFolder(), "upload/web");
this.uploadDir = new File(ImageFrame.plugin.getDataFolder(), "upload/images");
FileUtils.removeFolderRecursively(uploadDir);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
if (!webRootDir.exists()) {
webRootDir.mkdirs();
}
JarUtils.copyFolderFromJar("upload/web", ImageFrame.plugin.getDataFolder(), JarUtils.CopyOption.COPY_IF_NOT_EXIST);
Cache<UUID, PendingUpload> cache = CacheBuilder.newBuilder()
.expireAfterAccess(EXPIRATION)
.removalListener((RemovalNotification<UUID, PendingUpload> notification) -> {
if (notification.wasEvicted()) {
PendingUpload pendingUpload = notification.getValue();
if (pendingUpload != null) {
pendingUpload.getFuture().completeExceptionally(new LinkTimeoutException());
}
}
})
.build();
this.pendingUploads = cache.asMap();
this.imagesUploadedCounter = new AtomicLong(0);
if (enabled) {
this.server = HttpServer.create(new InetSocketAddress(port), 0);
this.server.createContext("/", new FileHandler());
this.server.createContext("/upload", new UploadHandler());
this.server.setExecutor(null);
this.server.start();
} else {
this.server = null;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public PendingUpload newPendingUpload(UUID user) {
PendingUpload existing = pendingUploads.remove(user);
if (existing != null) {
existing.getFuture().completeExceptionally(new LinkTimeoutException());
}
PendingUpload pendingUpload = PendingUpload.create();
pendingUploads.put(user, pendingUpload);
return pendingUpload;
}
public AtomicLong getImagesUploadedCounter() {
return imagesUploadedCounter;
}
public boolean wasUploaded(String url) {
try {
File file = Paths.get(new URL(url).toURI()).toFile();
return uploadDir.equals(file.getParentFile());
} catch (Exception e) {
return false;
}
}
public boolean isEnabled() {
return server != null;
}
@Override
public void close() {
if (server != null) {
server.stop(0);
}
}
private class FileHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equalsIgnoreCase("GET")) {
exchange.sendResponseHeaders(405, -1); // Method Not Allowed
return;
}
if (!webRootDir.exists()) {
webRootDir.mkdirs();
}
File file = resolvePath(webRootDir.toPath().toAbsolutePath(), Paths.get("." + exchange.getRequestURI().getPath())).toFile();
byte[] bytes;
if (file.exists()) {
if (file.isDirectory()) {
bytes = Files.readAllBytes(new File(file, "index.html").toPath());
} else {
bytes = Files.readAllBytes(file.toPath());
}
} else {
bytes = Files.readAllBytes(new File(webRootDir, "index.html").toPath());
}
exchange.getResponseHeaders().set("Content-Type", "text/html");
exchange.sendResponseHeaders(200, bytes.length);
exchange.getResponseBody().write(bytes);
exchange.getResponseBody().close();
}
private Path resolvePath(Path baseDirPath, Path userPath) {
Path resolvedPath = baseDirPath.resolve(userPath).normalize();
if (!resolvedPath.startsWith(baseDirPath)) {
return baseDirPath;
}
return resolvedPath;
}
}
private class UploadHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
if (!exchange.getRequestMethod().equalsIgnoreCase("POST")) {
exchange.sendResponseHeaders(405, -1);
return;
}
// Extract query parameters
String query = exchange.getRequestURI().getQuery();
Map<String, String> queryParams = parseQueryParams(query);
String user = queryParams.get("user");
String id = queryParams.get("id");
PendingUpload pendingUpload = findPendingUpload(user, id);
if (pendingUpload == null) {
sendResponse(exchange, 400, "{\"error\":\"Invalid or missing user or id\"}");
return;
}
List<String> contentType = Arrays.asList(exchange.getRequestHeaders().getFirst("Content-Type").split(";"));
if (!contentType.get(0).trim().equals("multipart/form-data")) {
sendResponse(exchange, 400, "{\"error\":\"Invalid content type\"}");
return;
}
byte[] boundary = contentType.stream()
.map(s -> s.trim())
.filter(s -> s.startsWith("boundary="))
.findFirst()
.map(s -> s.substring("boundary=".length()).getBytes(StandardCharsets.UTF_8))
.orElse(null);
if (boundary == null) {
sendResponse(exchange, 400, "{\"error\":\"Invalid multipart boundary\"}");
return;
}
SizeLimitedByteArrayOutputStream output = new SizeLimitedByteArrayOutputStream(ImageFrame.maxImageFileSize);
try (InputStream inputStream = exchange.getRequestBody()) {
MultipartStream multipartStream = new MultipartStream(inputStream, boundary, 2048, null);
boolean nextPart = multipartStream.skipPreamble();
while (nextPart) {
List<String> headers = Arrays.asList(multipartStream.readHeaders().split(";"));
String name = headers.stream()
.map(s -> s.trim()).filter(s -> s.startsWith("name="))
.findFirst()
.map(s -> s.substring("name=".length()))
.orElse(null);
if ("image".equals(name) || "\"image\"".equals(name)) {
multipartStream.readBodyData(output);
break;
} else {
multipartStream.discardBodyData();
nextPart = multipartStream.readBoundary();
}
}
}
byte[] fileData = output.toByteArray();
// Ensure upload directory exists
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
// Save the file with UUID as the filename
File outputFile = new File(uploadDir, id + ".png");
Files.write(outputFile.toPath(), fileData);
imagesUploadedCounter.incrementAndGet();
pendingUploads.remove(UUID.fromString(user));
pendingUpload.getFuture().complete(outputFile);
Scheduler.runTaskLaterAsynchronously(ImageFrame.plugin, () -> outputFile.delete(), EXPIRATION.toMillis() / 50);
// Send response
sendResponse(exchange, 200, "{\"message\":\"File uploaded successfully\"}");
}
// Parse query parameters from URL
private Map<String, String> parseQueryParams(String query) {
Map<String, String> map = new HashMap<>();
if (query != null) {
String[] pairs = query.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
if (keyValue.length == 2) {
map.put(keyValue[0], keyValue[1]);
}
}
}
return map;
}
private PendingUpload findPendingUpload(String user, String id) {
try {
PendingUpload pendingUpload = pendingUploads.get(UUID.fromString(user));
if (!pendingUpload.getId().equals(UUID.fromString(id))) {
return null;
}
return pendingUpload;
} catch (Exception e) {
return null;
}
}
// Send JSON response
private void sendResponse(HttpExchange exchange, int statusCode, String message) throws IOException {
exchange.getResponseHeaders().set("Content-Type", "application/json");
exchange.sendResponseHeaders(statusCode, message.length());
exchange.getResponseBody().write(message.getBytes());
exchange.getResponseBody().close();
}
}
public static class LinkTimeoutException extends Exception {
public LinkTimeoutException() {
super();
}
public LinkTimeoutException(String message) {
super(message);
}
public LinkTimeoutException(String message, Throwable cause) {
super(message, cause);
}
public LinkTimeoutException(Throwable cause) {
super(cause);
}
}
}
@@ -0,0 +1,76 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.loohp.imageframe.upload;
import org.bukkit.Bukkit;
import java.io.File;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class PendingUpload {
public static PendingUpload create() {
return new PendingUpload(UUID.randomUUID(), System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5), new CompletableFuture<>());
}
private final UUID id;
private final long expire;
private final CompletableFuture<File> future;
private PendingUpload(UUID id, long expire, CompletableFuture<File> future) {
this.id = id;
this.expire = expire;
this.future = future;
}
public UUID getId() {
return id;
}
public long getExpire() {
return expire;
}
protected CompletableFuture<File> getFuture() {
return future;
}
public File getFileBlocking() throws InterruptedException, ImageUploadManager.LinkTimeoutException {
try {
return future.get(expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
} catch (TimeoutException | ExecutionException e) {
throw new ImageUploadManager.LinkTimeoutException();
}
}
public String getUrl(String host, UUID user) {
String name = Bukkit.getOfflinePlayer(user).getName();
if (name == null) {
return host + "?user=" + user + "&id=" + id + "&expire=" + expire;
} else {
return host + "?user=" + user + "&id=" + id + "&expire=" + expire + "&name=" + name;
}
}
}
@@ -0,0 +1,51 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program 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.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.loohp.imageframe.upload;
import java.io.ByteArrayOutputStream;
public class SizeLimitedByteArrayOutputStream extends ByteArrayOutputStream {
private final long maxSize;
public SizeLimitedByteArrayOutputStream(long maxSize) {
this.maxSize = maxSize;
}
private void ensureSize(int added) {
if ((size() + added) > maxSize) {
throw new IllegalStateException("Size exceeded max size of " + maxSize + " bytes");
}
}
@Override
public synchronized void write(int b) {
ensureSize(1);
super.write(b);
}
@Override
public synchronized void write(byte[] b, int off, int len) {
ensureSize(len);
super.write(b, off, len);
}
}
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2023. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2023. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2024. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2024. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1,8 +1,8 @@
/*
* This file is part of ImageFrame.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
* Copyright (C) 2025. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2025. Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more