1
0
mirror of https://github.com/demodude4u/Factorio-FBSR.git synced 2024-11-27 08:20:57 +02:00
This commit is contained in:
Weston Rye (Demod) 2017-06-21 00:19:18 -04:00
parent 0cb5639ea9
commit 67313d72e3
8 changed files with 451 additions and 46 deletions

View File

@ -3,3 +3,4 @@
/blueprint-string_4.0.0.zip
/test.png
/config.json
/redditCache.json

View File

@ -1,7 +1,16 @@
{
"discord_bot_token": "",
"pastebin_developer_api_key": "",
"factorio": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Factorio",
"type_schema": "https://raw.githubusercontent.com/demodude4u/factorio-tools/master/schema.json",
"__type_schema": "https://raw.githubusercontent.com/jcranmer/factorio-tools/master/schema.json"
"reddit": {
"credentials": {
"username": "",
"password": "",
"client_id": "",
"client_secret": ""
},
"subreddit": "",
"summon_keyword": "!blueprint",
"refresh_seconds": 20,
"age_limit_hours": 24
},
"factorio": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Factorio"
}

View File

@ -65,5 +65,10 @@
<artifactId>fluent-hc</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>net.dean.jraw</groupId>
<artifactId>JRAW</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
</project>

View File

@ -25,19 +25,24 @@ public class TaskReporting {
private Optional<String> context = Optional.empty();
private final List<String> warnings = new ArrayList<>();
private final List<Exception> exceptions = new ArrayList<>();
private final List<String> imageUrls = new ArrayList<>();
private final List<String> downloadUrls = new ArrayList<>();
private final List<String> images = new ArrayList<>();
private final List<String> downloads = new ArrayList<>();
private final List<String> links = new ArrayList<>();
public void addDownloadURL(String downloadURL) {
downloadUrls.add(downloadURL);
public void addDownload(String url) {
downloads.add(url);
}
public synchronized void addException(Exception e) {
exceptions.add(e);
}
public void addImageURL(String imageURL) {
imageUrls.add(imageURL);
public void addImage(String url) {
images.add(url);
}
public void addLink(String url) {
links.add(url);
}
public void addWarning(String warning) {
@ -52,16 +57,16 @@ public class TaskReporting {
return debug;
}
public List<String> getDownloadURLs() {
return downloadUrls;
public List<String> getDownloads() {
return downloads;
}
public List<Exception> getExceptions() {
return exceptions;
}
public List<String> getImageURLs() {
return imageUrls;
public List<String> getImages() {
return images;
}
public Level getLevel() {
@ -77,6 +82,10 @@ public class TaskReporting {
return Level.INFO;
}
public List<String> getLinks() {
return links;
}
public List<String> getWarnings() {
return warnings;
}

View File

@ -55,14 +55,12 @@ import net.dv8tion.jda.core.events.message.MessageReceivedEvent;
public class BlueprintBotDiscordService extends AbstractIdleService {
private static final int MADEUP_NUMBER_FROM_AROUND_5_IN_THE_MORNING = 200;
private static final String USERID_DEMOD = "100075603016814592";
private static final Pattern debugPattern = Pattern.compile("DEBUG:([A-Za-z0-9_]+)");
public static void main(String[] args) {
new BlueprintBotDiscordService().startAsync();
}
private DiscordBot bot;
private CommandHandler createDataRawCommandHandler(Function<String, Optional<LuaValue>> query) {
@ -185,7 +183,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
processBlueprints(BlueprintFinder.search(content, reporting), event, reporting);
}
if (reporting.getImageURLs().isEmpty() && reporting.getDownloadURLs().isEmpty()) {
if (reporting.getImages().isEmpty() && reporting.getDownloads().isEmpty()) {
event.getChannel().sendMessage("I can't seem to find any blueprints. :frowning:").complete();
}
sendReportToDemod(event, reporting);
@ -202,7 +200,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
if (results.size() == 1) {
URL url = WebUtils.uploadToHostingService("blueprint.json", bytes);
event.getChannel().sendMessage("Blueprint JSON: " + url.toString()).complete();
reporting.addDownloadURL(url.toString());
reporting.addLink(url.toString());
} else {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos)) {
@ -219,7 +217,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
byte[] zipData = baos.toByteArray();
Message response = event.getChannel().sendFile(zipData, "blueprint JSON files.zip", null)
.complete();
reporting.addDownloadURL(response.getAttachments().get(0).getUrl());
reporting.addDownload(response.getAttachments().get(0).getUrl());
} catch (IOException e) {
reporting.addException(e);
}
@ -229,7 +227,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
}
}
if (reporting.getImageURLs().isEmpty() && reporting.getDownloadURLs().isEmpty()) {
if (reporting.getImages().isEmpty() && reporting.getDownloads().isEmpty()) {
event.getChannel().sendMessage("I can't seem to find any blueprints. :frowning:").complete();
}
sendReportToDemod(event, reporting);
@ -247,7 +245,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
byte[] imageData = generateDiscordFriendlyPNGImage(image);
Message message = event.getChannel()
.sendFile(new ByteArrayInputStream(imageData), "blueprint.png", null).complete();
reporting.addImageURL(message.getAttachments().get(0).getUrl());
reporting.addImage(message.getAttachments().get(0).getUrl());
} else {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos)) {
@ -269,7 +267,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
Message message = event.getChannel()
.sendFile(new ByteArrayInputStream(zipData), "blueprint book images.zip", null)
.complete();
reporting.addDownloadURL(message.getAttachments().get(0).getUrl());
reporting.addDownload(message.getAttachments().get(0).getUrl());
}
}
} catch (Exception e) {
@ -288,26 +286,31 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
URL url = WebUtils.uploadToHostingService(category + "_" + name + "_dump_" + FBSR.getVersion() + ".txt",
baos.toByteArray());
event.getChannel().sendMessage(category + " " + name + " lua dump: " + url.toString()).complete();
reporting.addDownloadURL(url.toString());
reporting.addLink(url.toString());
}
}
private void sendReportToDemod(MessageReceivedEvent event, TaskReporting reporting) {
Optional<String> context = reporting.getContext();
List<Exception> exceptions = reporting.getExceptions();
List<String> warnings = reporting.getWarnings();
List<String> imageUrls = reporting.getImageURLs();
List<String> downloadUrls = reporting.getDownloadURLs();
if (!exceptions.isEmpty()) {
public void sendReportToDemod(MessageReceivedEvent event, TaskReporting reporting) {
if (!reporting.getExceptions().isEmpty()) {
event.getChannel()
.sendMessage(
"There was a problem completing your request. I have contacted my programmer to fix it for you!")
.complete();
}
sendReportToDemod(getReadableAddress(event), event.getAuthor().getEffectiveAvatarUrl(), reporting);
}
public void sendReportToDemod(String author, String authorURL, TaskReporting reporting) {
Optional<String> context = reporting.getContext();
List<Exception> exceptions = reporting.getExceptions();
List<String> warnings = reporting.getWarnings();
List<String> images = reporting.getImages();
List<String> links = reporting.getLinks();
List<String> downloads = reporting.getDownloads();
EmbedBuilder builder = new EmbedBuilder();
builder.setAuthor(getReadableAddress(event), null, event.getAuthor().getEffectiveAvatarUrl());
builder.setAuthor(author, null, authorURL);
builder.setTimestamp(Instant.now());
Level level = reporting.getLevel();
@ -315,23 +318,24 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
builder.setColor(level.getColor());
}
if (context.isPresent() && context.get().length() <= 200) {
if (context.isPresent() && context.get().length() <= MADEUP_NUMBER_FROM_AROUND_5_IN_THE_MORNING) {
builder.addField("Context", context.get(), false);
context = Optional.empty();// XXX Being lazy at 5am
}
boolean firstImage = true;
for (String imageUrl : imageUrls) {
if (firstImage) {
firstImage = false;
builder.setImage(imageUrl);
} else {
builder.addField("Additional Image", imageUrl, true);
}
if (!links.isEmpty()) {
builder.addField("Link(s)", links.stream().collect(Collectors.joining("\n")), false);
}
for (String downloadUrl : downloadUrls) {
builder.addField("Download", downloadUrl, true);
if (!images.isEmpty()) {
builder.setImage(images.get(0));
}
if (images.size() > 1) {
builder.addField("Additional Image(s)", images.stream().skip(1).collect(Collectors.joining("\n")), false);
}
if (!downloads.isEmpty()) {
builder.addField("Download(s)", downloads.stream().collect(Collectors.joining("\n")), false);
}
Multiset<String> uniqueWarnings = LinkedHashMultiset.create(warnings);
@ -367,7 +371,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
false);
}
PrivateChannel privateChannel = event.getJDA().getUserById(USERID_DEMOD).openPrivateChannel().complete();
PrivateChannel privateChannel = bot.getJDA().getUserById(USERID_DEMOD).openPrivateChannel().complete();
privateChannel.sendMessage(builder.build()).complete();
if (context.isPresent()) {
@ -381,6 +385,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
@Override
protected void shutDown() throws Exception {
ServiceFinder.removeService(this);
bot.stopAsync().awaitTerminated();
}
@ -437,6 +442,7 @@ public class BlueprintBotDiscordService extends AbstractIdleService {
bot.startAsync().awaitRunning();
ServiceFinder.addService(this);
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -0,0 +1,334 @@
package com.demod.fbsr.app;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import org.json.JSONObject;
import com.demod.factorio.Config;
import com.demod.factorio.Utils;
import com.demod.fbsr.Blueprint;
import com.demod.fbsr.BlueprintFinder;
import com.demod.fbsr.BlueprintStringData;
import com.demod.fbsr.FBSR;
import com.demod.fbsr.RenderUtils;
import com.demod.fbsr.TaskReporting;
import com.demod.fbsr.WebUtils;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.AbstractScheduledService;
import javafx.util.Pair;
import net.dean.jraw.ApiException;
import net.dean.jraw.RedditClient;
import net.dean.jraw.http.NetworkException;
import net.dean.jraw.http.UserAgent;
import net.dean.jraw.http.oauth.Credentials;
import net.dean.jraw.http.oauth.OAuthData;
import net.dean.jraw.http.oauth.OAuthException;
import net.dean.jraw.managers.AccountManager;
import net.dean.jraw.models.Comment;
import net.dean.jraw.models.CommentNode;
import net.dean.jraw.models.Listing;
import net.dean.jraw.models.Submission;
import net.dean.jraw.paginators.CommentStream;
import net.dean.jraw.paginators.Sorting;
import net.dean.jraw.paginators.SubredditPaginator;
import net.dean.jraw.paginators.TimePeriod;
public class BlueprintBotRedditService extends AbstractScheduledService {
private static final File CACHE_FILE = new File("redditCache.json");
private static final String REDDIT_AUTHOR_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/Reddit.svg/64px-Reddit.svg.png";
private JSONObject configJson;
private String myUserName;
private RedditClient reddit;
private AccountManager account;
private Credentials credentials;
private OAuthData authData;
private void ensureValidAccessToken() throws NetworkException, OAuthException {
if (System.currentTimeMillis() > authData.getExpirationDate().getTime()) {
authData = reddit.getOAuthHelper().refreshToken(credentials);
reddit.authenticate(authData);
}
}
private byte[] generateRedditFriendlyPNGImage(BufferedImage image) throws IOException {
byte[] imageData;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(image, "PNG", baos);
imageData = baos.toByteArray();
}
if (imageData.length > 10000000) {
return generateRedditFriendlyPNGImage(
RenderUtils.scaleImage(image, image.getWidth() / 2, image.getHeight() / 2));
}
return imageData;
}
private Optional<Comment> getMyReply(CommentNode comments) {
return comments.getChildren().stream().map(c -> c.getComment()).filter(c -> c.getAuthor().equals(myUserName))
.findAny();
}
private JSONObject getOrCreateCache() throws FileNotFoundException, IOException {
if (CACHE_FILE.exists()) {
try (FileInputStream fis = new FileInputStream(CACHE_FILE)) {
return Utils.readJsonFromStream(fis);
}
} else {
JSONObject cache = new JSONObject();
cache.put("lastProcessedSubmissionMillis", 0L);
cache.put("lastProcessedCommentMillis", 0L);
cache.put("lastProcessedMessageMillis", 0L);
return cache;
}
}
private Optional<String> processContent(String content, String link, String subreddit, String author) {
if (!content.contains(configJson.getString("summon_keyword"))) {
return Optional.empty();
}
TaskReporting reporting = new TaskReporting();
reporting.setContext(content);
reporting.addLink(link);
try {
List<BlueprintStringData> blueprintStrings = BlueprintFinder.search(content, reporting);
List<Blueprint> blueprints = blueprintStrings.stream().flatMap(s -> s.getBlueprints().stream())
.collect(Collectors.toList());
for (Blueprint blueprint : blueprints) {
try {
BufferedImage image = FBSR.renderBlueprint(blueprint, reporting);
reporting.addImage(WebUtils
.uploadToHostingService("blueprint.png", generateRedditFriendlyPNGImage(image)).toString());
} catch (Exception e) {
reporting.addException(e);
}
}
} catch (Exception e) {
reporting.addException(e);
}
List<String> lines = new ArrayList<>();
List<String> images = reporting.getImages();
if (images.size() > 1) {
int id = 1;
for (String url : images) {
lines.add("[Blueprint Image " + (id++) + "](" + url + ")");
}
} else if (!images.isEmpty()) {
lines.add("[Blueprint Image](" + images.get(0) + ")");
}
if (images.isEmpty()) {
lines.add(" I can't seem to find any blueprints...");
}
if (!reporting.getExceptions().isEmpty()) {
lines.add(
" There was a problem completing your request. I have contacted my programmer to fix it for you!");
}
ServiceFinder.findService(BlueprintBotDiscordService.class).ifPresent(
s -> s.sendReportToDemod("Reddit / " + subreddit + " / " + author, REDDIT_AUTHOR_URL, reporting));
return Optional.of(lines.stream().collect(Collectors.joining("\n\n")));
}
private boolean processNewComments(JSONObject cacheJson, String subreddit, long ageLimitMillis)
throws NetworkException, ApiException {
long lastProcessedMillis = cacheJson.getLong("lastProcessedCommentMillis");
CommentStream commentStream = new CommentStream(reddit, subreddit);
commentStream.setTimePeriod(TimePeriod.ALL);
commentStream.setSorting(Sorting.NEW);
int processedCount = 0;
long newestMillis = lastProcessedMillis;
List<Pair<Comment, String>> pendingReplies = new LinkedList<>();
paginate: for (Listing<Comment> listing : commentStream) {
for (Comment comment : listing) {
long createMillis = comment.getCreated().getTime();
if (createMillis <= lastProcessedMillis
|| (System.currentTimeMillis() - createMillis > ageLimitMillis)) {
break paginate;
}
processedCount++;
newestMillis = Math.max(newestMillis, createMillis);
if (comment.getAuthor().equals(myUserName)) {
break paginate;
}
if (comment.isArchived()) {
continue;
}
Optional<String> response = processContent(comment.getBody(), comment.getUrl(),
comment.getSubredditName(), comment.getAuthor());
if (response.isPresent()) {
pendingReplies.add(new Pair<>(comment, response.get()));
}
}
}
for (Pair<Comment, String> pair : pendingReplies) {
System.out.println("IM TRYING TO REPLY TO A COMMENT!");
account.reply(pair.getKey(), pair.getValue());
}
if (processedCount > 0) {
System.out.println("Processed " + processedCount + " comment(s) from /r/" + subreddit);
cacheJson.put("lastProcessedCommentMillis", newestMillis);
return true;
} else {
return false;
}
}
private boolean processNewMessages(JSONObject cacheJson, long ageLimitMillis) {
// TODO Auto-generated method stub
return false;
}
private boolean processNewSubmissions(JSONObject cacheJson, String subreddit, long ageLimitMillis)
throws NetworkException, ApiException {
long lastProcessedMillis = cacheJson.getLong("lastProcessedSubmissionMillis");
SubredditPaginator paginator = new SubredditPaginator(reddit, subreddit);
paginator.setTimePeriod(TimePeriod.ALL);
paginator.setSorting(Sorting.NEW);
int processedCount = 0;
long newestMillis = lastProcessedMillis;
List<Pair<Submission, String>> pendingReplies = new LinkedList<>();
paginate: for (Listing<Submission> listing : paginator) {
for (Submission submission : listing) {
long createMillis = submission.getCreated().getTime();
if (createMillis <= lastProcessedMillis
|| (System.currentTimeMillis() - createMillis > ageLimitMillis)) {
break paginate;
}
processedCount++;
newestMillis = Math.max(newestMillis, createMillis);
if (!submission.isSelfPost() || submission.isLocked() || submission.isArchived()) {
continue;
}
CommentNode comments = submission.getComments();
if (comments == null && submission.getCommentCount() > 0) {
submission = reddit.getSubmission(submission.getId());
comments = submission.getComments();
}
if (comments != null && getMyReply(comments).isPresent()) {
break paginate;
}
Optional<String> response = processContent(submission.getSelftext(), submission.getUrl(),
submission.getSubredditName(), submission.getAuthor());
if (response.isPresent()) {
pendingReplies.add(new Pair<>(submission, response.get()));
}
}
}
for (Pair<Submission, String> pair : pendingReplies) {
System.out.println("IM TRYING TO REPLY TO A SUBMISSION!");
account.reply(pair.getKey(), pair.getValue());
}
if (processedCount > 0) {
System.out.println("Processed " + processedCount + " submission(s) from /r/" + subreddit);
cacheJson.put("lastProcessedSubmissionMillis", newestMillis);
return true;
} else {
return false;
}
}
@Override
protected void runOneIteration() throws Exception {
try {
String subreddit = configJson.getString("subreddit");
final long ageLimitMillis = configJson.getInt("age_limit_hours") * 60 * 60 * 1000;
JSONObject cacheJson = getOrCreateCache();
boolean cacheUpdated = false;
ensureValidAccessToken();
cacheUpdated |= processNewSubmissions(cacheJson, subreddit, ageLimitMillis);
cacheUpdated |= processNewComments(cacheJson, subreddit, ageLimitMillis);
cacheUpdated |= processNewMessages(cacheJson, ageLimitMillis);
if (cacheUpdated) {
saveCache(cacheJson);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void saveCache(JSONObject cacheJson) throws IOException {
try (FileWriter fw = new FileWriter(CACHE_FILE)) {
fw.write(cacheJson.toString(2));
}
}
@Override
protected Scheduler scheduler() {
return Scheduler.newFixedDelaySchedule(0, configJson.getInt("refresh_seconds"), TimeUnit.SECONDS);
}
@Override
protected void shutDown() throws Exception {
ServiceFinder.removeService(this);
reddit.getOAuthHelper().revokeAccessToken(credentials);
reddit.deauthenticate();
}
@Override
protected void startUp() {
try {
reddit = new RedditClient(UserAgent.of("server", "com.demod.fbsr", "0.0.1", "demodude4u"));
account = new AccountManager(reddit);
configJson = Config.get().getJSONObject("reddit");
JSONObject redditCredentialsJson = configJson.getJSONObject("credentials");
credentials = Credentials.script( //
redditCredentialsJson.getString("username"), //
redditCredentialsJson.getString("password"), //
redditCredentialsJson.getString("client_id"), //
redditCredentialsJson.getString("client_secret") //
);
authData = reddit.getOAuthHelper().easyAuth(credentials);
reddit.authenticate(authData);
myUserName = redditCredentialsJson.getString("username");
System.out.println("Reddit Authenticated!");
ServiceFinder.addService(this);
} catch (Exception e) {
e.printStackTrace();
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,22 @@
package com.demod.fbsr.app;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ServiceFinder {
private static final Map<Class<?>, Object> registry = new ConcurrentHashMap<>();
public static void addService(Object service) {
registry.put(service.getClass(), service);
}
@SuppressWarnings("unchecked")
public static <T> Optional<T> findService(Class<T> clazz) {
return Optional.ofNullable((T) registry.get(clazz));
}
public static void removeService(Object service) {
registry.remove(service.getClass());
}
}

View File

@ -0,0 +1,19 @@
package com.demod.fbsr.app;
import java.util.Arrays;
import com.google.common.util.concurrent.Service;
import com.google.common.util.concurrent.ServiceManager;
public class StartAllServices {
public static void main(String[] args) {
ServiceManager manager = new ServiceManager(Arrays.asList(new Service[] { //
new BlueprintBotDiscordService(), //
new BlueprintBotRedditService(),//
}));
manager.startAsync();
}
}