Merge branch 'develop'

This commit is contained in:
Alex Stewart
2019-04-15 15:10:30 -07:00
89 changed files with 1804 additions and 672 deletions
-1
View File
@@ -13,6 +13,5 @@
<classpathentry kind="lib" path="lib/slf4j-jdk14-1.7.25.jar"/>
<classpathentry kind="lib" path="lib/twitter4j-core-4.0.7-javadoc.jar"/>
<classpathentry kind="lib" path="lib/twitter4j-core-4.0.7.jar"/>
<classpathentry kind="lib" path="lib/ini4j-0.5.4.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+1 -1
View File
@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2019, AlexStewartCode
Copyright (c) 2019, AlexStewartCode and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
+5 -1
View File
@@ -1,2 +1,6 @@
# KittyBot
Fun and useful Discord bot
Fun and useful Discord bot designed to be able searching websites, playing games, compiling code, and much more!
This repo is an eclipse project, and can be opened in the IDE directly. Authentication keys will need to be placed in a file called `Ref.java` at `src/offline/`. This folder isn't present in this repository by default.
If you're looking to contribute, feel free to submit a pull request! Take a look at the styleguide to get some pointers on formatting for this repo!
+65
View File
@@ -0,0 +1,65 @@
# KittyBot Style Guide
This document represents the reasonable defaults and expectations for this project, and provides some rationale for the style restrictions! There can always be exceptions to the rules, but they may need justifying.
Code Formatting
---
#### Indenting
Tabs are used so that individual developers can configure their own indent width settings that won't be enforced on others working on the project.
```java
// I'm not indented!
{
// I'm indented with one tab!
}
```
#### Braces
When braces are present, Allman style is used meaning that braces start on the next line. Braces are used to prevent scope confusion. Omitting braces on single-line statements is fine if the situation is clear.
```java
if(foo)
{
System.out.println("This");
System.out.println("has");
System.out.println("braces!");
}
if(bar)
System.out.println("No braces!");
```
#### Naming
Class names and function names are `PascalCase`. Variable names are `camelCase`.
```java
public class MyClass
{
private Foo myVariable;
public function MyFuction()
{
// ...
}
}
```
Favor a broad to narrow naming scheme to promote autocomplete grouping. For example, use a name like `userDescriptionHeader` instead of `headerUserDescription` because there may be other variables related to a user or description, but there's likely only one header section of a description.
#### Ternary Operator (`?:`)
Don't.
Architecture
---
#### Commands
All commands get spawned on their own threads, and should be implemented in a thread-safe way. Commands all derive from the Command.java class, and use the custom types defined in KittyBot. In order to make any later changes easier and decouple the command logic from core and routing, no JDA structures should be directly exposed in commands, and instead everything needed should be added to the kitty structures that add space to the system. Commands should override the `HelpText` and `Run` functions. Commands are registered in the ObjectBuilderFactory, and the command registration structure can be used for sub-commands as needed. See the RPG for an example.
#### Tracked Values
Does something need to be tracked in a database? Make sure the class with the data you want inherits from DatabaseTrackedObject, and override the appropriate functions. The object, when marked as dirty, will automatically be written to the database when the next database upkeep tick occurs. Refer to CommandBoop to see an example of a tracked global value, and KittyUser for tracked per-user values.
#### Tokens, Secrets, etc
Things like tokens, private keys, an secrets are kept in the offline package. This file is intentionally excluded from the repo. If it shouldn't be in the repo, put it in the offline package.
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

+37
View File
@@ -0,0 +1,37 @@
indicator=1
boop=1
tony, stark, dontfeelgood, dontfeelsogood=1
yeet=1
role=1
ping=1
rating=1
roll=1
blur=1
choose=1
poll=1
rpstart=1
bet=1
perish, thenperish=1
teey=1
stats=1
beans=1
eightball, 8ball=1
vote=1
results=1
wolfram=1
map=1
info, about=1
givebeans=1
c++, g++, cplus, cpp=1
work=1
showpoll=1
rpend=1
rpg=1
tweet=1
java, jdoodle=1
help=1
buildHelp=1
invite=1
shutdown=1
addguildrole=1
allowedguildrole=1
Binary file not shown.
+40
View File
@@ -0,0 +1,40 @@
[ObjectBuilderFactory]
indicator=
boop=
tony, stark, dontfeelgood, dontfeelsogood=
yeet=
role=
ping=
rating=
roll=
blur=
choose=
poll=
rpstart=
bet=
addguildrole=
perish, thenperish=
teey=
stats=
allowedguildrole=
beans=
eightball, 8ball=
vote=
results=
wolfram=
map=
info, about=
givebeans=
c++, g++, cplus, cpp=
work=
showpoll=
rpend=
rpg=
tweet=
java, jdoodle=
help=
buildHelp=
invite=
shutdown=
+188
View File
@@ -0,0 +1,188 @@
[CommandPollManage]
PollManageInfo='start' will start a new poll with the query of the line you put after, 'choice' will add a choice to the poll, 'stop' will end the poll
[CommandBeansShow]
BeansShowDisplay=You have %s beans!
BeansShowInfo=Displays how many beans you have
[CommandHelp]
HelpInfo=Lets you look up specific commands, or get a link to a list of all commands.
HelpDisplay=You can get help with a specific command by typing `!help command`!\nGeneral Commands: `boop, roll, choose, help, info, vote, results, showpoll, wolfram, cplus, java, beans, role, bet, yeet`
[CommandRoll]
RollError=Didn't work ;3;
RollInfo=Based on input of xdy where x is number of dice and y is faces kitty will roll that amount of dice, display the individual rolls and total
[CommandStats]
StatsInfo=Displays the actively running KittyBot application information
[CommandTweet]
TweetInfo=Kitty will tweet to her personal twitter account
TweetError=Tweet command failed!
[CommandRole]
RoleInfo=Will show the current role you have, Admins can change others roles with input of 'role x @y' with x being blacklist, general, mod, or admin. Blacklist will not allow the user to interact with kitty anymore, general mod and admin will give the user access to those commands.
RoleStandardResponse=Your role is
RoleError=You aren't allowed to do that! You must have the KittyRole '%' or higher!
RoleChanged=Changed %s to role `%s`!
RoleNeededRole=Please enter `general`, `mod`, or `admin`!
[CommandColiru]
ColiruError=Please provide some c++ code to attempt to compile!
ColiruInfo=Will try to compile any c++ code you put in! Supports up to C++14 standard, uses g++.
[CommandJDoodle]
JDoodleInfo=Will compile any java code you put in! Supports Java 1.8
JDoodleError=Please provide some java code to get it compiled!
[CommandChangeIndicator]
ChangeIndicatorInfo=Changes the command indicator to any single character. By default, it's '!'. If more than one character is provided, the first one is used!
ChangeIndicatorError=Please specify a letter or symbol to use!
ChangeIndicatorChanged=Indicator changed to `%s`
[CommandPerish]
PerishInfo=Adds a red, 'a n g e r y' overlay to your icon or the icon of a friend you mentioned
[CommandStark]
StarkInfo=Snaps your icon or the icon of a friend you mentioned
[CommandYeet]
YeetInfo=Yeet yourself or yeet a friend with @!
[CommandAllowedGuildRole]
AllowedGuildRoleInfo=
AllowedGuildRoleDuplicate=Can't add the same role twice!
AllowedGuildRoleSuccess=Added %s to the allowed roles!
[CommandPollVote]
PollVoteNotValidNumber=That's not a vaild number!
PollVoteInfo=Vote in a poll with the choice number, won't work if no poll is running, you can't change your vote once you have cast it! Be careful!
PollVoteNoPoll=There is no poll running!
PollVoteAlreadyVoted=You already voted
PollVoteNotValidVote=%s That's not a vaild vote!
PollVoteSuccess=You successfully voted for
[CommandShutdown]
ShutdownInfo=Stops kitty. `-s` or `safe` as an argument attempts to sync off the database before shutdown.
[CommandTeey]
TeeyInfo=Teey yourself back into existance, or teey a friend with @!
[CommandBetBeans]
BetBeansLowBet=Please bet with at least 50 beans!
BetBeansInfo=Allows you to bet your beans, set your amount after the command! Warning! House always wins!\nSets of 3 will get 2x, 4 will get 10x, and 5 will get 1000x
BetBeansNotValid=That's not a valid bet!
BetBeansLose=Sorry, you didn't win, try again!
BetBeansNotEnough=You don't have enough beans!
BetBeansWin=You won %s beans!
[CommandDoWork]
DoWorkFinished=I finished with my work! :D
DoWorkInfo=Occupies a core for a few seconds
[CommandRPStart]
RPStartInfo=Starts an rp, add people to it by mentioning them!
[CommandBlurry]
BlurryInfo=Blurs your icon or the icon of a friend you mentioned
[CommandPollResults]
PollResultsResponse=%s people voted for %s `%s` with `%s%` !\n
PollResultsInfo=Will show current results of a poll and percentages of votes per choice
[CommandRating]
RatingInfo='0' is fully sfw (Derpi and e621 searches are disabled), '1' is filtered (kitty auto appends a sfw tag on any searches), '2' is nsfw (any search will go through). Some other words are supported for setting filter as well.
RatingWarning=Warning: NSFW may slip through, images are only based on tags on their respective sites!
RatingChanged=Kittybot content set to
RatingInvalid=Invalid content rating
[CommandGiveBeans]
GiveBeansNoneMentioned=You didn't mention anyone!
GiveBeansSuccess=Gave %s %s beans!
GiveBeansInvalid=That's not a valid number!
GiveBeansInfo=Gives beans to the mentioned users!
[CommandPollShow]
PollShowNoPoll=There is no poll running!
PollShowInfo=Will show the current poll running
PollShowPoll=The current poll is `%s`\n
PollShowChoices=And the choices are:\n
[CommandHelpBuilder]
HelpBuilderInfo=Emit help for all commands as formatted HTML
[CommandRPEnd]
RPEndError=You can't end this RP!
RPEndInfo=Ends RP in channel and gives you a .txt file!
RPEndFileOut=Here's your file!
[CommandRPG]
RPGInfo=Admin+ command only right now - experimental text RPG! Format is !rpg <rpg command>
RPGInvalid=Invalid RPG Command!
[CommandBoop]
BoopPerson=%s booped %s!
BoopInfo=Kitty will react with a counter
BoopStandard=Woah! %s booped me! That's %s total!
BoopMultiple=%s booped several others - %s!
[CommandInfo]
InfoResponse=I'm made by `Rin#8904` and `Reverie Wisp#3703`!\nYou can find more info about me along with a Patreon link to support us and GitHub link for filing bugs https://www.rinsnowmew.com/bot/
InfoInfo=Provides author info and a link to Kitty's website
[CommandPing]
PingResponse=Pong!
PingInfo=Will respond with Pong!
[CommandChoose]
ChooseChoice=I chooooooose %s!
ChooseOne=I can't choose from *one* thing!
ChooseInfo=With an input of x,y,z where x y and z are all choices, kitty will choose one
[CommandEightBall]
EightBallYes4=Yes - definitely.
EightBallYes3=Without a doubt.
EightBallYes2=It is decidedly so.
EightBallYes1=It is certain.
EightBallYes8=Outlook good.
EightBallYes7=Most likely.
EightBallYes6=As I see it, yes.
EightBallYes5=You may rely on it.
EightBallMaybe1=Reply hazy, try again.
EightBallMaybe2=Ask again later.
EightBallError=Hmm? You'll need to ask a question!
EightBallMaybe3=Better not tell you now.
EightBallMaybe4=Cannot predict now.
EightBallMaybe5=Concentrate and ask again.
EightBallInfo=Answers a yes or no question! Warning: Kitty can not actually tell the future, she claims no responsibility for any lion mauling, lack of lottery wins, or felony charges.
EightBallNo5=Very doubtful.
EightBallNo3=My sources say no.
EightBallNo4=Outlook not so good.
EightBallNo1=Don't count on it.
EightBallNo2=My reply is no.
EightBallYes9=Yes.
EightBallYes10=Signs point to yes.
[CommandInvite]
InviteInfo=Provides a direct invite link for KittyBot
[CommandWolfram]
WolframError=Something went wrong!
WolframInfo=Will query wolframalpha with your question and give a full image output of the answer
WolframNoArgs=You need to provide some arguments!
[CommandAddGuildRole]
AddGuildRoleInfo=Add a role to yourself!
AddGuildRoleNotAllowed=You are not allowed to add %s!
AddGuildRoleSuccess=Added %s to %s
AddGuildRoleFailure=Failed to add %s to %s
[CommandMap]
MapInfo=Generates a map! You can pass additional information if you want with the flags `-s<seed>-w<width>-h<height>`. If one of the fields isn't provided, its default will be used. Note that adjusting the width and height impacts the map outcomes.\n\nDefault seed: Random,\nDefault Width: 35(max %s),\nDefault Height: 25(max %s)
MapSeed=Seed
MapInvalid=Invalid arguments provided!
MapVersion=Using mapgen v0.1
MapWidth=Width
MapHeight=Height
-21
View File
@@ -1,21 +0,0 @@
[CommandBeansShow]
Displays how many beans you have =
You have %s beans! =
[CommandBetBeans]
Please bet with at least 50 beans! =
Allows you to bet your beans, set your amount after the command! Warning! House always wins!\nSets of 3 will get 5x, 4 will get 10x, and 5 will get 1000x =
That's not a valid bet! =
Sorry, you didn't win, try again! =
You don't have enough beans! =
You won %s beans! =
[CommandBoop]
%s booped %s! =
Kitty will react with a counter =
Woah! %s booped me! That's %s total! =
%s booped several others - %s! =
[CommandBlurry]
Blurs your icon or the icon of a friend you mentioned =
+40
View File
@@ -0,0 +1,40 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
import dataStructures.KittyRole;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
public class CommandAddGuildRole extends Command
{
public CommandAddGuildRole(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return LocStrings.Stub("AddGuildRoleInfo"); };
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
String role = input.args.split(" ")[0];
if(guild.allowedRole.contains(role))
{
if(guild.control.addRole(user.discordID, role))
{
res.Call(String.format(LocStrings.Stub("AddGuildRoleSuccess"), role, user.name));
}
else
{
res.Call(String.format(LocStrings.Stub("AddGuildRoleFailure"), role, user.name));
}
}
else
{
res.Call(String.format(LocStrings.Stub("AddGuildRoleNotAllowed"), role));
}
}
}
+39
View File
@@ -0,0 +1,39 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
import dataStructures.KittyRole;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
public class CommandAllowedGuildRole extends Command
{
public CommandAllowedGuildRole(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return LocStrings.Stub("AllowedGuildRoleInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
String [] roles = input.args.split(",");
String role;
for(int i = 0; i < roles.length; i++)
{
role = roles[i].trim();
if(guild.allowedRole.contains(role))
{
res.Call(LocStrings.Stub("AllowedGuildRoleDuplicate"));
}
else
{
guild.allowedRole.add(role);
res.Call(String.format(LocStrings.Stub("AllowedGuildRoleSuccess"), role));
}
}
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
package commands;
import core.Command;
import core.Localizer;
import core.LocStrings;
import dataStructures.*;
public class CommandBeansShow extends Command
@@ -9,11 +9,11 @@ public class CommandBeansShow extends Command
public CommandBeansShow(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return Localizer.Stub("Displays how many beans you have"); };
public String HelpText() { return LocStrings.Stub("BeansShowInfo"); };
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
res.Call(String.format(Localizer.Stub("You have %s beans!"), user.GetBeans()));
res.Call(String.format(LocStrings.Stub("BeansShowDisplay"), user.GetBeans()));
}
}
+19 -9
View File
@@ -1,7 +1,9 @@
package commands;
import java.util.Random;
import core.Command;
import core.Localizer;
import core.LocStrings;
import dataStructures.*;
public class CommandBetBeans extends Command
@@ -9,7 +11,7 @@ public class CommandBetBeans extends Command
public CommandBetBeans(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return Localizer.Stub("Allows you to bet your beans, set your amount after the command! Warning! House always wins!\nSets of 3 will get 5x, 4 will get 10x, and 5 will get 1000x"); }
public String HelpText() { return LocStrings.Stub("BetBeansInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -22,19 +24,19 @@ public class CommandBetBeans extends Command
bet = Integer.parseInt(input.args);
if(bet < 50)
{
res.Call(Localizer.Stub("Please bet with at least 50 beans!"));
res.Call(LocStrings.Stub("BetBeansLowBet"));
return;
}
}
catch (NumberFormatException e)
{
res.Call(Localizer.Stub("That's not a valid bet!"));
res.Call(LocStrings.Stub("BetBeansNotValid"));
return;
}
if(user.GetBeans() < bet)
{
res.Call(Localizer.Stub("You don't have enough beans!"));
res.Call(LocStrings.Stub("BetBeansNotEnough"));
return;
}
@@ -49,12 +51,12 @@ public class CommandBetBeans extends Command
if(win == 0)
{
res.Call(Localizer.Stub("Sorry, you didn't win, try again!"));
res.Call(LocStrings.Stub("BetBeansLose"));
return;
}
user.ChangeBeans(bet*win);
res.Call(String.format(Localizer.Stub("You won %s beans!"), "" + (bet*win)));
res.Call(String.format(LocStrings.Stub("BetBeansWin"), "" + (bet*win)));
}
private int getWinning(int [] slots)
@@ -69,7 +71,7 @@ public class CommandBetBeans extends Command
counter = 1;
if(counter == 3)
winning = 5;
winning = 2;
if(counter == 4)
winning = 10;
@@ -83,10 +85,12 @@ public class CommandBetBeans extends Command
private int[] getSlots()
{
Random gen = new Random();
int[] nums = new int[5];
for (int i = 0; i < nums.length; i++)
{
nums[i] = (int) (Math.random() * 5) + 1;
nums[i] = Math.abs(gen.nextInt() % 7) + 1;
System.out.println(nums [i]);
}
return nums;
}
@@ -138,6 +142,12 @@ public class CommandBetBeans extends Command
case 5:
slotHearts += ":purple_heart:";
break;
case 6:
slotHearts += ":black_heart:";
break;
case 7:
slotHearts += ":broken_heart:";
break;
}
}
+2 -2
View File
@@ -8,7 +8,7 @@ import java.io.IOException;
import javax.imageio.ImageIO;
import core.Command;
import core.Localizer;
import core.LocStrings;
import dataStructures.*;
import utils.ImageUtils;
@@ -17,7 +17,7 @@ public class CommandBlurry extends Command
public CommandBlurry(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return Localizer.Stub("Blurs your icon or the icon of a friend you mentioned"); };
public String HelpText() { return LocStrings.Stub("BlurryInfo"); };
private static Long num = 0l;
+4 -4
View File
@@ -60,7 +60,7 @@ public class CommandBoop extends Command
}
@Override
public String HelpText() { return Localizer.Stub("Kitty will react with a counter"); }
public String HelpText() { return LocStrings.Stub("BoopInfo"); }
// Called when the command is run!
@Override
@@ -69,14 +69,14 @@ public class CommandBoop extends Command
if(input.mentions == null)
{
boopTracker.ApplyBoop();
res.Call(String.format(Localizer.Stub("Woah! %s booped me! That's %s total!"), user.name, boopTracker.HowMany()));
res.Call(String.format(LocStrings.Stub("BoopStandard"), user.name, boopTracker.HowMany()));
}
else
{
if(input.mentions.length == 1)
{
boopTracker.ApplyBoop();
res.Call(String.format(Localizer.Stub("%s booped %s!"), user.name, input.mentions[0].name));
res.Call(String.format(LocStrings.Stub("BoopPerson"), user.name, input.mentions[0].name));
return;
}
@@ -90,7 +90,7 @@ public class CommandBoop extends Command
booped += "and " + input.mentions[i].name;
}
res.Call(String.format(Localizer.Stub("%s booped several others - %s!"), user.name, booped));
res.Call(String.format(LocStrings.Stub("BoopMultiple"), user.name, booped));
}
}
}
+4 -3
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
public class CommandChangeIndicator extends Command
@@ -8,7 +9,7 @@ public class CommandChangeIndicator extends Command
public CommandChangeIndicator(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Changes the command indicator to any single character. By default, it's '!'. If more than one character is provided, the first one is used!"; }
public String HelpText() { return LocStrings.Stub("ChangeIndicatorInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -16,11 +17,11 @@ public class CommandChangeIndicator extends Command
String arg = input.args.trim();
if(arg.length() == 0)
{
res.Call("Please specify a letter or symbol to use!");
res.Call(LocStrings.Stub("ChangeIndicatorError"));
return;
}
guild.SetCommandIndicator(arg.substring(0, 1));
res.Call("Indicator changed to `" + guild.GetCommandIndicator() +"`!");
res.Call(String.format(LocStrings.Stub("ChangeIndicatorChanged"), guild.GetCommandIndicator()));
}
}
+3 -3
View File
@@ -14,7 +14,7 @@ public class CommandChoose extends Command
public CommandChoose(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "With an input of x,y,z where x y and z are all choices, kitty will choose one"; }
public String HelpText() { return LocStrings.Stub("ChooseInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -22,10 +22,10 @@ public class CommandChoose extends Command
String [] choices = input.args.split(",");
if(choices.length == 1)
{
res.Call("I can't choose from *one* thing!");
res.Call(LocStrings.Stub("ChooseOne"));
return;
}
res.Call("I chooooooose" + choices[(int) (Math.random()*choices.length)] + "!");
res.Call(String.format(LocStrings.Stub("ChooseChoice"), (choices[(int) (Math.random()*choices.length)]).toString()));
}
}
+8 -1
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
import network.NetworkColiru;
@@ -11,11 +12,17 @@ public class CommandColiru extends Command
public CommandColiru(KittyRole level, KittyRating rating) { super(level, rating);}
@Override
public String HelpText() { return "Will try to compile any c++ code you put in! Supports up to C++14 standard, uses g++."; }
public String HelpText() { return LocStrings.Stub("ColiruInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
if(input.args.trim().length() < 1)
{
res.Call(LocStrings.Stub("ColiruError"));
return;
}
res.Call(compiler.compileCPlus(input.args));
}
+2 -2
View File
@@ -15,7 +15,7 @@ public class CommandDoWork extends Command
public CommandDoWork(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Occupies a core for a few seconds"; }
public String HelpText() { return LocStrings.Stub("DoWorkInfo"); }
// Called when the command is run!
@Override
@@ -28,6 +28,6 @@ public class CommandDoWork extends Command
thispieceofshit += Math.log((double)i);
}
res.Call("I finished with my work! :D");
res.Call(LocStrings.Stub("DoWorkFinished"));
}
}
+31 -8
View File
@@ -1,25 +1,48 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
public class CommandEightBall extends Command
{
String [] answers = {"It is certain.", "It is decidedly so.", "Without a doubt.", "Yes - definitely.", "You may rely on it.", "As I see it, yes.", "Most likely.",
"Outlook good.", "Yes.", "Signs point to yes.", "Reply hazy, try again.", "Ask again later.", "Better not tell you now.", "Cannot predict now.",
"Concentrate and ask again.", "Don't count on it.", "My reply is no.", "My sources say no.", "Outlook not so good.", "Very doubtful."};
public CommandEightBall(KittyRole level, KittyRating rating)
String [] answers =
{
super(level, rating);
}
LocStrings.Stub("EightBallYes1")
, LocStrings.Stub("EightBallYes2")
, LocStrings.Stub("EightBallYes3")
, LocStrings.Stub("EightBallYes4")
, LocStrings.Stub("EightBallYes5")
, LocStrings.Stub("EightBallYes6")
, LocStrings.Stub("EightBallYes7")
, LocStrings.Stub("EightBallYes8")
, LocStrings.Stub("EightBallYes9")
, LocStrings.Stub("EightBallYes10")
, LocStrings.Stub("EightBallMaybe1")
, LocStrings.Stub("EightBallMaybe2")
, LocStrings.Stub("EightBallMaybe3")
, LocStrings.Stub("EightBallMaybe4")
, LocStrings.Stub("EightBallMaybe5")
, LocStrings.Stub("EightBallNo1")
, LocStrings.Stub("EightBallNo2")
, LocStrings.Stub("EightBallNo3")
, LocStrings.Stub("EightBallNo4")
, LocStrings.Stub("EightBallNo5")
};
public CommandEightBall(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Answers a yes or no question! Warning: Kitty can not actually tell the future,"
+ " she claims no responsibility for any lion mauling, lack of lottery wins, or felony charges."; }
public String HelpText() { return LocStrings.Stub("EightBallInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
if(input.args.trim().length() < 1)
res.Call(LocStrings.Stub("EightBallError"));
res.Call(answers[(int) (Math.random()*answers.length)]);
}
}
+5 -4
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
public class CommandGiveBeans extends Command
@@ -8,7 +9,7 @@ public class CommandGiveBeans extends Command
public CommandGiveBeans (KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Gives beans to the mentioned users!"; }
public String HelpText() { return LocStrings.Stub("GiveBeansInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -19,20 +20,20 @@ public class CommandGiveBeans extends Command
}
catch (NumberFormatException e)
{
res.Call("That's not a valid number!");
res.Call(LocStrings.Stub("GiveBeansInvalid"));
return;
}
if(input.mentions == null)
{
res.Call("You didn't mention anyone!");
res.Call(LocStrings.Stub("GiveBeansNoneMentioned"));
return;
}
for(int i = 0; i < input.mentions.length; i++)
{
input.mentions[i].ChangeBeans(beans);
res.Call("Gave " + input.mentions[i].name + " " + beans + " beans!");
res.Call(String.format(LocStrings.Stub("GiveBeansSuccess"), input.mentions[i].name, "" + beans));
}
}
}
+3 -8
View File
@@ -9,14 +9,13 @@ import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
// TODO: Convert this to a per-command help string! This lets us do all sorts of neat stuff,
// mostly tho it lets us construct this on the fly or by hand for a specific command!
// Either allows for specifically looking up commands or gets a list of general commands to try out!
public class CommandHelp extends Command
{
public CommandHelp(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Lets you look up specific commands, or get a link to a list of all commands."; }
public String HelpText() { return LocStrings.Stub("HelpInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -25,11 +24,7 @@ public class CommandHelp extends Command
if(help == null)
{
help = "You can get help with a specific command by typing `!help command`!"
+ "\nYou can also look at <https://www.rinsnowmew.com/bot/info/#commands>"
+ "\nGeneral Commands: `boop, roll, choose, help, info, vote, "
+ "results, showpoll, wolfram, cplus, java,"
+ "beans, role, bet, yeet`";
help = LocStrings.Stub("HelpDisplay");
}
else
{
+2 -1
View File
@@ -7,6 +7,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import core.Command;
import core.LocStrings;
import core.Stats;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
@@ -25,7 +26,7 @@ public class CommandHelpBuilder extends Command {
}
@Override
public String HelpText() { return "Emit help for all commands as formatted HTML"; }
public String HelpText() { return LocStrings.Stub("HelpBuilderInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
+3 -4
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
@@ -14,13 +15,11 @@ public class CommandInfo extends Command
public CommandInfo(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Provides author info and a link to Kitty's website"; }
public String HelpText() { return LocStrings.Stub("InfoInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
String info = "I'm made by `Rin#8904` and `Reverie Wisp#3703`!\n"
+ "You can find more info about me along with a Patreon link to support us and GitHub link for filing bugs https://www.rinsnowmew.com/bot/" ;
res.Call(info);
res.Call(LocStrings.Stub("InfoResponse"));
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
import offline.Ref;
@@ -9,7 +10,7 @@ public class CommandInvite extends Command
public CommandInvite(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Provies a direct invite link for KittyBot"; }
public String HelpText() { return LocStrings.Stub("InviteInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
+8 -1
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
import network.NetworkJDoodle;
@@ -11,11 +12,17 @@ public class CommandJDoodle extends Command
public CommandJDoodle(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Will compile any java code you put in! Supports Java 1.8"; }
public String HelpText() { return LocStrings.Stub("JDoodleInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
if(input.args.trim().length() < 1)
{
res.Call(LocStrings.Stub("JDoodleError"));
return;
}
res.Call(compiler.compileJava(input.args));
}
}
+7 -4
View File
@@ -3,6 +3,7 @@ package commands;
import java.util.Random;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
@@ -22,7 +23,7 @@ public class CommandMap extends Command
public CommandMap(KittyRole roleLevel, KittyRating contentRating) { super(roleLevel, contentRating); }
@Override
public String HelpText() { return "Generates a map! You can pass additional information if you want with the flags `-s<seed> -w<width> -h<height>`. If one of the fields isn't provided, its default will be used. Note that adjusting the width and height impacts the map outcomes.\n\nDefault seed: Random,\nDefault Width: 35(max "+ MaxWidth + "),\nDefault Height: 25(max " + MaxHeight + ")"; }
public String HelpText() { return String.format(LocStrings.Stub("MapInfo"), "" + MaxWidth, "" + MaxHeight); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -59,7 +60,7 @@ public class CommandMap extends Command
}
catch(NumberFormatException e)
{
res.Call("Invalid arguments provided!");
res.Call(LocStrings.Stub("MapInvalid"));
return;
}
@@ -75,8 +76,10 @@ public class CommandMap extends Command
}
// Response header creation
header += "Using mapgen v0.1\n";
header += "Seed: `" + seed + "`, Width: `"+ width +"`, Height: `"+ height + "`\n";
header += LocStrings.Stub("MapVersion") + "\n";
header += LocStrings.Stub("MapSeed") + ": `" + seed + "`, ";
header += LocStrings.Stub("MapWidth") + "`"+ width +"`, ";
header += LocStrings.Stub("MapHeight") + ": `"+ height + "`\n";
// Response body creation
body += "```\n";
+2 -1
View File
@@ -8,6 +8,7 @@ import java.io.IOException;
import javax.imageio.ImageIO;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
@@ -22,7 +23,7 @@ public class CommandPerish extends Command
public CommandPerish(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Adds a red, 'a n g e r y' overlay to your icon or the icon of a friend you mentioned"; };
public String HelpText() { return LocStrings.Stub("PerishInfo"); };
private static Long num = 0l;
+2 -2
View File
@@ -15,12 +15,12 @@ public class CommandPing extends Command
public CommandPing(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Will respond with Pong!"; }
public String HelpText() { return LocStrings.Stub("PingInfo"); }
// Called when the command is run!
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
res.Call("Pong!");
res.Call(LocStrings.Stub("PingResponse"));
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ public class CommandPollManage extends Command
public CommandPollManage(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "'start' will start a new poll with the query of the line you put after, 'choice' will add a choice to the poll, 'stop' will end the poll"; }
public String HelpText() { return LocStrings.Stub("PollManageInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
+4 -3
View File
@@ -10,7 +10,7 @@ public class CommandPollResults extends Command
public CommandPollResults(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Will show current results of a poll and percentages of votes per choice"; }
public String HelpText() { return LocStrings.Stub("PollResultsInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -24,10 +24,11 @@ public class CommandPollResults extends Command
totalVotes += votes.get(i).votes;
}
results += "The poll was " + guild.poll + "\n";
for(int i = 0; i < votes.size(); i++)
{
results += votes.get(i).votes + " people voted for `" + votes.get(i).choice + "` with `" + (int)(((double)votes.get(i).votes) / ((double)totalVotes) * 100) + "%`!\n";
results += String.format(LocStrings.Stub("PollResultsResponse"), votes.get(i).votes, votes.get(i).choice, (int)(((double)votes.get(i).votes) / ((double)totalVotes) * 100));
}
res.Call(results);
+4 -4
View File
@@ -8,18 +8,18 @@ public class CommandPollShow extends Command
public CommandPollShow(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Will show the current poll running"; }
public String HelpText() { return LocStrings.Stub("PollShowInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
if(!guild.polling)
{
res.Call("There is no poll running!");
res.Call(LocStrings.Stub("PollShowNoPoll"));
return;
}
String poll = "The current poll is `" + guild.poll + "`\n";
poll += "And the choices are:\n";
String poll = String.format(LocStrings.Stub("PollShowPoll"), guild.poll);
poll += LocStrings.Stub("PollShowChoices");
for(int i = 0; i < guild.choices.size(); i++)
{
poll += (i+1) + ": `" + guild.choices.get(i).choice + "`\n";
+8 -6
View File
@@ -8,7 +8,7 @@ public class CommandPollVote extends Command
public CommandPollVote(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Vote in a poll with the choice number, won't work if no poll is running, you can't change your vote once you have cast it! Be careful!"; }
public String HelpText() { return LocStrings.Stub("PollVoteInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -17,7 +17,7 @@ public class CommandPollVote extends Command
{
if(guild.hasVoted.contains(user.uniqueID))
{
res.Call("You already voted");
res.Call(LocStrings.Stub("PollVoteAlreadyVoted"));
return;
}
try
@@ -25,21 +25,23 @@ public class CommandPollVote extends Command
int voteNum = Integer.parseInt(input.args)-1;
if(voteNum >= guild.choices.size() || voteNum < 0)
{
res.Call(voteNum + " That's not a vaild vote!");
res.Call(String.format(LocStrings.Stub("PollVoteNotValidVote"), voteNum));
return;
}
KittyPoll polled = guild.choices.get(voteNum);
polled.votes++;
guild.hasVoted.add(user.uniqueID);
res.Call("You successfully voted for `" + polled.choice + "`!");
res.Call(LocStrings.Stub("PollVoteSuccess") + " `" + polled.choice + "`!");
return;
}
catch (NumberFormatException e)
{
res.Call("That's not a vaild number!");
res.Call(LocStrings.Stub("PollVoteNotValidNumber"));
return;
}
}
res.Call("There is no poll running!");
res.Call(LocStrings.Stub("PollVoteNoPoll"));
}
}
+12 -5
View File
@@ -5,6 +5,7 @@ import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import core.Command;
import core.LocStrings;
import core.RPManager;
import dataStructures.*;
@@ -13,24 +14,30 @@ public class CommandRPEnd extends Command
public CommandRPEnd (KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Ends RP in channel"; }
public String HelpText() { return LocStrings.Stub("RPEndInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
File sending = null;
try {
try
{
sending = RPManager.instance.endRP(channel, user);
} catch (FileNotFoundException | UnsupportedEncodingException e)
}
catch (FileNotFoundException | UnsupportedEncodingException e)
{
System.out.println("I don't know how you got here");
}
if(sending != null)
{
res.CallFile(sending, "txt");
res.Call("Here's your file!");
res.Call(LocStrings.Stub("RPEndFileOut"));
}
else
res.Call("You can't end this RP!");
{
res.Call(LocStrings.Stub("RPEndError"));
}
}
}
+6 -3
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import core.rpg.RPGFramework;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
@@ -22,7 +23,7 @@ public class CommandRPG extends Command
}
@Override
public String HelpText() { return "Admin+ command only right now - experimental text RPG! Format is !rpg <rpg command>"; };
public String HelpText() { return LocStrings.Stub("RPGInfo"); };
@Override
@@ -30,7 +31,9 @@ public class CommandRPG extends Command
{
if(input.args == null || input.args.length() == 0)
{
res.Call(HelpText());
String output = HelpText();
System.out.println("Out: |" + output + "|");
res.Call(output);
return;
}
@@ -42,7 +45,7 @@ public class CommandRPG extends Command
if(result == null)
{
res.Call("Invalid RPG Command!");
res.Call(LocStrings.Stub("RPGInvalid"));
return;
}
+2 -1
View File
@@ -3,6 +3,7 @@ package commands;
import java.util.ArrayList;
import core.Command;
import core.LocStrings;
import core.RPManager;
import dataStructures.*;
@@ -11,7 +12,7 @@ public class CommandRPStart extends Command
public CommandRPStart (KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Starts an rp, add people to it by mentioning them!"; }
public String HelpText() { return LocStrings.Stub("RPStartInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
+5 -4
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
@@ -14,7 +15,7 @@ public class CommandRating extends Command
public CommandRating(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "'0' is fully sfw (Derpi and e621 searches are disabled), '1' is filtered (kitty auto appends a sfw tag on any searches), '2' is nsfw (any search will go through). Some other words are supported for setting filter as well."; }
public String HelpText() { return LocStrings.Stub("RatingInfo"); }
// Called when the command is run!
@Override
@@ -54,11 +55,11 @@ public class CommandRating extends Command
}
if(newRating != null)
res.Call("Kittybot content set to " + newRating);
res.Call(LocStrings.Stub("RatingChanged") + " " + newRating);
else
res.Call("Invalid content rating `" + input.args + "`");
res.Call(LocStrings.Stub("RatingInvalid") + " `" + input.args + "`");
if(newRating.equals("Filtered"))
res.Call("Warning: NSFW may slip through, images are only based on tags on their respective sites!");
res.Call(LocStrings.Stub("RatingWarning"));
}
}
+6 -5
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
public class CommandRole extends Command
@@ -8,20 +9,20 @@ public class CommandRole extends Command
public CommandRole (KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Will show the current role you have, Admins can change others roles with input of 'role x @y' with x being blacklist, general, mod, or admin. Blacklist will not allow the user to interact with kitty anymore, general mod and admin will give the user access to those commands."; }
public String HelpText() { return LocStrings.Stub("RoleInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
if(input.args.isEmpty())
{
res.Call("Your role is " + user.GetRole().name() + "!");
res.Call(LocStrings.Stub("RoleStandardResponse") + " " + user.GetRole().name() + "!");
return;
}
if(user.GetRole().getValue() < KittyRole.Admin.getValue())
{
res.Call("You aren't allowed to do that! You must have the KittyRole '" + KittyRole.Admin.toString() + "' or higher!");
res.Call(String.format(LocStrings.Stub("RoleError"), KittyRole.Admin.toString()));
return;
}
@@ -45,7 +46,7 @@ public class CommandRole extends Command
break;
default:
res.Call("Please enter `general`, `mod`, or `admin`!");
res.Call(LocStrings.Stub("RoleNeededRole"));
return;
}
String users = "";
@@ -55,6 +56,6 @@ public class CommandRole extends Command
users += input.mentions[i].name + " ";
}
res.Call("Changed " + users + " to role `" + newRole.name() + "`!");
res.Call(String.format(LocStrings.Stub("RoleChanged"), users, newRole.name()));
}
}
+2 -2
View File
@@ -16,7 +16,7 @@ public class CommandRoll extends Command
public CommandRoll(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Based on input of xdy where x is number of dice and y is faces kitty will roll that amount of dice, display the individual rolls and total"; }
public String HelpText() { return LocStrings.Stub("RollInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
@@ -27,7 +27,7 @@ public class CommandRoll extends Command
}
catch(Exception e)
{
res.Call("Didn't work ;3;");
res.Call(LocStrings.Stub("RollError"));
}
}
+8 -4
View File
@@ -2,6 +2,7 @@ package commands;
import core.Command;
import core.DatabaseManager;
import core.LocStrings;
import core.Stats;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
@@ -10,6 +11,8 @@ import dataStructures.KittyRole;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
import utils.GlobalLog;
import utils.LogFilter;
// NOTE(wisp): This is a sort of special command.
public class CommandShutdown extends Command
@@ -17,7 +20,7 @@ public class CommandShutdown extends Command
public CommandShutdown(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Stops kitty. `-s` or `safe` as an argument attempts to sync off the database before shutdown."; }
public String HelpText() { return LocStrings.Stub("ShutdownInfo"); }
// Called when the command is run!
@Override
@@ -40,13 +43,14 @@ public class CommandShutdown extends Command
if(isSafe)
{
DatabaseManager.instance.Upkeep(); // Force upkeep, this works so long as on main thread.
res.CallImmediate("Forced shutdown, database synced before abandoning threads.");
// Force upkeep, this works so long as upkeep is on the main thread.
DatabaseManager.instance.Upkeep();
GlobalLog.Error(LogFilter.Command, "Forced shutdown, database synced before abandoning threads.");
System.exit(0);
}
else
{
res.CallImmediate("Forced immediate shutdown, threads abandoned without sync.");
GlobalLog.Error(LogFilter.Command, "Forced immediate shutdown, threads abandoned without sync.");
System.exit(0);
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ import java.io.IOException;
import javax.imageio.ImageIO;
import core.Command;
import core.LocStrings;
import dataStructures.*;
import utils.ImageUtils;
@@ -16,7 +17,7 @@ public class CommandStark extends Command
public CommandStark(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Snaps your icon or the icon of a friend you mentioned"; };
public String HelpText() { return LocStrings.Stub("StarkInfo"); };
private static Long num = 0l;
+2 -1
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import core.CommandManager.ThreadData;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
@@ -16,7 +17,7 @@ public class CommandStats extends Command
public CommandStats(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "Displays the actively running KittyBot application information"; }
public String HelpText() { return LocStrings.Stub("StatsInfo"); }
// Called when the command is run!
@Override
+71
View File
@@ -0,0 +1,71 @@
package commands;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
import dataStructures.KittyRole;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
import utils.ImageOverlayBuilder;
import utils.ImageUtils;
public class CommandTeey extends Command
{
// Required constructor
public CommandTeey(KittyRole level, KittyRating rating) { super(level, rating); }
private static Long num = 0l;
@Override
public String HelpText() { return LocStrings.Stub("TeeyInfo"); }
// Called when the command is run!
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
String name = null;
File teeyFile = null;
File teeyeeFile = null;
synchronized(num)
{
name = "teey_" + num + ".gif";
++num;
}
try
{
KittyUser person = null;
if(input.mentions == null)
person = user;
else
person = input.mentions[0];
String yeeteeFilename = ImageUtils.DownloadFromURL(person.avatarID, ".png");
if(yeeteeFilename == null)
return;
teeyeeFile = new File(yeeteeFilename);
ImageOverlayBuilder builder = new ImageOverlayBuilder("assets/teey/frames/", "teey ", 24, 18);
builder.Overlay(ImageIO.read(teeyeeFile), name);
}
catch (IOException e)
{
e.printStackTrace();
}
teeyFile = new File (name);
res.CallFile(teeyFile, "gif");
// Thread cleanup...
ImageUtils.BlockingFileDelete(teeyFile);
ImageUtils.BlockingFileDelete(teeyeeFile);
}
}
+4 -2
View File
@@ -1,6 +1,7 @@
package commands;
import core.Command;
import core.LocStrings;
import dataStructures.*;
import network.NetworkTwitter;
@@ -10,15 +11,16 @@ public class CommandTweet extends Command
public CommandTweet(KittyRole level, KittyRating rating) { super(level, rating); }
@Override
public String HelpText() { return "With an input of x,y,z where x y and z are all choices, kitty will choose one"; }
public String HelpText() { return LocStrings.Stub("TweetInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
//TODO: Figure out how to pass pictures correctly
try {
res.Call(tweet.tweet(input.args));
} catch (Exception e) {
res.Call("FUCKED UP HARD");
res.Call(LocStrings.Stub("TweetError"));
}
}
}
+4 -3
View File
@@ -3,6 +3,7 @@ package commands;
import java.io.File;
import java.io.IOException;
import core.Command;
import core.LocStrings;
import dataStructures.*;
import network.*;
import utils.ImageUtils;
@@ -14,13 +15,13 @@ public class CommandWolfram extends Command
public CommandWolfram(KittyRole level, KittyRating rating) { super(level, rating);}
@Override
public String HelpText() { return "Will query wolframalpha with your question and give a full image output of the answer"; }
public String HelpText() { return LocStrings.Stub("WolframInfo"); }
@Override
public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res)
{
if(input.args == null || input.args.trim().length() == 0)
res.Call("You need to provide some arguments!");
res.Call(LocStrings.Stub("WolframNoArgs"));
try
{
@@ -30,7 +31,7 @@ public class CommandWolfram extends Command
}
catch (IOException e)
{
res.Call("Something went wrong!");
res.Call(LocStrings.Stub("WolframError"));
}
}
}
+5 -270
View File
@@ -1,25 +1,10 @@
package commands;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
import core.Command;
import core.LocStrings;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
@@ -27,18 +12,18 @@ import dataStructures.KittyRole;
import dataStructures.KittyUser;
import dataStructures.Response;
import dataStructures.UserInput;
import utils.ImageOverlayBuilder;
import utils.ImageUtils;
public class CommandYeet extends Command
{
// Required constructor
public CommandYeet(KittyRole level, KittyRating rating) { super(level, rating); }
private static Long num = 0l;
@Override
public String HelpText() { return "Yeet yourself or yeet a friend with @!"; }
public String HelpText() { return LocStrings.Stub("YeetInfo"); }
// Called when the command is run!
@Override
@@ -59,20 +44,17 @@ public class CommandYeet extends Command
KittyUser person = null;
if(input.mentions == null)
{
person = user;
}
else
{
person = input.mentions[0];
}
String yeeteeFilename = ImageUtils.DownloadFromURL(person.avatarID, ".png");
if(yeeteeFilename == null)
return;
yeeteeFile = new File(yeeteeFilename);
YEET(ImageIO.read(yeeteeFile), name);
ImageOverlayBuilder builder = new ImageOverlayBuilder("assets/yeet/frames/", "yeet ", 24, 18);
builder.Overlay(ImageIO.read(yeeteeFile), name);
}
catch (IOException e)
{
@@ -86,251 +68,4 @@ public class CommandYeet extends Command
ImageUtils.BlockingFileDelete(yeetFile);
ImageUtils.BlockingFileDelete(yeeteeFile);
}
// Lets get some trash established for the yeet
public static final boolean VERBOSE = false;
public static final int NONE = -1;
public static final int PIXEL_BYTE_LENGTH = 4; // 32-bit PNG, RGBA
public static final String YEET_BASE_PATH = "assets/yeet/frames/";
public static final int YEET_BASE_SIZE = 24;
public static final int YEET_FPS = 18;
// Some small logging functions... cause we're professionals...
public static void Verbose(String str) { if(VERBOSE) Log("[Verbose] " + str); }
public static void Log(String str) { System.out.println("[Log] " + str); }
public static void Warn(String str) { System.out.println("[Warn] " + str); }
public static void Error(String str) { System.out.println("[Error] " + str); }
// Performs the Y E E T (image overlay per frame specified, catches IO errors as a note.)
public static void YEET(BufferedImage overlay, String outpath)
{
try
{
long start = Calendar.getInstance().getTimeInMillis();
ProcessYeet(overlay, outpath);
long end = Calendar.getInstance().getTimeInMillis();
Log("Took " + (end - start) + "ms");
}
catch (IOException e)
{
e.printStackTrace();
}
}
// Processing the image frames to construct a gif. Relies on (0,255,0) pixel for image center specification.
public static void ProcessYeet(BufferedImage overlay, String outfileName) throws IOException
{
BufferedImage[] frames = new BufferedImage[YEET_BASE_SIZE];
for(int i = 0; i < YEET_BASE_SIZE; ++i)
{
BufferedImage out = CombineImages(YEET_BASE_PATH + "yeet " + i + ".png", overlay);
if(out != null)
frames[i] = out;
else
Error("There was an issue with frame " + i);
}
Verbose("Processed " + YEET_BASE_SIZE + " frames, writing...");
ImageOutputStream output = new FileImageOutputStream(new File(outfileName));
GifSequenceWriter writer = new GifSequenceWriter(output, frames[0].getType(), YEET_FPS, true);
for(int i = 0; i < YEET_BASE_SIZE; ++i)
writer.writeToSequence(frames[i]);
writer.close();
output.close();
}
// Performs an overlay at a pixel location. We're making some assumptions here, mostly that there is going
// to be an RGBA-32-bit encoded PNG image read in for our parsing purposes. We then look for a 0, 255, 0
// green pixel on the base frame to apply the overlay buffer to, centered.
public static BufferedImage CombineImages(String pathBase, BufferedImage overlay) throws IOException
{
// Acquire images
BufferedImage imageBase = null;
BufferedImage imageOverlay = overlay;
imageBase = ImageIO.read(new File(pathBase));
// Grab data we know won't change. As a side note, I discovered you can get specific pixels a bit
// differently later, but it was too late, I wrote the pixel specific code already.
final byte[] pixelsBase = ((DataBufferByte) imageBase.getRaster().getDataBuffer()).getData();
final int baseWidth = imageBase.getWidth();
final int overlayWidth = imageOverlay.getWidth();
final int baseHeight = imageBase.getHeight();
final int overlayHeight = imageOverlay.getHeight();
// Warn about odd sizing when applicable
if(overlayHeight > baseHeight || overlayWidth > baseWidth)
Warn("Size mismatch, overlay is larger. May function, but not supported.");
// Skim base picture for solid green pixel to use as target center
int targetX = NONE;
int targetY = NONE;
for(int i = 0; i < pixelsBase.length; i += PIXEL_BYTE_LENGTH)
{
final int x = ((i / PIXEL_BYTE_LENGTH) % baseWidth);
final int y = ((i / PIXEL_BYTE_LENGTH) / baseWidth);
final int r = (pixelsBase[i]);
final int g = (pixelsBase[i + 1]);
final int b = (pixelsBase[i + 2]);
//final int a = (pixelsBase[i + 3]);
// Max contrast in 32-bit PNG for green is no red no blue, max green (ratio), max alpha (implied 0 here)
if(r == -1 && g == 0 && b == -1)
{
//Log("Found target pixel at x: " + x + " y: " + y);
targetX = x;
targetY = y;
break;
}
}
// Skip overlay if we have no target location
if(targetX == NONE || targetY == NONE)
{
Verbose("No overlay required for this frame.");
return imageBase;
}
// Re-create a new byte array and establish overlay bounds
final int left = targetX - overlayWidth / 2;
final int right = targetX + overlayWidth / 2;
final int top = targetY - overlayHeight / 2;
final int bottom = targetY + overlayHeight / 2;
// Apply overlay when appropriate
for(int i = 0; i < pixelsBase.length; i += PIXEL_BYTE_LENGTH)
{
final int x = ((i / PIXEL_BYTE_LENGTH) % baseWidth);
final int y = ((i / PIXEL_BYTE_LENGTH) / baseWidth);
// If we're within the bounds of where the overlay picture should go...
if(x > left && x < right && y > top && y < bottom)
{
final int overlayX = x - left;
final int overlayY = y - top;
// Skip out of bounds pixels
if(overlayX < 0 || overlayY < 0 || overlayX >= baseWidth || overlayY >= baseHeight)
continue;
// Re-write bytes as overlay
imageBase.setRGB(x, y, imageOverlay.getRGB(overlayX, overlayY));
}
}
// Give the frame back
return imageBase;
}
// GifSequenceWriter.java
//
// Created by Elliot Kroo on 2009-04-25,
// (Small modifications by wisp in 2018)
//
// This work is licensed under the Creative Commons Attribution 3.0 Unported License.
// To view a copy of this license visit http://creativecommons.org/licenses/by/3.0/
// NOTE(wisp): This is Elliot's original license
public static class GifSequenceWriter
{
protected ImageWriter gifWriter;
protected ImageWriteParam imageWriteParam;
protected IIOMetadata imageMetaData;
// NOTE: This is limited to things that divide into 1000 cleanly as multiples of 10.
public GifSequenceWriter(ImageOutputStream outputStream, int imageType, int framesPerSecond, boolean isLooping) throws IIOException, IOException
{
gifWriter = getWriter();
imageWriteParam = gifWriter.getDefaultWriteParam();
ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType);
imageMetaData = gifWriter.getDefaultImageMetadata(imageTypeSpecifier, imageWriteParam);
String metaFormatName = imageMetaData.getNativeMetadataFormatName();
IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName);
IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("delayTime", "" + (int) ((1000.0 / framesPerSecond) / 10.0)); // THis line isn't super well honored
graphicsControlExtensionNode.setAttribute("transparentColorIndex","0");
IIOMetadataNode appEntensionsNode = getNode( root, "ApplicationExtensions");
IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");
child.setAttribute("applicationID", "NETSCAPE");
child.setAttribute("authenticationCode", "2.0");
int loopValue = isLooping ? 0 : 1;
child.setUserObject(new byte[]{ 0x1, (byte) (loopValue & 0xFF), (byte) ((loopValue >> 8) & 0xFF)});
appEntensionsNode.appendChild(child);
imageMetaData.setFromTree(metaFormatName, root);
gifWriter.setOutput(outputStream);
gifWriter.prepareWriteSequence(null);
}
public void writeToSequence(RenderedImage img) throws IOException
{
gifWriter.writeToSequence(new IIOImage(img, null, imageMetaData), imageWriteParam);
}
/**
* Close this GifSequenceWriter object. This does not close the underlying
* stream, just finishes off the GIF.
*/
public void close() throws IOException
{
gifWriter.endWriteSequence();
}
/**
* Returns the first available GIF ImageWriter using
* ImageIO.getImageWritersBySuffix("gif").
*
* @return a GIF ImageWriter object
* @throws IIOException if no GIF image writers are returned
*/
private static ImageWriter getWriter() throws IIOException
{
Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif");
if(!iter.hasNext())
throw new IIOException("No GIF Image Writers Exist");
else
return iter.next();
}
/**
* Returns an existing child node, or creates and returns a new child node (if
* the requested node does not exist).
*
* @param rootNode the <tt>IIOMetadataNode</tt> to search for the child node.
* @param nodeName the name of the child node.
*
* @return the child node, if found or a new node created with the given name.
*/
private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName)
{
int nodeCount = rootNode.getLength();
for (int i = 0; i < nodeCount; i++)
{
if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) == 0)
return((IIOMetadataNode) rootNode.item(i));
}
IIOMetadataNode node = new IIOMetadataNode(nodeName);
rootNode.appendChild(node);
return(node);
}
}
}
+1 -2
View File
@@ -1,7 +1,6 @@
package core;
import java.util.ArrayList;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
@@ -42,7 +41,7 @@ public abstract class Command
return false;
}
//TODO(wisp, rin): ADD CHANNEL CHECK HERE
//TODO: ADD CHANNEL CHECK HERE
if(user.GetRole().getValue() >= roleLevel.getValue())
{
+124
View File
@@ -0,0 +1,124 @@
package core;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import utils.FileUtils;
import utils.GlobalLog;
import utils.LogFilter;
// While some of the patterns in this file are similar to the localization files,
// commands that are being looked up will behave slightly differently so trimming
// rules for this file are different than the localization ones - this is more
// aggresive with whitespace removal.
public class CommandEnabler
{
// Config/const variables
public static final String filename = "commands.config";
public static final String pairSplit = "=";
public static final char pairSeparator = '\n';
public static final String enabled = "1";
public static final String disabled = "0";
public static final boolean defaultEnabledState = true;
// Local variables
private HashMap<String, Boolean> enabledMap; // Quick lookup
private ArrayList<String> keyList; // Tracking ordering for later
public CommandEnabler()
{
GlobalLog.Log(LogFilter.Core, "Initializing " + this.getClass().getSimpleName());
enabledMap = new HashMap<>();
keyList = new ArrayList<>();
ReadIn();
GetTrackedCommands();
WriteOut();
}
// Reads in the config file and parses it, keeping tabs on the order it read things
private void ReadIn()
{
File f = new File(filename);
if(f.isFile() && f.canRead())
{
String content = FileUtils.ReadContent(f).trim();
String[] lines = content.split("" + pairSeparator);
for(int i = 0; i < lines.length; ++i)
{
String[] pair = lines[i].split(pairSplit);
if(pair.length < 2)
continue;
String key = pair[0].trim();
String value = pair[1].trim().toLowerCase();
keyList.add(key);
if(value.equalsIgnoreCase(enabled))
enabledMap.putIfAbsent(key, true);
else
enabledMap.putIfAbsent(key, false);
}
}
}
// Look up the already scraped values from the localizer and store them if they
// don't already exist in the lookup. Defaults to defaultEnabledState.
private void GetTrackedCommands()
{
ArrayList<String> unloc = LocCommands.GetUnlocalizedCommands();
for(int i = 0; i < unloc.size(); ++i)
{
String command = unloc.get(i);
if(enabledMap.putIfAbsent(command, defaultEnabledState) == null)
{
GlobalLog.Log(LogFilter.Strings, "Identified new toggleable raw command: " + command);
keyList.add(command);
}
}
}
// Write out enabled/disabled file info.
private void WriteOut()
{
try
{
String outString = "";
for(int i = 0; i < keyList.size(); ++i)
{
String key = keyList.get(i);
String value = enabled;
if(enabledMap.get(key) == false)
value = disabled;
outString += key + pairSplit + value + pairSeparator;
}
BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
writer.write(outString);
writer.close();
}
catch (IOException e)
{
GlobalLog.Error(LogFilter.Core, "Command enabler issue writing file! " + e.getMessage());
}
}
// Looks up a key to see if it's enabled or not
public boolean IsEnabled(String key)
{
if(enabledMap.containsKey(key))
return enabledMap.get(key);
return true;
}
}
+28 -9
View File
@@ -6,6 +6,7 @@ import java.util.Map.Entry;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyUser;
import dataStructures.Pair;
import dataStructures.Response;
import dataStructures.UserInput;
import utils.GlobalLog;
@@ -16,23 +17,41 @@ public class CommandManager
// Variables
private HashMap<String, Command> commands;
private ArrayList<CommandThread> threadAccumulator;
private CommandEnabler commandEnabler;
private long invokeCount;
// Default Constructor
public CommandManager()
public CommandManager(CommandEnabler commandEnabler)
{
commands = new HashMap<String, Command>();
threadAccumulator = new ArrayList<CommandThread>();
invokeCount = 0;
this.commands = new HashMap<String, Command>();
this.threadAccumulator = new ArrayList<CommandThread>();
this.invokeCount = 0;
this.commandEnabler = commandEnabler;
}
// Allows the command manager to keep track of a command.
public void Register(String key, Command command)
// Allows the command manager to keep track of a command. Takes a pair (the un-localized and localzied commands)
// and the command associated with the localized commands.
public void Register(Pair<String, String> pair, Command command)
{
if(key == null)
if(pair == null || pair.Second == null)
return;
// If we haven't already split a multisplit command (or even assessed that),
// then verify if we even need to register the commands at all. If it's
// not enabled, don't register it.
if(pair.First != null && !commandEnabler.IsEnabled(pair.First))
return;
String key = pair.Second;
if(key.contains(","))
{
String[] keys = key.split(",");
Register(keys, command);
return;
}
key = key.toLowerCase();
command.registeredNames.add(key);
@@ -51,7 +70,7 @@ public class CommandManager
public void Register(String[] keys, Command command)
{
for(int i = 0; i < keys.length; ++i)
Register(keys[i], command);
Register(new Pair<String, String>(null, keys[i].trim()), command);
}
// Calls the command but on a whole new thread!
+190
View File
@@ -0,0 +1,190 @@
package core;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import dataStructures.TaggedPairStore;
import utils.FileUtils;
import utils.GlobalLog;
import utils.LogFilter;
// A quick-and-dirty localization tool that scrapes the project for calls to itself, then
// generates/updates a file externally with all the stub values as keys that are localized.
public abstract class LocBase
{
// Pre-defined values
public static final String KittySourceDirectory = "./src";
// Filename
public final String filename; // Example: "localization.config";
public final String functionName; // Example: "Localizer.Stub";
// Local translation storage
protected TaggedPairStore stringStore;
// Logging
private void Log(String str) { GlobalLog.Log(LogFilter.Strings, str); }
private void Warn(String str) { GlobalLog.Warn(LogFilter.Strings, str); }
private void Error(String str) { GlobalLog.Error(LogFilter.Strings, str); }
// Ok... so this is an array because if it's not an array, the parser will parse the string
public LocBase(String filename, String functionName)
{
this.filename = filename;
this.functionName = functionName;
}
// Structure used for holding a pair of strings and any other info we need
// about localized information that is being looked up.
private class LocInfo
{
public String file;
public String phrase;
public LocInfo(String f, String p)
{
this.file = f;
this.phrase = p;
}
}
// Do processing on each path in the scraped directory here, assuming it's .java
public void StripForContents(Path path, ArrayList<LocInfo> strings)
{
String filename = path.getFileName().toString();
if(filename.contains(".java"))
{
String contents = FileUtils.ReadContent(path);
String[] split = contents.split(functionName);
// Identify all localizer function calls
for(int i = 1; i < split.length; ++i)
{
int loc = split[i].indexOf(")");
String noWhitespace = split[i].replaceAll("\\s+","");
if(loc != -1)
{
if(noWhitespace.charAt(noWhitespace.indexOf(")") - 1) == '"' && split[i].charAt(loc - 2) != '\\')
{
// At this point, we find the first ), then verify there's a ") behind it, and that
// the " is not an escaped character.
try
{
String toLocalize = split[i].substring(2, loc - 1);
strings.add(new LocInfo(filename.substring(0, filename.lastIndexOf('.')), toLocalize));
Log("Found lookup call in " + path + ": " + toLocalize);
}
catch(IndexOutOfBoundsException e)
{
Warn("Found phrase but couldn't parse in file " + path);
}
}
}
}
}
}
// Nothing for now, but in the future will return a parsed and localized version of
// the string in question if one can be found. If the localized string is empty,
// returns a the key instead which is the default phrase.
public String GetKey(String input)
{
if(stringStore == null)
return input;
String value = stringStore.GetKey(input);
if(value == null || value.trim().length() < 1)
return input;
return value;
}
// Reads a file to string, adapted from https://stackoverflow.com/a/326440/5383198
private String ReadFileAsString(String path, Charset encoding)
{
try
{
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}
catch (IOException e)
{
Warn("No file found to read from!");
}
return null;
}
// Update localization from the disk on file. Creates the file if it doesn't exist.
// This file is internally formatted as an ini file.
public void UpdateLocFromDisk()
{
Log("Attempting to read localization file: " + filename);
try
{
String fileContents = ReadFileAsString(filename, Charset.defaultCharset());
if(fileContents == null)
{
File file = new File(filename);
file.createNewFile();
}
stringStore = new TaggedPairStore(fileContents);
}
catch(IOException e)
{
Error("IO exception during localization file read");
}
}
// Rewrites out at the specified filename with existing stubs.
// This preserves existing localized phrases.
public void SaveLocToDisk()
{
Log("Attempting to write updated localization file");
try
{
PrintWriter pw = new PrintWriter(filename);
pw.println(stringStore.toString());
pw.close();
}
catch(IOException e)
{
Error("IO exception during localization file write");
}
}
private void TryStripSpecified(Path path, ArrayList<LocInfo> toFill)
{
try
{
StripForContents(path, toFill);
}
catch(Exception e)
{
Error("issue with file: " + path.toString());
}
}
// Scrape the project and generate all the possible localizeable phrases.
// This stubs out phrases to be localized.
public void ScrapeAll()
{
ArrayList<LocInfo> localizeList = new ArrayList<LocInfo>();
FileUtils.AcquireAllFiles(KittySourceDirectory).forEach((path) -> TryStripSpecified(path, localizeList));
for(LocInfo toStub : localizeList)
stringStore.AddKeyValue(toStub.file, toStub.phrase, toStub.phrase);
}
}
+50
View File
@@ -0,0 +1,50 @@
package core;
import java.util.ArrayList;
import utils.GlobalLog;
import utils.LogFilter;
import dataStructures.Pair;
// Performs the same localization for the strings associated with command names as
// is performed with general strings in the application
public class LocCommands extends LocBase
{
public static final String fileName = "locCommands.config";
public static final String function = "LocCommands.Stub";
private static LocCommands instance;
public LocCommands()
{
super(fileName, function);
GlobalLog.Log(LogFilter.Core, "Initializing " + this.getClass().getSimpleName());
if(instance == null)
{
instance = this;
UpdateLocFromDisk();
ScrapeAll();
SaveLocToDisk();
}
else
{
GlobalLog.Error(LogFilter.Core, "You can't have two of the following: " + this.getClass().getSimpleName());
}
}
// Returns a pair, the raw key and the stub key
public static Pair<String, String> Stub(String toStub)
{
return new Pair<String, String>(toStub, instance.GetKey(toStub));
}
// Gets all of the un-translated defaults in the commands list.
public static ArrayList<String> GetUnlocalizedCommands()
{
ArrayList<String> raw = new ArrayList<>();
instance.stringStore.ForEach((pair) -> raw.add((String)((Pair<?, ?>)pair).First ));
return raw;
}
}
+40
View File
@@ -0,0 +1,40 @@
package core;
import utils.GlobalLog;
import utils.LogFilter;
// A quick-and-dirty localization tool that scrapes the project for calls to itself, then
// generates/updates a file externally (phrases.config) with all the stub values as keys that
// can then be localized.
public class LocStrings extends LocBase
{
public static final String fileName = "locStrings.config";
public static final String function = "LocStrings.Stub";
private static LocStrings instance;
public LocStrings()
{
super(fileName, function);
GlobalLog.Log(LogFilter.Core, "Initializing " + this.getClass().getSimpleName());
if(instance == null)
{
instance = this;
UpdateLocFromDisk();
ScrapeAll();
SaveLocToDisk();
}
else
{
GlobalLog.Error(LogFilter.Core, "You can't have two of the following: " + this.getClass().getSimpleName());
}
}
public static String Stub(String toStub)
{
return instance.GetKey(toStub);
}
}
-182
View File
@@ -1,182 +0,0 @@
package core;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import org.ini4j.InvalidFileFormatException;
import org.ini4j.Profile.Section;
import org.ini4j.Wini;
import utils.FileUtils;
import utils.GlobalLog;
import utils.LogFilter;
// A quick-and-dirty localization tool that scrapes the project for calls to itself, then
// generates/updates a file externally (phrases.config) with all the stub values as keys that
// can then be localized.
public class Localizer
{
// Filename
public final static String filename = "localization.config";
public final static String functionName = "Localizer.Stub";
// Local translation
private static HashMap<String, LocInfo> translated = new HashMap<String, LocInfo>();
// Logging
private static void Log(String str) { GlobalLog.Log(LogFilter.Strings, str); }
private static void Warn(String str) { GlobalLog.Warn(LogFilter.Strings, str); }
private static void Error(String str) { GlobalLog.Error(LogFilter.Strings, str); }
// Structure used for holding a pair of strings and any other info we need
// about localized information that is being looked up.
private static class LocInfo
{
public String file;
public String phrase;
public LocInfo(String f, String p)
{
this.file = f;
this.phrase = p;
}
}
// Do processing on each path in the scraped directory here, assuming it's .java
public static void StripForContents(Path path, ArrayList<LocInfo> strings)
{
String filename = path.getFileName().toString();
if(filename.contains(".java"))
{
String contents = FileUtils.ReadContent(path);
String[] split = contents.split(functionName);
// Identify all localizer function calls
for(int i = 1; i < split.length; ++i)
{
int loc = split[i].indexOf(")");
if(loc != -1)
{
if(split[i].charAt(loc - 1) == '"' && split[i].charAt(loc - 2) != '\\')
{
// At this point, we find the first ), then verify there's a ") behind it, and that
// the " is not an escaped character.
try
{
String toLocalize = split[i].substring(2, loc - 1);
strings.add(new LocInfo(filename.substring(0, filename.lastIndexOf('.')), toLocalize));
Log("Found stubbed phrase in " + path + " : " + toLocalize);
}
catch(IndexOutOfBoundsException e)
{
Warn("Found phrase but couldn't parse in file " + path);
}
}
}
}
}
}
// Nothing for now, but in the future will return a parsed and localized version of
// the string in question if one can be found.
public static String Stub(String input)
{
if(translated.containsKey(input))
{
String value = translated.get(input).phrase;
if(value.trim().length() > 0)
return value;
}
return input;
}
// Update localization from the disk on file. Creates the file if it doesn't exist.
// This file is internally formatted as an ini file.
public static void UpdateLocFromDisk()
{
Log("Attempting to read localization file");
try
{
File file = new File(filename);
file.createNewFile();
Wini ini = new Wini(file);
for(String str : ini.keySet())
{
// Store everything in all .ini sections
Section sec = ini.get(str);
for(String s : sec.keySet())
{
if(!translated.containsKey(s))
translated.put(s, new LocInfo(sec.getName(), sec.get(s, String.class)));
}
}
}
catch(InvalidFileFormatException e)
{
Error("File issue with format during localization file read");
}
catch(IOException e)
{
Error("IO exception during localization file read");
}
}
// Rewrites out at the specified filename with existing stubs.
// This preserves existing localized phrases.
public static void SaveLocToDisk()
{
Log("Attempting to write updated localization file");
try
{
PrintWriter pw = new PrintWriter(filename);
pw.close();
File file = new File(filename);
file.createNewFile();
Wini ini = new Wini(file);
for(String s : translated.keySet())
{
LocInfo toWrite = translated.get(s);
ini.put(toWrite.file, s, toWrite.phrase);
}
ini.store();
}
catch(InvalidFileFormatException e)
{
Error("File issue with format during localization file write");
}
catch(IOException e)
{
Error("IO exception during localization file write");
}
}
// Scrape the project and generate all the possible localizeable phrases.
// This stubs out phrases to be localized.
public static void ScrapeAll()
{
ArrayList<LocInfo> localizeList = new ArrayList<LocInfo>();
FileUtils.AcquireAllFiles(".\\src").forEach((path) -> StripForContents(path, localizeList));
for(LocInfo toStub : localizeList)
{
if(!translated.containsKey(toStub.phrase))
{
translated.put(toStub.phrase, new LocInfo(toStub.file, ""));
Log("Found new stubbed phrase '" + toStub.phrase + "' in " + toStub.file);
}
}
}
}
+122 -51
View File
@@ -3,10 +3,51 @@ package core;
import java.util.HashMap;
import java.util.concurrent.Semaphore;
import commands.*;
import dataStructures.*;
import commands.CommandAddGuildRole;
import commands.CommandAllowedGuildRole;
import commands.CommandBeansShow;
import commands.CommandBetBeans;
import commands.CommandBlurry;
import commands.CommandBoop;
import commands.CommandChangeIndicator;
import commands.CommandChoose;
import commands.CommandColiru;
import commands.CommandDoWork;
import commands.CommandEightBall;
import commands.CommandGiveBeans;
import commands.CommandHelp;
import commands.CommandHelpBuilder;
import commands.CommandInfo;
import commands.CommandInvite;
import commands.CommandJDoodle;
import commands.CommandMap;
import commands.CommandPerish;
import commands.CommandPing;
import commands.CommandPollManage;
import commands.CommandPollResults;
import commands.CommandPollShow;
import commands.CommandPollVote;
import commands.CommandRPEnd;
import commands.CommandRPG;
import commands.CommandRPStart;
import commands.CommandRating;
import commands.CommandRole;
import commands.CommandRoll;
import commands.CommandShutdown;
import commands.CommandStark;
import commands.CommandStats;
import commands.CommandTeey;
import commands.CommandTweet;
import commands.CommandWolfram;
import commands.CommandYeet;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
import dataStructures.KittyRating;
import dataStructures.KittyRole;
import dataStructures.KittyUser;
import net.dv8tion.jda.core.entities.Member;
import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent;
import utils.AdminControl;
import utils.GlobalLog;
import utils.LogFilter;
@@ -34,9 +75,20 @@ public class ObjectBuilderFactory
// RPManger for tracking RP system
private static RPManager rpManager;
// Lazy initialization style for
// Localization classes - these are singletons, but should be initialized before almost all other
// things so their inclusion in the factory is to ensure they're started at the correct time.
@SuppressWarnings("unused") private static LocStrings locStrings;
@SuppressWarnings("unused") private static LocCommands locCommands;
// Handles if we can or can't use specific commands, parsing a config file based on loc data to do so.
private static CommandEnabler commandEnabler;
// Lazy initialization multithreaded mutex stuff to prevent explosions.
// TODO: Investigate using 'synchronized' instead potentially
private static boolean hasInitialized;
private static Semaphore initMutex = new Semaphore(1);
// This is it, this is how the lazy init starts!
private static void LazyInit()
{
if(hasInitialized)
@@ -47,26 +99,31 @@ public class ObjectBuilderFactory
initMutex.acquire();
try
{
// Initialization here. This is where we could read from something external.
// structure initialization
// Construct necessary data structures.
guildCache = new HashMap<String, KittyGuild>();
userCache = new HashMap<String, KittyUser>();
channelCache = new HashMap<String, KittyChannel>();
database = null;
stats = null;
// Start by reading from things that are external. Because
// we require these things to be resolved before the rest of the application,
// we place them here.
locStrings = new LocStrings();
locCommands = new LocCommands();
}
finally
{
initMutex.release();
hasInitialized = true;
}
}
catch(InterruptedException ie)
{
GlobalLog.Error(LogFilter.Core, "Issue during object builder lazy initialization."
+ " The factory was not initialized, "
+ "and kitty will not be able to continue functionally.");
+ " The factory was not initialized, and kitty will not be able to continue functionally.");
}
hasInitialized = true;
}
// Explicitly locks: guildCache
@@ -79,7 +136,7 @@ public class ObjectBuilderFactory
// look up the guild.
String uid = event.getGuild().getId();
// ince we're lazily initialized, we can synchronize w/ the
// once we're lazily initialized, we can synchronize w/ the
// guildCache object now instead of having to use a mutex.
KittyGuild guild = null;
synchronized (guildCache)
@@ -92,7 +149,7 @@ public class ObjectBuilderFactory
else
{
// Construct a new guild with defaults
guild = new KittyGuild(uid);
guild = new KittyGuild(uid, new AdminControl(event.getGuild()));
DatabaseManager.instance.Register(guild);
guildCache.put(uid, guild);
}
@@ -276,58 +333,72 @@ public class ObjectBuilderFactory
user.avatarID = member.getUser().getAvatarUrl();
}
// Default construction of the command manager.
// TODO(wisp): We want to be able to keep all this data
// stored off in a file at some point, so we can reflect it onto the
// project and build it per-guild. That's for later now tho.
public static CommandManager ConstructCommandManager()
// Constructs a CommandEnabler if it doesn't exist, and gets the existing one if it does.
public static CommandEnabler ConstructCommandEnabler()
{
LazyInit();
CommandManager manager = new CommandManager();
if(commandEnabler == null)
commandEnabler = new CommandEnabler();
manager.Register("work", new CommandDoWork(KittyRole.Dev, KittyRating.Safe));
manager.Register("shutdown", new CommandShutdown(KittyRole.Dev, KittyRating.Safe));
manager.Register("stats", new CommandStats(KittyRole.Dev, KittyRating.Safe));
manager.Register("invite", new CommandInvite(KittyRole.Dev, KittyRating.Safe));
manager.Register("buildHelp", new CommandHelpBuilder(KittyRole.Dev, KittyRating.Safe));
manager.Register("tweet", new CommandTweet(KittyRole.Dev, KittyRating.Safe));
return commandEnabler;
}
manager.Register("rating", new CommandRating(KittyRole.Admin, KittyRating.Safe));
manager.Register("indicator", new CommandChangeIndicator(KittyRole.Admin, KittyRating.Safe));
// Default construction of the command manager. In order to remotely resolve command enabling
// and disabling, what we do is construct the commands with a localized pair that is checked against
// the CommandEnabler object passed in. In theory, we could have multiple CommandManagers, tho we can
// only have one CommandEnabler.
public static CommandManager ConstructCommandManager(CommandEnabler commandEnabler)
{
LazyInit();
manager.Register("poll", new CommandPollManage(KittyRole.Mod, KittyRating.Safe));
manager.Register("givebeans", new CommandGiveBeans(KittyRole.Mod, KittyRating.Safe));
manager.Register("rpg", new CommandRPG(KittyRole.Mod, KittyRating.Safe));
CommandManager manager = new CommandManager(commandEnabler);
manager.Register(new String[]{"perish", "thenperish"}, new CommandPerish(KittyRole.General, KittyRating.Safe));
manager.Register("yeet", new CommandYeet(KittyRole.General, KittyRating.Safe));
manager.Register("ping", new CommandPing(KittyRole.General, KittyRating.Safe));
manager.Register("boop", new CommandBoop(KittyRole.General, KittyRating.Safe));
manager.Register("roll", new CommandRoll(KittyRole.General, KittyRating.Safe));
manager.Register("choose", new CommandChoose(KittyRole.General, KittyRating.Safe));
manager.Register("help", new CommandHelp(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"info", "about"}, new CommandInfo(KittyRole.General, KittyRating.Safe));
manager.Register("vote", new CommandPollVote(KittyRole.General, KittyRating.Safe));
manager.Register("results", new CommandPollResults(KittyRole.General, KittyRating.Safe));
manager.Register("showpoll", new CommandPollShow(KittyRole.General, KittyRating.Safe));
manager.Register("wolfram", new CommandWolfram(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"c++", "g++", "cplus",}, new CommandColiru(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"java", "jdoodle" }, new CommandJDoodle(KittyRole.General, KittyRating.Safe));
manager.Register("beans", new CommandBeansShow(KittyRole.General, KittyRating.Safe));
manager.Register("role", new CommandRole(KittyRole.General, KittyRating.Safe));
manager.Register("bet", new CommandBetBeans(KittyRole.General, KittyRating.Safe));
manager.Register("map", new CommandMap(KittyRole.General, KittyRating.Safe));
manager.Register("rpstart", new CommandRPStart(KittyRole.General, KittyRating.Safe));
manager.Register("rpend", new CommandRPEnd(KittyRole.General, KittyRating.Safe));
manager.Register(new String[] {"tony", "stark", "dontfeelgood", "dontfeelsogood"}, new CommandStark(KittyRole.General, KittyRating.Safe));
manager.Register("blur", new CommandBlurry(KittyRole.General, KittyRating.Safe));
manager.Register(new String [] {"eightball", "8ball"}, new CommandEightBall(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("work"), new CommandDoWork(KittyRole.Dev, KittyRating.Safe));
manager.Register(LocCommands.Stub("shutdown"), new CommandShutdown(KittyRole.Dev, KittyRating.Safe));
manager.Register(LocCommands.Stub("stats"), new CommandStats(KittyRole.Dev, KittyRating.Safe));
manager.Register(LocCommands.Stub("invite"), new CommandInvite(KittyRole.Dev, KittyRating.Safe));
manager.Register(LocCommands.Stub("buildHelp"), new CommandHelpBuilder(KittyRole.Dev, KittyRating.Safe));
manager.Register(LocCommands.Stub("tweet"), new CommandTweet(KittyRole.Dev, KittyRating.Safe));
manager.Register(LocCommands.Stub("rating"), new CommandRating(KittyRole.Admin, KittyRating.Safe));
manager.Register(LocCommands.Stub("indicator"), new CommandChangeIndicator(KittyRole.Admin, KittyRating.Safe));
manager.Register(LocCommands.Stub("allowedguildrole"), new CommandAllowedGuildRole(KittyRole.Admin, KittyRating.Safe));
manager.Register(LocCommands.Stub("poll"), new CommandPollManage(KittyRole.Mod, KittyRating.Safe));
manager.Register(LocCommands.Stub("givebeans"), new CommandGiveBeans(KittyRole.Mod, KittyRating.Safe));
manager.Register(LocCommands.Stub("rpg"), new CommandRPG(KittyRole.Mod, KittyRating.Safe));
manager.Register(LocCommands.Stub("addguildrole"), new CommandAddGuildRole(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("teey"), new CommandTeey(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("perish, thenperish"), new CommandPerish(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("yeet"), new CommandYeet(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("ping"), new CommandPing(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("boop"), new CommandBoop(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("roll"), new CommandRoll(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("choose"), new CommandChoose(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("help"), new CommandHelp(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("info, about"), new CommandInfo(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("vote"), new CommandPollVote(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("results"), new CommandPollResults(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("showpoll"), new CommandPollShow(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("wolfram"), new CommandWolfram(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("c++, g++, cplus, cpp"), new CommandColiru(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("java, jdoodle"), new CommandJDoodle(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("beans"), new CommandBeansShow(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("role"), new CommandRole(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("bet"), new CommandBetBeans(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("map"), new CommandMap(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("rpstart"), new CommandRPStart(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("rpend"), new CommandRPEnd(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("tony, stark, dontfeelgood, dontfeelsogood"), new CommandStark(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("blur"), new CommandBlurry(KittyRole.General, KittyRating.Safe));
manager.Register(LocCommands.Stub("eightball, 8ball"), new CommandEightBall(KittyRole.General, KittyRating.Safe));
return manager;
}
// NOTE(wisp): Default database manager construction. It can be constructed
// Default database manager construction. It can be constructed
// in different ways, and so we construct it outside of the constructor for
// the factory since it doesn't have to be present / can be elsewhere.
// Effectively we cache the database here.
+5 -2
View File
@@ -2,6 +2,7 @@ package dataStructures;
import java.util.ArrayList;
import core.DatabaseTrackedObject;
import utils.AdminControl;
// Context for a given guild for kittybot. Primarily designed to hold guild-specific settings.
public class KittyGuild extends DatabaseTrackedObject
@@ -12,15 +13,17 @@ public class KittyGuild extends DatabaseTrackedObject
public boolean polling;
public String poll;
public ArrayList<String> hasVoted = new ArrayList<String>();
public ArrayList<String> allowedRole = new ArrayList<String>();
public ArrayList <KittyPoll> choices = new ArrayList<KittyPoll>();
public AdminControl control;
private String commandIndicator;
// Default content for a guild
public KittyGuild(String uniqueID)
public KittyGuild(String uniqueID, AdminControl adminControl)
{
super(uniqueID);
control = adminControl;
this.uniqueID = uniqueID;
this.contentRating = KittyRating.Safe;
this.polling = false;
+14
View File
@@ -0,0 +1,14 @@
package dataStructures;
// A generic class designed to hold two types, as a first and second variable.
public class Pair<T1, T2>
{
public T1 First;
public T2 Second;
public Pair(T1 first, T2 second)
{
this.First = first;
this.Second = second;
}
}
+193
View File
@@ -0,0 +1,193 @@
package dataStructures;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
// This is a class designed to parse ini inspired key-value pairs that are sectioned off.
// The difference here is that this is more permissive than an ini file, and only accepts
// a single split character, not the traditional set an ini does. All of the following are valid:
//
// [ExampleSection]
// Key=Value
// Valid Ridiculous Key&\n\t_.:;foo = \tValid Ridiculous Value*&%$()^.[]{}@
// EmptyValue=
//
// Note that the sections are NOT designed to allow for duplicate keys across them.
// This is a restriction of the structure, but can be changed later potentially.
// The only value not allowed in a key or value is the KeyValueSplit.
public class TaggedPairStore
{
// Variables
public final char SectionStart = '[';
public final char SectionEnd = ']';
public final char PairLineSeparator = '\n';
public final String PairSplit = "=";
// [Key: SectionName, [Key: KeyString, Value: ValueString]]
private HashMap<String, HashMap<String, String>> taggedPairs;
// [Key: KeyString, Value: ValueString]]
private HashMap<String, String> allPairs;
// String constructor that parses the input string into the object
public TaggedPairStore(String input)
{
taggedPairs = new HashMap<String, HashMap<String, String>>();
allPairs = new HashMap<String, String>();
Parse(input);
}
// Calls back on each item in the entire structure. Provides section, then a pair of the keyString and valueString.
@SuppressWarnings({"rawtypes", "unchecked"})
public void ForEach(BiConsumer<? super String, Pair<? super String, ? super String>> action)
{
Iterator it = taggedPairs.entrySet().iterator();
while (it.hasNext())
{
Map.Entry pair = (Map.Entry)it.next();
Iterator internal = ((HashMap<String, String>)pair.getValue()).entrySet().iterator();
while(internal.hasNext())
{
Map.Entry internalPair = (Map.Entry)internal.next();
action.accept((String)pair.getKey(), new Pair<String, String>((String)internalPair.getKey(), (String)internalPair.getValue()));
}
}
}
// Calls back each item in the structure but does not priv
@SuppressWarnings({"rawtypes"})
public void ForEach(Consumer<Pair<? super String, ? super String>> action)
{
Iterator it = allPairs.entrySet().iterator();
while (it.hasNext())
{
Map.Entry pair = (Map.Entry)it.next();
action.accept(new Pair<String, String>((String)pair.getKey(), (String)pair.getValue()));
}
}
// Parses the internal hashmap as a string then reutrns it, featuring sections.
// Iterates over all key/value pairs in the section and print them. Does not print
// the value of a given key if it is the same as the key.
@SuppressWarnings({"rawtypes", "unchecked"})
public String toString()
{
String out = "";
// Iterates over the sections and
Iterator it = taggedPairs.entrySet().iterator();
while (it.hasNext())
{
Map.Entry pair = (Map.Entry)it.next();
out += ("" + SectionStart + pair.getKey() + SectionEnd + PairLineSeparator);
Iterator internal = ((HashMap<String, String>)pair.getValue()).entrySet().iterator();
while(internal.hasNext())
{
Map.Entry internalPair = (Map.Entry)internal.next();
String key = (String)internalPair.getKey();
String value = (String)internalPair.getValue();
if(key == value)
out += (key + PairSplit) + PairLineSeparator;
else
out += (key + PairSplit + value) + PairLineSeparator;
}
out += PairLineSeparator;
}
return out;
}
// Parses out the string passed in into the sectionkeyValue HashMap.
// Any character used in a split call is escaped just in case on
// account of some characters having specific regex meanings.
private void Parse(String input)
{
if(input == null)
return;
String[] sections = input.split("\\" + SectionStart);
for(int sec = 0; sec < sections.length; ++sec)
{
// Gather information about the contents of the section, and the header.
String section = sections[sec];
if(section.length() < 2)
continue;
int pos = section.indexOf(SectionEnd);
if(pos == -1)
continue;
// Parse section name. If it already exists, don't bother making it.
String sectionName = section.substring(0, pos);
AddSection(sectionName);
// Parse out the pairs within the section, split them all out.
String unparsedPairs = section.substring(pos + 1);
String[] pairs = unparsedPairs.split("\\" + PairLineSeparator);
// Parse out valid key-value pairs, and store them in the specified section.
// At this point, we can be guarenteed that sectionName is in the Hashmap.
for(int pair = 0; pair < pairs.length; ++pair)
{
String line = pairs[pair];
int splitPos = line.indexOf(PairSplit);
if(splitPos < 0)
continue;
String key = line.substring(0, splitPos);
String value = line.substring(splitPos + PairSplit.length());
taggedPairs.get(sectionName).putIfAbsent(key, value);
allPairs.putIfAbsent(key, value);
}
}
}
// Dumps out a string array
@SuppressWarnings("unused")
private void Dump(String[] toPrint)
{
System.out.println("Length: " + toPrint.length);
for(int i = 0; i < toPrint.length; ++i)
System.out.println(toPrint[i]);
}
// Adds a KeyValue pair to the specified section if it's not already there.
// Also creates the section if it's not already present.
public void AddKeyValue(String sectionName, String key, String value)
{
AddSection(sectionName);
taggedPairs.get(sectionName).putIfAbsent(key, value);
allPairs.putIfAbsent(key, value);
}
// Adds a given section to the hashmap if it's not already present
public void AddSection(String sectionName)
{
taggedPairs.putIfAbsent(sectionName, new HashMap<String, String>());
}
// Returns a HashMap of Keys to Values for a given section
@SuppressWarnings("unchecked")
public HashMap<String, String> GetSection(String sectionName)
{
return (HashMap<String, String>) taggedPairs.get(sectionName).clone();
}
// Look up a global key
public String GetKey(String key)
{
if(allPairs.containsKey(key))
return allPairs.get(key);
return null;
}
}
+9 -13
View File
@@ -1,7 +1,6 @@
package main;
import javax.security.auth.login.LoginException;
import core.*;
import dataStructures.KittyChannel;
import dataStructures.KittyGuild;
@@ -14,17 +13,19 @@ import net.dv8tion.jda.core.entities.*;
import net.dv8tion.jda.core.events.message.guild.*;
import net.dv8tion.jda.core.hooks.ListenerAdapter;
import offline.*;
import core.Localizer;
import utils.GlobalLog;
import net.dv8tion.jda.core.*;
// NOTE(wisp): http://www.slf4j.org/ - this JDA logging tool has been disabled by specifying NOP implementation.
// NOTE(wisp): Application entry point!
// http://www.slf4j.org/ - this JDA logging tool has been disabled by specifying NOP implementation.
// This is the application entry point, and bot startup location!
@SuppressWarnings("unused")
public class Main extends ListenerAdapter
{
// Variables and stuff
private static JDA kitty;
private static CommandManager commandManager;
private static CommandEnabler commandEnabler;
private static DatabaseManager databaseManager;
private static Stats stats;
private static RPManager rpManager;
@@ -32,17 +33,13 @@ public class Main extends ListenerAdapter
// Main test location
public static void main(String[] args) throws InterruptedException, LoginException, Exception
{
// Facotry startup.
// Factory startup. The ordering is intentional.
databaseManager = ObjectBuilderFactory.ConstructDatabaseManager();
commandManager = ObjectBuilderFactory.ConstructCommandManager();
commandEnabler = ObjectBuilderFactory.ConstructCommandEnabler();
commandManager = ObjectBuilderFactory.ConstructCommandManager(commandEnabler);
rpManager = ObjectBuilderFactory.ConstructRPManager();
stats = ObjectBuilderFactory.ConstructStats(commandManager);
// Localizer startup - Potentially integrate with the factory.
Localizer.UpdateLocFromDisk();
Localizer.ScrapeAll();
Localizer.SaveLocToDisk();
// Bot startup
kitty = new JDABuilder(AccountType.BOT).setToken(Ref.TestToken).buildBlocking();
kitty.getPresence().setGame(Game.playing("with a new build"));
@@ -56,8 +53,6 @@ public class Main extends ListenerAdapter
if(!PreProcessSetup(event))
return;
GlobalLog.Log("Parseable message recieved!");
// Factory objects
KittyUser user = ObjectBuilderFactory.ExtractUser(event);
KittyGuild guild = ObjectBuilderFactory.ExtractGuild(event);
@@ -140,6 +135,7 @@ public class Main extends ListenerAdapter
{
databaseManager.Upkeep();
RPManager.Upkeep(kitty);
return true;
}
}
-1
View File
@@ -1,7 +1,6 @@
package network;
import com.google.gson.Gson;
import core.*;
import dataStructures.GenericImage;
import offline.*;
import utils.*;
+2 -2
View File
@@ -1,8 +1,8 @@
package network;
import com.google.gson.Gson;
import core.*;
import dataStructures.GenericImage;
import offline.Ref;
import utils.*;
/**
@@ -25,7 +25,7 @@ public class NetworkE621
private static final Gson jsonParser_ = new Gson();
private static final String API_ROOT = "https://e621.net/post/index.json?";
private static int maxSearchResults_ = 10;
private static String[] blacklist = {"theallseeingeye","scat","diaper","cub"};
private static String[] blacklist = Ref.e621Blacklist;
private class E621ResponseObject
{
// public varaibles matching the case and the type we want for JSON.
+29
View File
@@ -0,0 +1,29 @@
package utils;
import net.dv8tion.jda.core.entities.Guild;
import net.dv8tion.jda.core.managers.GuildController;
public class AdminControl
{
private GuildController guildCon;
private Guild guild;
public AdminControl(Guild guild)
{
this.guild = guild;
guildCon = guild.getController();
}
public boolean addRole(String memberID, String roleName)
{
try
{
guildCon.addRolesToMember(guild.getMemberById(memberID), guild.getRolesByName(roleName, true)).complete();
}
catch(Exception e)
{
return false;
}
return true;
}
}
+9 -2
View File
@@ -1,5 +1,6 @@
package utils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -11,6 +12,7 @@ import java.util.stream.Stream;
public class FileUtils
{
// Reads all lines from a file as a string
public static String ReadContent(File file) { return ReadContent(file.toPath()); }
public static String ReadContent(Path filePath)
{
StringBuilder contentBuilder = new StringBuilder();
@@ -24,7 +26,7 @@ public class FileUtils
}
catch (IOException e)
{
e.printStackTrace();
GlobalLog.Error(LogFilter.Util, e.getMessage());
}
return contentBuilder.toString();
@@ -35,13 +37,18 @@ public class FileUtils
{
ArrayList<Path> items = new ArrayList<Path>();
File tmpDir = new File(startingDir);
if(!tmpDir.exists())
return new ArrayList<Path>();
try
{
Files.find(Paths.get(startingDir), 999, (path, attributes) -> attributes.isRegularFile()).forEach(items::add);
}
catch (IOException e)
{
e.printStackTrace();
GlobalLog.Error(LogFilter.Util, e.getMessage());
}
return items;
+308
View File
@@ -0,0 +1,308 @@
package utils;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Calendar;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
// A class that overlays an image based on a series of frames that will be converted into a gif.
// The class targets a pixel with max Green value and no red or blue that's fully opaque (0, 255, 0)
// and then places the center of the overlay image on the specified location. Must be 32-bit PNGs with RGBA format.
public class ImageOverlayBuilder
{
// Lets get some trash established for the yeet
public static final boolean VERBOSE = false;
public static final int NONE = -1;
public static final int PIXEL_BYTE_LENGTH = 4; // 32-bit PNG, RGBA
public static final int DEFAULT_FPS = 18;
public static final String FRAME_FILE_EXTENSION = ".png";
public static final String LOG_HEADER = "[Overlay] ";
// Logging intermediate functions
private static void Verbose(String str) { if(VERBOSE) Log("[Verbose] " + str); }
private static void Log(String str) { GlobalLog.Log(LogFilter.Util, LOG_HEADER + str); }
private static void Warn(String str) { GlobalLog.Warn(LogFilter.Util, LOG_HEADER + str); }
private static void Error(String str) { GlobalLog.Error(LogFilter.Util, LOG_HEADER + str); }
// Local variables
private final String basePath; // The folder location of the files
private final String baseName; // This is the consistent part of the filename of each frame, for example "image "
private final int frameCount; // The number of frames in the image
private final int fps; // This doesn't quite line up, multiples of 10 are as specific as it gets.
// Required minimal constructor
public ImageOverlayBuilder(String basePath, String baseName, int frameCount)
{
this(basePath, baseName, frameCount, DEFAULT_FPS);
}
// Constructor with framerate specification
public ImageOverlayBuilder(String basePath, String baseName, int frameCount, int fps)
{
this.basePath = basePath;
this.baseName = baseName;
this.frameCount = frameCount;
this.fps = fps;
}
// Performs a per-frame overlay, catches IO errors as a note.)
public void Overlay(BufferedImage overlay, String outpath)
{
try
{
long start = Calendar.getInstance().getTimeInMillis();
ProcessOverlay(overlay, outpath);
long end = Calendar.getInstance().getTimeInMillis();
Log("Took " + (end - start) + "ms");
}
catch (IOException e)
{
Error(e.getMessage());
}
}
// Processing the image frames to construct a gif. Relies on (0,255,0) pixel for image center specification.
private void ProcessOverlay(BufferedImage overlay, String outfileName) throws IOException
{
BufferedImage[] frames = new BufferedImage[frameCount];
for(int i = 0; i < frameCount; ++i)
{
BufferedImage out = CombineImages(basePath + baseName + i + FRAME_FILE_EXTENSION , overlay);
if(out != null)
frames[i] = out;
else
Error("There was an issue with overlaying frame " + i);
}
Verbose("Processed " + frameCount + " frames, writing...");
ImageOutputStream output = new FileImageOutputStream(new File(outfileName));
GifSequenceWriter writer = new GifSequenceWriter(output, frames[0].getType(), fps, true);
for(int i = 0; i < frameCount; ++i)
writer.writeToSequence(frames[i]);
writer.close();
output.close();
}
// Performs an overlay at a pixel location. We're making some assumptions here, mostly that there is going
// to be an RGBA-32-bit encoded PNG image read in for our parsing purposes. We then look for a 0, 255, 0
// green pixel on the base frame to apply the overlay buffer to, centered.
private BufferedImage CombineImages(String pathBase, BufferedImage overlay) throws IOException
{
// Acquire images
BufferedImage imageBase = null;
BufferedImage imageOverlay = overlay;
imageBase = ImageIO.read(new File(pathBase));
// Grab data we know won't change. As a side note, I discovered you can get specific pixels a bit
// differently later, but it was too late, I wrote the pixel specific code already.
final byte[] pixelsBase = ((DataBufferByte) imageBase.getRaster().getDataBuffer()).getData();
final int baseWidth = imageBase.getWidth();
final int overlayWidth = imageOverlay.getWidth();
final int baseHeight = imageBase.getHeight();
final int overlayHeight = imageOverlay.getHeight();
// Warn about odd sizing when applicable
if(overlayHeight > baseHeight || overlayWidth > baseWidth)
Warn("Size mismatch, overlay is larger. May function, but not supported.");
// Skim base picture for solid green pixel to use as target center
int targetX = NONE;
int targetY = NONE;
for(int i = 0; i < pixelsBase.length; i += PIXEL_BYTE_LENGTH)
{
final int x = ((i / PIXEL_BYTE_LENGTH) % baseWidth);
final int y = ((i / PIXEL_BYTE_LENGTH) / baseWidth);
final int r = (pixelsBase[i]);
final int g = (pixelsBase[i + 1]);
final int b = (pixelsBase[i + 2]);
//final int a = (pixelsBase[i + 3]);
// Max contrast in 32-bit PNG for green is no red no blue, max green (ratio), max alpha (implied 0 here)
if(r == -1 && g == 0 && b == -1)
{
//Log("Found target pixel at x: " + x + " y: " + y);
targetX = x;
targetY = y;
break;
}
}
// Skip overlay if we have no target location
if(targetX == NONE || targetY == NONE)
{
Verbose("No overlay required for this frame.");
return imageBase;
}
// Re-create a new byte array and establish overlay bounds
final int left = targetX - overlayWidth / 2;
final int right = targetX + overlayWidth / 2;
final int top = targetY - overlayHeight / 2;
final int bottom = targetY + overlayHeight / 2;
// Apply overlay when appropriate
for(int i = 0; i < pixelsBase.length; i += PIXEL_BYTE_LENGTH)
{
final int x = ((i / PIXEL_BYTE_LENGTH) % baseWidth);
final int y = ((i / PIXEL_BYTE_LENGTH) / baseWidth);
// If we're within the bounds of where the overlay picture should go...
// ...then get bytes and manually apply alpha overlay.
if(x > left && x < right && y > top && y < bottom)
{
final int overlayX = x - left;
final int overlayY = y - top;
// Skip out of bounds pixels
if(overlayX < 0 || overlayY < 0 || overlayX >= baseWidth || overlayY >= baseHeight)
continue;
byte[] overlayBytes = ByteBuffer.allocate(4).putInt(imageOverlay.getRGB(overlayX, overlayY)).array();
byte[] baseBytes = ByteBuffer.allocate(4).putInt(imageBase.getRGB(x, y)).array();
float overlayT = (overlayBytes[0] & 0xFF) / 255.0f;
// Java has a restriction where we can't actually manupulate unsigned bytes apparently(?) so
// what this does is convert the specific bytes to ints in order to manipuate them, then takes the
// byte buffer and parses the far right byte out of the int after manipulating the value.
byte[] output = new byte[4];
output[0] = (byte)0b11111111;
output[1] = ByteBuffer.allocate(4).putInt((int) ((baseBytes[1] & 0xFF) - (overlayT * ((baseBytes[1] & 0xFF) - (overlayBytes[1] & 0xFF))))).array()[3];
output[2] = ByteBuffer.allocate(4).putInt((int) ((baseBytes[2] & 0xFF) - (overlayT * ((baseBytes[2] & 0xFF) - (overlayBytes[2] & 0xFF))))).array()[3];
output[3] = ByteBuffer.allocate(4).putInt((int) ((baseBytes[3] & 0xFF) - (overlayT * ((baseBytes[3] & 0xFF) - (overlayBytes[3] & 0xFF))))).array()[3];
// Re-write bytes as overlay
imageBase.setRGB(x, y, ByteBuffer.wrap(output).getInt());
}
}
// Give the frame back
return imageBase;
}
// GifSequenceWriter.java
//
// Created by Elliot Kroo on 2009-04-25,
// (Small modifications by wisp in 2018)
//
// This work is licensed under the Creative Commons Attribution 3.0 Unported License.
// To view a copy of this license visit http://creativecommons.org/licenses/by/3.0/
// NOTE: ^ This is Elliot's original license
private static class GifSequenceWriter
{
protected ImageWriter gifWriter;
protected ImageWriteParam imageWriteParam;
protected IIOMetadata imageMetaData;
// NOTE: This is limited to things that divide into 1000 cleanly as multiples of 10.
public GifSequenceWriter(ImageOutputStream outputStream, int imageType, int framesPerSecond, boolean isLooping) throws IIOException, IOException
{
gifWriter = getWriter();
imageWriteParam = gifWriter.getDefaultWriteParam();
ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType);
imageMetaData = gifWriter.getDefaultImageMetadata(imageTypeSpecifier, imageWriteParam);
String metaFormatName = imageMetaData.getNativeMetadataFormatName();
IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName);
IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("delayTime", "" + (int) ((1000.0 / framesPerSecond) / 10.0)); // THis line isn't super well honored
graphicsControlExtensionNode.setAttribute("transparentColorIndex","0");
IIOMetadataNode appEntensionsNode = getNode( root, "ApplicationExtensions");
IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");
child.setAttribute("applicationID", "NETSCAPE");
child.setAttribute("authenticationCode", "2.0");
int loopValue = isLooping ? 0 : 1;
child.setUserObject(new byte[]{ 0x1, (byte) (loopValue & 0xFF), (byte) ((loopValue >> 8) & 0xFF)});
appEntensionsNode.appendChild(child);
imageMetaData.setFromTree(metaFormatName, root);
gifWriter.setOutput(outputStream);
gifWriter.prepareWriteSequence(null);
}
public void writeToSequence(RenderedImage img) throws IOException
{
gifWriter.writeToSequence(new IIOImage(img, null, imageMetaData), imageWriteParam);
}
/**
* Close this GifSequenceWriter object. This does not close the underlying
* stream, just finishes off the GIF.
*/
public void close() throws IOException
{
gifWriter.endWriteSequence();
}
/**
* Returns the first available GIF ImageWriter using
* ImageIO.getImageWritersBySuffix("gif").
*
* @return a GIF ImageWriter object
* @throws IIOException if no GIF image writers are returned
*/
private static ImageWriter getWriter() throws IIOException
{
Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif");
if(!iter.hasNext())
throw new IIOException("No GIF Image Writers Exist");
else
return iter.next();
}
/**
* Returns an existing child node, or creates and returns a new child node (if
* the requested node does not exist).
*
* @param rootNode the <tt>IIOMetadataNode</tt> to search for the child node.
* @param nodeName the name of the child node.
*
* @return the child node, if found or a new node created with the given name.
*/
private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName)
{
int nodeCount = rootNode.getLength();
for (int i = 0; i < nodeCount; i++)
{
if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) == 0)
return((IIOMetadataNode) rootNode.item(i));
}
IIOMetadataNode node = new IIOMetadataNode(nodeName);
rootNode.appendChild(node);
return(node);
}
}
}
+2 -1
View File
@@ -2,7 +2,8 @@ package utils;
public enum LogFilter
{
Debug(0), Command(1), Core(2), Database(4), Response(8), Network(16), Strings(32); //8, 16, 32... (flags, so we can | together later)
// Assign numbers as flags, so we can | ('or') them together as necessary
Debug(0), Command(1), Core(2), Util(4), Database(8), Response(16), Network(32), Strings(64);
private final int value;
private LogFilter(int value)