move capturing of screenshots to ImageCapture utility

Extracts the logic for caturing/saving/uploading
images from the screenshot plugin to a separate
utility named ImageCapture.
The Imgur class were made inner classes of ImageCapture
since the only location they see use is there.
ImageUploadStyle and TransferableBufferedImage were
also moved to ulities to allow ImageCapture and other
plugins to use them.
This commit is contained in:
Alexsuperfly
2019-11-26 19:59:28 -05:00
committed by Adam
parent bbdb812a4b
commit 437a1a2286
9 changed files with 271 additions and 249 deletions

View File

@@ -48,6 +48,7 @@ public class RuneLiteProperties
private static final String JAV_CONFIG_BACKUP = "runelite.jav_config_backup";
private static final String PLUGINHUB_BASE = "runelite.pluginhub.url";
private static final String PLUGINHUB_VERSION = "runelite.pluginhub.version";
private static final String IMGUR_CLIENT_ID = "runelite.imgur.client.id";
private static final Properties properties = new Properties();
@@ -139,4 +140,9 @@ public class RuneLiteProperties
String version = System.getProperty(PLUGINHUB_VERSION, properties.getProperty(PLUGINHUB_VERSION));
return HttpUrl.parse(properties.get(PLUGINHUB_BASE) + "/" + version);
}
public static String getImgurClientId()
{
return properties.getProperty(IMGUR_CLIENT_ID);
}
}

View File

@@ -28,6 +28,7 @@ import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Keybind;
import net.runelite.client.util.ImageUploadStyle;
@ConfigGroup("screenshot")
public interface ScreenshotConfig extends Config
@@ -115,9 +116,9 @@ public interface ScreenshotConfig extends Config
description = "Configures whether or not screenshots are uploaded to Imgur, or placed on your clipboard",
position = 7
)
default UploadStyle uploadScreenshot()
default ImageUploadStyle uploadScreenshot()
{
return UploadStyle.NEITHER;
return ImageUploadStyle.NEITHER;
}
@ConfigItem(

View File

@@ -31,26 +31,14 @@ import com.google.inject.Provides;
import java.awt.Desktop;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
import java.util.EnumSet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.swing.SwingUtilities;
import lombok.AccessLevel;
@@ -62,7 +50,6 @@ import net.runelite.api.GameState;
import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.api.SpriteID;
import net.runelite.api.WorldType;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.PlayerDeath;
@@ -86,24 +73,15 @@ import net.runelite.client.game.SpriteManager;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.screenshot.imgur.ImageUploadRequest;
import net.runelite.client.plugins.screenshot.imgur.ImageUploadResponse;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.DrawManager;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.util.HotkeyListener;
import net.runelite.client.util.ImageCapture;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.Text;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@PluginDescriptor(
name = "Screenshot",
@@ -113,12 +91,6 @@ import okhttp3.Response;
@Slf4j
public class ScreenshotPlugin extends Plugin
{
private static final String IMGUR_CLIENT_ID = "30d71e5f6860809";
private static final HttpUrl IMGUR_IMAGE_UPLOAD_URL = HttpUrl.parse("https://api.imgur.com/3/image");
private static final MediaType JSON = MediaType.parse("application/json");
private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
private static final Pattern NUMBER_PATTERN = Pattern.compile("([0-9]+)");
private static final Pattern LEVEL_UP_PATTERN = Pattern.compile(".*Your ([a-zA-Z]+) (?:level is|are)? now (\\d+)\\.");
private static final Pattern BOSSKILL_MESSAGE_PATTERN = Pattern.compile("Your (.+) kill count is: <col=ff0000>(\\d+)</col>.");
@@ -129,14 +101,6 @@ public class ScreenshotPlugin extends Plugin
"You feel something weird sneaking into your backpack",
"You have a funny feeling like you would have been followed");
static String format(Date date)
{
synchronized (TIME_FORMAT)
{
return TIME_FORMAT.format(date);
}
}
private String clueType;
private Integer clueNumber;
@@ -183,6 +147,9 @@ public class ScreenshotPlugin extends Plugin
@Inject
private SpriteManager spriteManager;
@Inject
private ImageCapture imageCapture;
@Getter(AccessLevel.PACKAGE)
private BufferedImage reportButton;
@@ -193,7 +160,7 @@ public class ScreenshotPlugin extends Plugin
@Override
public void hotkeyPressed()
{
takeScreenshot(format(new Date()));
takeScreenshot("");
}
};
@@ -216,7 +183,7 @@ public class ScreenshotPlugin extends Plugin
.tab(false)
.tooltip("Take screenshot")
.icon(iconImage)
.onClick(() -> takeScreenshot(format(new Date())))
.onClick(() -> takeScreenshot(""))
.popup(ImmutableMap
.<String, Runnable>builder()
.put("Open screenshot folder...", () ->
@@ -284,11 +251,11 @@ public class ScreenshotPlugin extends Plugin
Player player = playerDeath.getPlayer();
if (player == client.getLocalPlayer() && config.screenshotPlayerDeath())
{
takeScreenshot("Death " + format(new Date()));
takeScreenshot("Death");
}
else if ((player.isClanMember() || player.isFriend()) && config.screenshotFriendDeath() && player.getCanvasTilePoly() != null)
{
takeScreenshot("Death " + player.getName() + " " + format(new Date()));
takeScreenshot("Death " + player.getName());
}
}
@@ -299,7 +266,7 @@ public class ScreenshotPlugin extends Plugin
{
final Player player = playerLootReceived.getPlayer();
final String name = player.getName();
String fileName = "Kill " + name + " " + format(new Date());
String fileName = "Kill " + name;
takeScreenshot(fileName);
}
}
@@ -367,7 +334,7 @@ public class ScreenshotPlugin extends Plugin
if (config.screenshotPet() && PET_MESSAGES.stream().anyMatch(chatMessage::contains))
{
String fileName = "Pet " + format(new Date());
String fileName = "Pet";
takeScreenshot(fileName);
}
@@ -389,7 +356,7 @@ public class ScreenshotPlugin extends Plugin
if (m.matches())
{
String valuableDropName = m.group(1);
String fileName = "Valuable drop " + valuableDropName + " " + format(new Date());
String fileName = "Valuable drop " + valuableDropName;
takeScreenshot(fileName);
}
}
@@ -400,7 +367,7 @@ public class ScreenshotPlugin extends Plugin
if (m.matches())
{
String untradeableDropName = m.group(1);
String fileName = "Untradeable drop " + untradeableDropName + " " + format(new Date());
String fileName = "Untradeable drop " + untradeableDropName;
takeScreenshot(fileName);
}
}
@@ -615,120 +582,7 @@ public class ScreenshotPlugin extends Plugin
// Draw the game onto the screenshot
graphics.drawImage(image, gameOffsetX, gameOffsetY, null);
File playerFolder;
if (client.getLocalPlayer() != null && client.getLocalPlayer().getName() != null)
{
final EnumSet<WorldType> worldTypes = client.getWorldType();
String playerDir = client.getLocalPlayer().getName();
if (worldTypes.contains(WorldType.DEADMAN))
{
playerDir += "-Deadman";
}
else if (worldTypes.contains(WorldType.LEAGUE))
{
playerDir += "-League";
}
playerFolder = new File(SCREENSHOT_DIR, playerDir);
}
else
{
playerFolder = SCREENSHOT_DIR;
}
playerFolder.mkdirs();
try
{
File screenshotFile = new File(playerFolder, fileName + ".png");
// To make sure that screenshots don't get overwritten, check if file exists,
// and if it does create file with same name and suffix.
int i = 1;
while (screenshotFile.exists())
{
screenshotFile = new File(playerFolder, fileName + String.format("(%d)", i++) + ".png");
}
ImageIO.write(screenshot, "PNG", screenshotFile);
UploadStyle uploadStyle = config.uploadScreenshot();
if (uploadStyle == UploadStyle.IMGUR)
{
uploadScreenshot(screenshotFile);
}
else if (uploadStyle == UploadStyle.CLIPBOARD)
{
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
TransferableBufferedImage transferableBufferedImage = new TransferableBufferedImage(screenshot);
clipboard.setContents(transferableBufferedImage, null);
if (config.notifyWhenTaken())
{
notifier.notify("A screenshot was saved and inserted into your clipboard!", TrayIcon.MessageType.INFO);
}
}
else if (config.notifyWhenTaken())
{
notifier.notify("A screenshot was saved to " + screenshotFile, TrayIcon.MessageType.INFO);
}
}
catch (IOException ex)
{
log.warn("error writing screenshot", ex);
}
}
/**
* Uploads a screenshot to the Imgur image-hosting service,
* and copies the image link to the clipboard.
*
* @param screenshotFile Image file to upload.
* @throws IOException Thrown if the file cannot be read.
*/
private void uploadScreenshot(File screenshotFile) throws IOException
{
String json = RuneLiteAPI.GSON.toJson(new ImageUploadRequest(screenshotFile));
Request request = new Request.Builder()
.url(IMGUR_IMAGE_UPLOAD_URL)
.addHeader("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.post(RequestBody.create(JSON, json))
.build();
RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(Call call, IOException ex)
{
log.warn("error uploading screenshot", ex);
}
@Override
public void onResponse(Call call, Response response) throws IOException
{
try (InputStream in = response.body().byteStream())
{
ImageUploadResponse imageUploadResponse = RuneLiteAPI.GSON
.fromJson(new InputStreamReader(in), ImageUploadResponse.class);
if (imageUploadResponse.isSuccess())
{
String link = imageUploadResponse.getData().getLink();
StringSelection selection = new StringSelection(link);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(selection, selection);
if (config.notifyWhenTaken())
{
notifier.notify("A screenshot was uploaded and inserted into your clipboard!", TrayIcon.MessageType.INFO);
}
}
}
}
});
imageCapture.takeScreenshot(screenshot, fileName, config.notifyWhenTaken(), config.uploadScreenshot());
}
@VisibleForTesting

View File

@@ -1,44 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot.imgur;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Base64;
import lombok.Data;
@Data
public class ImageUploadRequest
{
private final String image;
private final String type;
public ImageUploadRequest(File imageFile) throws IOException
{
this.image = Base64.getEncoder().encodeToString(Files.readAllBytes(imageFile.toPath()));
this.type = "base64";
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot.imgur;
import lombok.Data;
@Data
public class ImageUploadResponse
{
private Data data;
private boolean success;
@lombok.Data
public static class Data
{
private String link;
}
}

View File

@@ -0,0 +1,244 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* Copyright (c) 2019, Alexsuperfly <https://github.com/Alexsuperfly>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.util;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.EnumSet;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.WorldType;
import net.runelite.client.Notifier;
import static net.runelite.client.RuneLite.SCREENSHOT_DIR;
import net.runelite.client.RuneLiteProperties;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@Slf4j
@Singleton
public class ImageCapture
{
private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
private static final HttpUrl IMGUR_IMAGE_UPLOAD_URL = HttpUrl.parse("https://api.imgur.com/3/image");
private static final MediaType JSON = MediaType.parse("application/json");
@Inject
private Client client;
@Inject
private Notifier notifier;
/**
* Saves a screenshot of the client window to the screenshot folder as a PNG,
* and optionally uploads it to an image-hosting service.
*
* @param screenshot BufferedImage to capture.
* @param fileName Filename to use, without file extension.
* @param notify Send a notification to the system tray when the image is captured.
* @param imageUploadStyle which method to use to upload the screenshot (Imgur or directly to clipboard).
*/
public void takeScreenshot(BufferedImage screenshot, String fileName, boolean notify, ImageUploadStyle imageUploadStyle)
{
if (client.getGameState() == GameState.LOGIN_SCREEN)
{
// Prevent the screenshot from being captured
log.info("Login screenshot prevented");
return;
}
File playerFolder;
if (client.getLocalPlayer() != null && client.getLocalPlayer().getName() != null)
{
final EnumSet<WorldType> worldTypes = client.getWorldType();
String playerDir = client.getLocalPlayer().getName();
if (worldTypes.contains(WorldType.DEADMAN))
{
playerDir += "-Deadman";
}
else if (worldTypes.contains(WorldType.LEAGUE))
{
playerDir += "-League";
}
playerFolder = new File(SCREENSHOT_DIR, playerDir);
}
else
{
playerFolder = SCREENSHOT_DIR;
}
playerFolder.mkdirs();
fileName += " " + format(new Date());
try
{
File screenshotFile = new File(playerFolder, fileName + ".png");
// To make sure that screenshots don't get overwritten, check if file exists,
// and if it does create file with same name and suffix.
int i = 1;
while (screenshotFile.exists())
{
screenshotFile = new File(playerFolder, fileName + String.format("(%d)", i++) + ".png");
}
ImageIO.write(screenshot, "PNG", screenshotFile);
if (imageUploadStyle == ImageUploadStyle.IMGUR)
{
uploadScreenshot(screenshotFile, notify);
}
else if (imageUploadStyle == ImageUploadStyle.CLIPBOARD)
{
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
TransferableBufferedImage transferableBufferedImage = new TransferableBufferedImage(screenshot);
clipboard.setContents(transferableBufferedImage, null);
if (notify)
{
notifier.notify("A screenshot was saved and inserted into your clipboard!", TrayIcon.MessageType.INFO);
}
}
else if (notify)
{
notifier.notify("A screenshot was saved to " + screenshotFile, TrayIcon.MessageType.INFO);
}
}
catch (IOException ex)
{
log.warn("error writing screenshot", ex);
}
}
/**
* Uploads a screenshot to the Imgur image-hosting service,
* and copies the image link to the clipboard.
*
* @param screenshotFile Image file to upload.
* @throws IOException Thrown if the file cannot be read.
*/
private void uploadScreenshot(File screenshotFile, boolean notify) throws IOException
{
String json = RuneLiteAPI.GSON.toJson(new ImageUploadRequest(screenshotFile));
Request request = new Request.Builder()
.url(IMGUR_IMAGE_UPLOAD_URL)
.addHeader("Authorization", "Client-ID " + RuneLiteProperties.getImgurClientId())
.post(RequestBody.create(JSON, json))
.build();
RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback()
{
@Override
public void onFailure(Call call, IOException ex)
{
log.warn("error uploading screenshot", ex);
}
@Override
public void onResponse(Call call, Response response) throws IOException
{
try (InputStream in = response.body().byteStream())
{
ImageUploadResponse imageUploadResponse = RuneLiteAPI.GSON
.fromJson(new InputStreamReader(in), ImageUploadResponse.class);
if (imageUploadResponse.isSuccess())
{
String link = imageUploadResponse.getData().getLink();
StringSelection selection = new StringSelection(link);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(selection, selection);
if (notify)
{
notifier.notify("A screenshot was uploaded and inserted into your clipboard!", TrayIcon.MessageType.INFO);
}
}
}
}
});
}
private static String format(Date date)
{
synchronized (TIME_FORMAT)
{
return TIME_FORMAT.format(date);
}
}
@Data
private static class ImageUploadResponse
{
private Data data;
private boolean success;
@lombok.Data
private static class Data
{
private String link;
}
}
@Data
private static class ImageUploadRequest
{
private final String image;
private final String type;
ImageUploadRequest(File imageFile) throws IOException
{
this.image = Base64.getEncoder().encodeToString(Files.readAllBytes(imageFile.toPath()));
this.type = "base64";
}
}
}

View File

@@ -23,9 +23,9 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot;
package net.runelite.client.util;
public enum UploadStyle
public enum ImageUploadStyle
{
NEITHER,
IMGUR,

View File

@@ -24,7 +24,7 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.screenshot;
package net.runelite.client.util;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;

View File

@@ -12,4 +12,5 @@ runelite.dnschange.link=https://1.1.1.1/dns/
runelite.jav_config=http://oldschool.runescape.com/jav_config.ws
runelite.jav_config_backup=http://static.runelite.net/jav_config.ws
runelite.pluginhub.url=https://repo.runelite.net/plugins
runelite.pluginhub.version=${project.version}
runelite.pluginhub.version=${project.version}
runelite.imgur.client.id=30d71e5f6860809