/*
* Copyright (C) 2022 NotEnoughUpdates contributors
*
* This file is part of NotEnoughUpdates.
*
* NotEnoughUpdates is free software: you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* NotEnoughUpdates 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with NotEnoughUpdates. If not, see .
*/
package io.github.moulberry.notenoughupdates.infopanes;
import info.bliki.htmlcleaner.TagNode;
import info.bliki.wiki.filter.Encoder;
import info.bliki.wiki.model.Configuration;
import info.bliki.wiki.model.ImageFormat;
import info.bliki.wiki.model.WikiModel;
import info.bliki.wiki.tags.HTMLBlockTag;
import info.bliki.wiki.tags.HTMLTag;
import info.bliki.wiki.tags.IgnoreTag;
import io.github.moulberry.notenoughupdates.NEUManager;
import io.github.moulberry.notenoughupdates.NEUOverlay;
import io.github.moulberry.notenoughupdates.util.AllowEmptyHTMLTag;
import io.github.moulberry.notenoughupdates.util.Utils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
public class HTMLInfoPane extends TextInfoPane {
private static final WikiModel wikiModel;
private final int ZOOM_FACTOR = 2;
private final int IMAGE_WIDTH = 400;
private final int EXT_WIDTH = 100;
private ResourceLocation imageTexture = null;
private BufferedImage imageTemp = null;
private int imageHeight = 0;
private int imageWidth = 0;
private float xMin = 0;
private int mouseOffset = 0;
private boolean selected = false;
private static boolean hasAttemptedDownload = false;
/*
* Creates a wiki model and sets the configuration to work with hypixel-skyblock wikia.
*/
static {
Configuration conf = new Configuration();
conf.addTokenTag("img", new HTMLTag("img"));
conf.addTokenTag("code", new HTMLTag("code"));
conf.addTokenTag("span", new AllowEmptyHTMLTag("span"));
conf.addTokenTag("table", new HTMLBlockTag("table", Configuration.SPECIAL_BLOCK_TAGS + "span|"));
conf.addTokenTag("infobox", new IgnoreTag("infobox"));
conf.addTokenTag("tabber", new IgnoreTag("tabber"));
conf.addTokenTag("kbd", new HTMLTag("kbd"));
conf.addTokenTag("td", new AllowEmptyHTMLTag("td"));
conf.addTokenTag("tbody", new AllowEmptyHTMLTag("tbody"));
conf.addTokenTag("style", new AllowEmptyHTMLTag("style"));
conf.addTokenTag("article", new AllowEmptyHTMLTag("article"));
conf.addTokenTag("section", new AllowEmptyHTMLTag("section"));
conf.addTokenTag("link", new AllowEmptyHTMLTag("link"));
conf.addTokenTag("wbr", new AllowEmptyHTMLTag("wbr"));
conf.addTokenTag("dl", new AllowEmptyHTMLTag("dl"));
conf.addTokenTag("dd", new AllowEmptyHTMLTag("dd"));
conf.addTokenTag("dt", new AllowEmptyHTMLTag("dt"));
wikiModel = new WikiModel(conf, "https://hypixel-skyblock.fandom.com/wiki/Special:Filepath/${image}",
"https://hypixel-skyblock.fandom.com/wiki/${title}"
) {
{
TagNode.addAllowedAttribute("style");
TagNode.addAllowedAttribute("src");
}
protected String createImageName(ImageFormat imageFormat) {
String imageName = imageFormat.getFilename();
if (imageName.endsWith(".svg")) {
imageName += ".png";
}
imageName = Encoder.encodeUrl(imageName);
if (replaceColon()) {
imageName = imageName.replace(':', '/');
}
return imageName;
}
public void parseInternalImageLink(String imageNamespace, String rawImageLink) {
rawImageLink = rawImageLink.replaceFirst("\\|x([0-9]+)px", "\\|$1x$1px");
if (!rawImageLink.split("\\|")[0].toLowerCase(Locale.ROOT).endsWith(".jpg")) {
super.parseInternalImageLink(imageNamespace, rawImageLink);
}
}
};
}
/**
* Takes a wiki url, uses NEUManager#getWebFile to download the web file and passed that in to #createFromWiki
*/
public static CompletableFuture createFromWikiUrl(
NEUOverlay overlay,
NEUManager manager,
String name,
String wikiUrl
) {
return manager.getWebFile(wikiUrl).thenApply(f -> {
if (f == null) {
return new HTMLInfoPane(overlay, manager, "error", "error", "Failed to load wiki url: " + wikiUrl, false);
}
StringBuilder sb = new StringBuilder();
try (
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(f), StandardCharsets.UTF_8))
) {
String l;
while ((l = br.readLine()) != null) {
sb.append(l).append("\n");
}
} catch (IOException e) {
return new HTMLInfoPane(overlay, manager, "error", "error", "Failed to load wiki url: " + wikiUrl, false);
}
return createFromWikiText(
overlay,
manager,
name,
f.getName(),
sb.toString(),
wikiUrl.startsWith("https://wiki.hypixel.net/")
);
});
}
/**
* Takes raw wikia code and uses Bliki to generate HTML. Lot's of shennanigans to get it to render appropriately.
* Honestly, I could have just downloaded the raw HTML of the wiki page and displayed that but I wanted
* a more permanent solution that can be abstracted to work with arbitrary wiki codes (eg. moulberry.github.io/
* files/neu_help.html).
*/
private static final Pattern replacePattern = Pattern.compile(
"|",
Pattern.DOTALL
);
public static HTMLInfoPane createFromWikiText(
NEUOverlay overlay, NEUManager manager, String name, String filename,
String wiki, boolean isOfficialWiki
) {
if (isOfficialWiki) {
wiki = wiki.split("")[1].split("")[0]; // hide top bar
wiki = wiki.split("
")[0]; // hide giant bottom list
wiki = wiki.split("
")[0]; // hide small bottom category thing
wiki = replacePattern.matcher(wiki).replaceAll("");
wiki = wiki.replaceAll(
"",
""
); // hide beta box
wiki = wiki.replaceAll("
.*
", ""); // hide title
wiki = wiki.replace("src=\"/", "src=\"https://wiki.hypixel.net/");
wiki = wiki.replace("\uD83D\uDDF8", "✓"); // replace checkmark with one that renders
wiki = wiki.replace("\uD83E\uDC10", "\u27F5"); // replace left arrow with one that renders
wiki = wiki.replace("\uD83E\uDC12", "\u27F6"); // replace right arrow with one that renders
} else {
String[] split = wiki.split("");
wiki = split[split.length - 1]; //Remove everything before infobox
wiki = wiki.split("")[0]; //Remove navbox
wiki = wiki.split("
{
try {
File itemsZip = new File(manager.configLocation, "wkhtmltox-" + osId + ".zip");
if (!itemsZip.exists()) {
URL url = new URL("https://moulberry.codes/wkhtmltox/wkhtmltox-" + osId + ".zip");
URLConnection urlConnection = url.openConnection();
urlConnection.setConnectTimeout(15000);
urlConnection.setReadTimeout(60000);
FileUtils.copyInputStreamToFile(urlConnection.getInputStream(), itemsZip);
}
try (InputStream is = new FileInputStream(itemsZip)) {
NEUManager.unzip(is, manager.configLocation);
}
itemsZip.delete();
itemsZip.deleteOnExit();
} catch (IOException e) {
e.printStackTrace();
}
});
text = EnumChatFormatting.YELLOW + "Downloading web renderer... try again soon";
}
return;
}
if (!cssFile.exists() && isOfficial) {
try {
Files.copy(this.getClass().getResourceAsStream("/assets/notenoughupdates/official-wiki.css"), cssFile.toPath());
} catch (IOException e) {
e.printStackTrace();
return;
}
}
File input = new File(manager.configLocation, "tmp/input.html");
String outputFileName = filename.replaceAll("(?i)\\u00A7.", "")
.replaceAll("[^a-zA-Z0-9_\\-]", "_");
File output = new File(manager.configLocation, "tmp/" +
outputFileName + ".png");
input.deleteOnExit();
output.deleteOnExit();
File tmp = new File(manager.configLocation, "tmp");
if (!tmp.exists()) {
tmp.mkdir();
}
if (output.exists()) {
try {
imageTemp = ImageIO.read(output);
text = EnumChatFormatting.RED + "Creating dynamic texture.";
} catch (IOException e) {
e.printStackTrace();
text = EnumChatFormatting.RED + "Failed to read image.";
return;
}
} else {
html = "