Implemented Rock Paper Scissors module in Kotlin.

This commit is contained in:
Erik C. Thauvin 2020-03-23 05:53:11 -07:00
parent d8da21b0ef
commit 92576f9496
26 changed files with 501 additions and 248 deletions

View file

@ -1,4 +1,4 @@
Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View file

@ -14,13 +14,13 @@ import java.time.*;
public final class ReleaseInfo { public final class ReleaseInfo {
public static final String PROJECT = "mobibot"; public static final String PROJECT = "mobibot";
public static final LocalDateTime BUILDDATE = public static final LocalDateTime BUILDDATE =
LocalDateTime.ofInstant(Instant.ofEpochMilli(1584572412391L), ZoneId.systemDefault()); LocalDateTime.ofInstant(Instant.ofEpochMilli(1584966079848L), ZoneId.systemDefault());
public static final int MAJOR = 0; public static final int MAJOR = 0;
public static final int MINOR = 7; public static final int MINOR = 7;
public static final int PATCH = 3; public static final int PATCH = 3;
public static final String PRERELEASE = "beta"; public static final String PRERELEASE = "beta";
public static final String BUILDMETA = "581"; public static final String BUILDMETA = "682";
public static final String VERSION = "0.7.3-beta+581"; public static final String VERSION = "0.7.3-beta+682";
/** /**
* Disables the default constructor. * Disables the default constructor.

View file

@ -1,7 +1,7 @@
/* /*
* FeedReader.java * FeedReader.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -88,11 +88,14 @@ class FeedReader implements Runnable {
SyndEntry item; SyndEntry item;
final List<SyndEntry> items = feed.getEntries(); final List<SyndEntry> items = feed.getEntries();
if (items.isEmpty()) {
for (int i = 0; (i < items.size()) && (i < MAX_ITEMS); i++) { bot.send(sender, "There is currently nothing to view. Why don't you post something?");
item = items.get(i); } else {
bot.send(sender, item.getTitle()); for (int i = 0; (i < items.size()) && (i < MAX_ITEMS); i++) {
bot.send(sender, TAB_INDENT + Utils.green(item.getLink())); item = items.get(i);
bot.send(sender, item.getTitle());
bot.send(sender, TAB_INDENT + Utils.green(item.getLink()));
}
} }
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
bot.getLogger().debug("Invalid feed URL.", e); bot.getLogger().debug("Invalid feed URL.", e);

View file

@ -1,7 +1,7 @@
/* /*
* Mobibot.java * Mobibot.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -47,6 +47,7 @@ import net.thauvin.erik.mobibot.modules.Joke;
import net.thauvin.erik.mobibot.modules.Lookup; import net.thauvin.erik.mobibot.modules.Lookup;
import net.thauvin.erik.mobibot.modules.ModuleException; import net.thauvin.erik.mobibot.modules.ModuleException;
import net.thauvin.erik.mobibot.modules.Ping; import net.thauvin.erik.mobibot.modules.Ping;
import net.thauvin.erik.mobibot.modules.RockPaperScissors;
import net.thauvin.erik.mobibot.modules.StockQuote; import net.thauvin.erik.mobibot.modules.StockQuote;
import net.thauvin.erik.mobibot.modules.Twitter; import net.thauvin.erik.mobibot.modules.Twitter;
import net.thauvin.erik.mobibot.modules.War; import net.thauvin.erik.mobibot.modules.War;
@ -62,7 +63,6 @@ import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option; import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -93,6 +93,9 @@ import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import static net.thauvin.erik.mobibot.Utils.bold;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/** /**
* Implements the #mobitopia bot. * Implements the #mobitopia bot.
* *
@ -101,7 +104,8 @@ import java.util.StringTokenizer;
* @since 1.0 * @since 1.0
*/ */
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@Version(properties = "version.properties", className = "ReleaseInfo") @Version(properties = "version.properties",
className = "ReleaseInfo")
public class Mobibot extends PircBot { public class Mobibot extends PircBot {
// The default port. // The default port.
@ -111,9 +115,10 @@ public class Mobibot extends PircBot {
private static final String DEFAULT_SERVER = "irc.freenode.net"; private static final String DEFAULT_SERVER = "irc.freenode.net";
// The info strings. // The info strings.
private static final String[] INFO_STRS = @SuppressWarnings("indentation")
{ReleaseInfo.PROJECT + " v" + ReleaseInfo.VERSION + " by Erik C. Thauvin (erik@thauvin.net)", private static final String[] INFO_STRS = {
"https://www.mobitopia.org/mobibot/"}; ReleaseInfo.PROJECT + " v" + ReleaseInfo.VERSION + " by Erik C. Thauvin (erik@thauvin.net)",
"https://www.mobitopia.org/mobibot/" };
// The link match string. // The link match string.
private static final String LINK_MATCH = "^[hH][tT][tT][pP](|[sS])://.*"; private static final String LINK_MATCH = "^[hH][tT][tT][pP](|[sS])://.*";
@ -137,14 +142,15 @@ public class Mobibot extends PircBot {
private static final String TAGS_MARKER = "tags:"; private static final String TAGS_MARKER = "tags:";
/* The version strings.*/ /* The version strings.*/
@SuppressWarnings("indentation")
private static final String[] VERSION_STRS = private static final String[] VERSION_STRS =
{"Version: " + ReleaseInfo.VERSION + " (" + Utils.isoLocalDate(ReleaseInfo.BUILDDATE) + ')', { "Version: " + ReleaseInfo.VERSION + " (" + Utils.isoLocalDate(ReleaseInfo.BUILDDATE) + ')',
"Platform: " + System.getProperty("os.name") + " (" + System.getProperty("os.version") + ", " + System "Platform: " + System.getProperty("os.name") + " (" + System.getProperty("os.version") + ", "
.getProperty("os.arch") + ", " + System.getProperty("user.country") + ')', + System.getProperty("os.arch") + ", " + System.getProperty("user.country") + ')',
"Runtime: " + System.getProperty("java.runtime.name") + " (build " + System "Runtime: " + System.getProperty("java.runtime.name") + " (build " + System.getProperty(
.getProperty("java.runtime.version") + ')', "java.runtime.version") + ')',
"VM: " + System.getProperty("java.vm.name") + " (build " + System.getProperty("java.vm.version") + ", " "VM: " + System.getProperty("java.vm.name") + " (build " + System.getProperty("java.vm.version") + ", "
+ System.getProperty("java.vm.info") + ')'}; + System.getProperty("java.vm.info") + ')' };
// The logger. // The logger.
private static final Logger logger = LogManager.getLogger(Mobibot.class); private static final Logger logger = LogManager.getLogger(Mobibot.class);
// The commands list. // The commands list.
@ -272,6 +278,7 @@ public class Mobibot extends PircBot {
MODULES.add(new Joke()); MODULES.add(new Joke());
MODULES.add(new Lookup()); MODULES.add(new Lookup());
MODULES.add(new Ping()); MODULES.add(new Ping());
MODULES.add(new RockPaperScissors());
MODULES.add(new StockQuote()); MODULES.add(new StockQuote());
twitterModule = new Twitter(); twitterModule = new Twitter();
@ -308,21 +315,29 @@ public class Mobibot extends PircBot {
* @param args The command line arguments. * @param args The command line arguments.
*/ */
@SuppressFBWarnings( @SuppressFBWarnings(
{ {
"INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE", "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE",
"DM_DEFAULT_ENCODING", "DM_DEFAULT_ENCODING",
"IOI_USE_OF_FILE_STREAM_CONSTRUCTORS" "IOI_USE_OF_FILE_STREAM_CONSTRUCTORS"
}) })
@SuppressWarnings({"PMD.SystemPrintln", "PMD.AvoidFileStream", "PMD.CloseResource"}) @SuppressWarnings({ "PMD.SystemPrintln", "PMD.AvoidFileStream", "PMD.CloseResource" })
public static void main(final String[] args) { public static void main(final String[] args) {
// Setup the command line options // Setup the command line options
final Options options = new Options() final Options options = new Options()
.addOption(Commands.HELP_ARG.substring(0, 1), Commands.HELP_ARG, false, "print this help message") .addOption(Commands.HELP_ARG.substring(0, 1),
.addOption(Commands.DEBUG_ARG.substring(0, 1), Commands.DEBUG_ARG, false, Commands.HELP_ARG,
"print debug & logging data directly to the console") false,
.addOption(Option.builder(Commands.PROPS_ARG.substring(0, 1)).hasArg().argName("file") "print this help message")
.desc("use " + "alternate properties file").longOpt(Commands.PROPS_ARG).build()) .addOption(Commands.DEBUG_ARG.substring(0, 1), Commands.DEBUG_ARG, false,
.addOption(Commands.VERSION_ARG.substring(0, 1), Commands.VERSION_ARG, false, "print version info"); "print debug & logging data directly to the console")
.addOption(Option.builder(Commands.PROPS_ARG.substring(0, 1)).hasArg()
.argName("file")
.desc("use " + "alternate properties file")
.longOpt(Commands.PROPS_ARG).build())
.addOption(Commands.VERSION_ARG.substring(0, 1),
Commands.VERSION_ARG,
false,
"print version info");
// Parse the command line // Parse the command line
final CommandLineParser parser = new DefaultParser(); final CommandLineParser parser = new DefaultParser();
@ -347,7 +362,7 @@ public class Mobibot extends PircBot {
final Properties p = new Properties(); final Properties p = new Properties();
try (final InputStream fis = Files.newInputStream( try (final InputStream fis = Files.newInputStream(
Paths.get(line.getOptionValue(Commands.PROPS_ARG.charAt(0), "./mobibot.properties")))) { Paths.get(line.getOptionValue(Commands.PROPS_ARG.charAt(0), "./mobibot.properties")))) {
// Load the properties files // Load the properties files
p.load(fis); p.load(fis);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
@ -368,7 +383,7 @@ public class Mobibot extends PircBot {
if (!line.hasOption(Commands.DEBUG_ARG.charAt(0))) { if (!line.hasOption(Commands.DEBUG_ARG.charAt(0))) {
try { try {
final PrintStream stdout = new PrintStream( final PrintStream stdout = new PrintStream(
new FileOutputStream(logsDir + channel.substring(1) + '.' + Utils.today() + ".log", true)); new FileOutputStream(logsDir + channel.substring(1) + '.' + Utils.today() + ".log", true));
System.setOut(stdout); System.setOut(stdout);
} catch (IOException e) { } catch (IOException e) {
System.err.println("Unable to open output (stdout) log file."); System.err.println("Unable to open output (stdout) log file.");
@ -423,7 +438,7 @@ public class Mobibot extends PircBot {
* @param action The action. * @param action The action.
*/ */
private void action(final String channel, final String action) { private void action(final String channel, final String action) {
if (StringUtils.isNotBlank(channel) && StringUtils.isNotBlank(action)) { if (isNotBlank(channel) && isNotBlank(action)) {
sendAction(channel, action); sendAction(channel, action);
} }
} }
@ -431,7 +446,7 @@ public class Mobibot extends PircBot {
/** /**
* Connects to the server and joins the channel. * Connects to the server and joins the channel.
*/ */
@SuppressFBWarnings({"DM_EXIT", "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE"}) @SuppressFBWarnings({ "DM_EXIT", "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE" })
public final void connect() { public final void connect() {
try { try {
connect(ircServer, ircPort); connect(ircServer, ircPort);
@ -447,7 +462,7 @@ public class Mobibot extends PircBot {
if (retries == MAX_RECONNECT) { if (retries == MAX_RECONNECT) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug( logger.debug(
"Unable to reconnect to {} after {} retries.", ircServer, MAX_RECONNECT, ex); "Unable to reconnect to {} after {} retries.", ircServer, MAX_RECONNECT, ex);
} }
e.printStackTrace(System.err); e.printStackTrace(System.err);
@ -470,7 +485,7 @@ public class Mobibot extends PircBot {
* @param sender The nick of the person who sent the private message. * @param sender The nick of the person who sent the private message.
*/ */
private void feedResponse(final String sender) { private void feedResponse(final String sender) {
if (StringUtils.isNotBlank(feedUrl)) { if (isNotBlank(feedUrl)) {
new Thread(new FeedReader(this, sender, feedUrl)).start(); new Thread(new FeedReader(this, sender, feedUrl)).start();
} else { } else {
send(sender, "There is no weblog setup for this channel."); send(sender, "There is no weblog setup for this channel.");
@ -561,7 +576,7 @@ public class Mobibot extends PircBot {
for (final char c : getNick().toCharArray()) { for (final char c : getNick().toCharArray()) {
if (Character.isLetter(c)) { if (Character.isLetter(c)) {
buff.append('[').append(String.valueOf(c).toLowerCase(Constants.LOCALE)).append( buff.append('[').append(String.valueOf(c).toLowerCase(Constants.LOCALE)).append(
String.valueOf(c).toUpperCase(Constants.LOCALE)).append(']'); String.valueOf(c).toUpperCase(Constants.LOCALE)).append(']');
} else { } else {
buff.append(c); buff.append(c);
} }
@ -606,7 +621,7 @@ public class Mobibot extends PircBot {
* @return The indented help string. * @return The indented help string.
*/ */
public String helpIndent(final String help, final boolean isBold) { public String helpIndent(final String help, final boolean isBold) {
return " " + (isBold ? Utils.bold(help) : help); return " " + (isBold ? bold(help) : help);
} }
/** /**
@ -619,21 +634,21 @@ public class Mobibot extends PircBot {
final String lcTopic = topic.toLowerCase(Constants.LOCALE).trim(); final String lcTopic = topic.toLowerCase(Constants.LOCALE).trim();
if (Commands.HELP_POSTING_KEYWORD.equals(lcTopic)) { if (Commands.HELP_POSTING_KEYWORD.equals(lcTopic)) {
send(sender, Utils.bold("Post a URL, by saying it on a line on its own:")); send(sender, bold("Post a URL, by saying it on a line on its own:"));
send(sender, helpIndent("<url> [<title>] [" + TAGS_MARKER + "<+tag> [...]]")); send(sender, helpIndent("<url> [<title>] [" + TAGS_MARKER + "<+tag> [...]]"));
send(sender, "I will reply with a label, for example: " + Utils.bold(Commands.LINK_CMD + '1')); send(sender, "I will reply with a label, for example: " + bold(Commands.LINK_CMD + '1'));
send(sender, "To add a title, use a its label and a pipe:"); send(sender, "To add a title, use a its label and a pipe:");
send(sender, helpIndent(Commands.LINK_CMD + "1:|This is the title")); send(sender, helpIndent(Commands.LINK_CMD + "1:|This is the title"));
send(sender, "To add a comment: "); send(sender, "To add a comment: ");
send(sender, helpIndent(Commands.LINK_CMD + "1:This is a comment")); send(sender, helpIndent(Commands.LINK_CMD + "1:This is a comment"));
send(sender, "I will reply with a label, for example: " + Utils.bold(Commands.LINK_CMD + "1.1")); send(sender, "I will reply with a label, for example: " + bold(Commands.LINK_CMD + "1.1"));
send(sender, "To edit a comment, use its label: "); send(sender, "To edit a comment, use its label: ");
send(sender, helpIndent(Commands.LINK_CMD + "1.1:This is an edited comment")); send(sender, helpIndent(Commands.LINK_CMD + "1.1:This is an edited comment"));
send(sender, "To delete a comment, use its label and a minus sign: "); send(sender, "To delete a comment, use its label and a minus sign: ");
send(sender, helpIndent(Commands.LINK_CMD + "1.1:-")); send(sender, helpIndent(Commands.LINK_CMD + "1.1:-"));
send(sender, "You can also view a posting by saying its label."); send(sender, "You can also view a posting by saying its label.");
} else if (Commands.HELP_TAGS_KEYWORD.equals(lcTopic)) { } else if (Commands.HELP_TAGS_KEYWORD.equals(lcTopic)) {
send(sender, Utils.bold("To categorize or tag a URL, use its label and a T:")); send(sender, bold("To categorize or tag a URL, use its label and a T:"));
send(sender, helpIndent(Commands.LINK_CMD + "1T:<+tag|-tag> [...]")); send(sender, helpIndent(Commands.LINK_CMD + "1T:<+tag|-tag> [...]"));
} else if (Commands.VIEW_CMD.equals(lcTopic)) { } else if (Commands.VIEW_CMD.equals(lcTopic)) {
send(sender, "To list or search the current URL posts:"); send(sender, "To list or search the current URL posts:");
@ -685,7 +700,7 @@ public class Mobibot extends PircBot {
} }
} }
send(sender, Utils.bold("Type a URL on " + ircChannel + " to post it.")); send(sender, bold("Type a URL on " + ircChannel + " to post it."));
send(sender, "For more information on a specific command, type:"); send(sender, "For more information on a specific command, type:");
send(sender, helpIndent(getNick() + ": " + Commands.HELP_CMD + " <command>")); send(sender, helpIndent(getNick() + ": " + Commands.HELP_CMD + " <command>"));
send(sender, "The commands are:"); send(sender, "The commands are:");
@ -743,12 +758,12 @@ public class Mobibot extends PircBot {
*/ */
private void identify() { private void identify() {
// Identify with NickServ // Identify with NickServ
if (StringUtils.isNotBlank(identPwd)) { if (isNotBlank(identPwd)) {
identify(identPwd); identify(identPwd);
} }
// Identify with a specified nick // Identify with a specified nick
if (StringUtils.isNotBlank(identNick) && StringUtils.isNotBlank(identMsg)) { if (isNotBlank(identNick) && isNotBlank(identMsg)) {
sendMessage(identNick, identMsg); sendMessage(identNick, identMsg);
} }
} }
@ -818,7 +833,7 @@ public class Mobibot extends PircBot {
final StringBuilder info = new StringBuilder(29); final StringBuilder info = new StringBuilder(29);
info.append("Uptime: ").append(Utils.uptime(ManagementFactory.getRuntimeMXBean().getUptime())).append( info.append("Uptime: ").append(Utils.uptime(ManagementFactory.getRuntimeMXBean().getUptime())).append(
" [Entries: ").append(entries.size()); " [Entries: ").append(entries.size());
if (tell.isEnabled() && isOp(sender)) { if (tell.isEnabled() && isOp(sender)) {
info.append(", Messages: ").append(tell.size()); info.append(", Messages: ").append(tell.size());
@ -836,7 +851,7 @@ public class Mobibot extends PircBot {
* @return <code>true</code> if the nick should be ignored, <code>false</code> otherwise. * @return <code>true</code> if the nick should be ignored, <code>false</code> otherwise.
*/ */
private boolean isIgnoredNick(final String nick) { private boolean isIgnoredNick(final String nick) {
return StringUtils.isNotBlank(nick) && ignoredNicks.contains(nick.toLowerCase(Constants.LOCALE)); return isNotBlank(nick) && ignoredNicks.contains(nick.toLowerCase(Constants.LOCALE));
} }
/** /**
@ -871,7 +886,7 @@ public class Mobibot extends PircBot {
*/ */
@Override @Override
protected final void onDisconnect() { protected final void onDisconnect() {
if (StringUtils.isNotBlank(weblogUrl)) { if (isNotBlank(weblogUrl)) {
setVersion(weblogUrl); setVersion(weblogUrl);
} }
@ -883,7 +898,8 @@ public class Mobibot extends PircBot {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@SuppressFBWarnings(value = "CC_CYCLOMATIC_COMPLEXITY", justification = "Working on it.") @SuppressFBWarnings(value = "CC_CYCLOMATIC_COMPLEXITY",
justification = "Working on it.")
@Override @Override
protected final void onMessage(final String channel, final String sender, final String login, final String hostname, protected final void onMessage(final String channel, final String sender, final String login, final String hostname,
final String message) { final String message) {
@ -923,7 +939,7 @@ public class Mobibot extends PircBot {
if (data.length == 1) { if (data.length == 1) {
title = data[0].trim(); title = data[0].trim();
} else { } else {
if (StringUtils.isNotBlank(data[0])) { if (isNotBlank(data[0])) {
title = data[0].trim(); title = data[0].trim();
} }
@ -936,7 +952,7 @@ public class Mobibot extends PircBot {
final Document html = Jsoup.connect(link).userAgent("Mozilla").get(); final Document html = Jsoup.connect(link).userAgent("Mozilla").get();
final String htmlTitle = html.title(); final String htmlTitle = html.title();
if (StringUtils.isNotBlank(htmlTitle)) { if (isNotBlank(htmlTitle)) {
title = htmlTitle; title = htmlTitle;
} }
} catch (IOException ignore) { } catch (IOException ignore) {
@ -962,7 +978,7 @@ public class Mobibot extends PircBot {
} }
} else { } else {
final EntryLink entry = entries.get(dupIndex); final EntryLink entry = entries.get(dupIndex);
send(sender, Utils.bold("Duplicate") + " >> " + EntriesUtils.buildLink(dupIndex, entry)); send(sender, bold("Duplicate") + " >> " + EntriesUtils.buildLink(dupIndex, entry));
} }
} }
} else if (message.matches(getNickPattern() + ":.*")) { // mobibot: <command> } else if (message.matches(getNickPattern() + ":.*")) { // mobibot: <command>
@ -1000,7 +1016,7 @@ public class Mobibot extends PircBot {
for (final AbstractModule module : MODULES) { // modules for (final AbstractModule module : MODULES) { // modules
for (final String c : module.getCommands()) { for (final String c : module.getCommands()) {
if (cmd.startsWith(c)) { if (cmd.startsWith(c)) {
module.commandResponse(this, sender, args, false); module.commandResponse(this, sender, cmd, args, false);
} }
} }
} }
@ -1185,7 +1201,8 @@ public class Mobibot extends PircBot {
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@SuppressFBWarnings(value = {"DM_EXIT", "CC_CYCLOMATIC_COMPLEXITY"}, justification = "Yes, we want to bail out.") @SuppressFBWarnings(value = { "DM_EXIT", "CC_CYCLOMATIC_COMPLEXITY" },
justification = "Yes, we want to bail out.")
@Override @Override
protected final void onPrivateMessage(final String sender, final String login, final String hostname, protected final void onPrivateMessage(final String sender, final String login, final String hostname,
final String message) { final String message) {
@ -1280,7 +1297,7 @@ public class Mobibot extends PircBot {
if (module.isPrivateMsgEnabled()) { if (module.isPrivateMsgEnabled()) {
for (final String c : module.getCommands()) { for (final String c : module.getCommands()) {
if (cmd.equals(c)) { if (cmd.equals(c)) {
module.commandResponse(this, sender, args, true); module.commandResponse(this, sender, cmd, args, true);
return; return;
} }
} }
@ -1352,7 +1369,7 @@ public class Mobibot extends PircBot {
* sent. * sent.
*/ */
public final void send(final String sender, final String message, final boolean isPrivate) { public final void send(final String sender, final String message, final boolean isPrivate) {
if (StringUtils.isNotBlank(message) && StringUtils.isNotBlank(sender)) { if (isNotBlank(message) && isNotBlank(sender)) {
if (isPrivate) { if (isPrivate) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Sending message to {} : {}", sender, message); logger.debug("Sending message to {} : {}", sender, message);
@ -1460,7 +1477,7 @@ public class Mobibot extends PircBot {
* @param nicks The nicks to ignore * @param nicks The nicks to ignore
*/ */
final void setIgnoredNicks(final String nicks) { final void setIgnoredNicks(final String nicks) {
if (StringUtils.isNotBlank(nicks)) { if (isNotBlank(nicks)) {
final StringTokenizer st = new StringTokenizer(nicks, ","); final StringTokenizer st = new StringTokenizer(nicks, ",");
while (st.hasMoreTokens()) { while (st.hasMoreTokens()) {
@ -1475,7 +1492,7 @@ public class Mobibot extends PircBot {
* @param apiToken The API token * @param apiToken The API token
*/ */
final void setPinboardAuth(final String apiToken) { final void setPinboardAuth(final String apiToken) {
if (StringUtils.isNotBlank(apiToken)) { if (isNotBlank(apiToken)) {
pinboard = new Pinboard(this, apiToken, ircServer); pinboard = new Pinboard(this, apiToken, ircServer);
} }
} }
@ -1520,7 +1537,7 @@ public class Mobibot extends PircBot {
* @param msg The twitter message. * @param msg The twitter message.
*/ */
final void twitterNotification(final String msg) { final void twitterNotification(final String msg) {
if (twitterModule.isEnabled() && StringUtils.isNotBlank(twitterHandle)) { if (twitterModule.isEnabled() && isNotBlank(twitterHandle)) {
new Thread(() -> { new Thread(() -> {
try { try {
twitterModule.post(twitterHandle, getName() + ' ' + ReleaseInfo.VERSION + " " + msg, true); twitterModule.post(twitterHandle, getName() + ' ' + ReleaseInfo.VERSION + " " + msg, true);
@ -1629,8 +1646,8 @@ public class Mobibot extends PircBot {
|| (entry.getNick().toLowerCase(Constants.LOCALE).contains(lcArgs))) { || (entry.getNick().toLowerCase(Constants.LOCALE).contains(lcArgs))) {
if (sent > MAX_ENTRIES) { if (sent > MAX_ENTRIES) {
send(sender, send(sender,
"To view more, try: " + Utils "To view more, try: "
.bold(getNick() + ": " + Commands.VIEW_CMD + ' ' + (i + 1) + ' ' + lcArgs), + bold(getNick() + ": " + Commands.VIEW_CMD + ' ' + (i + 1) + ' ' + lcArgs),
isPrivate); isPrivate);
break; break;
@ -1642,7 +1659,7 @@ public class Mobibot extends PircBot {
} else { } else {
if (sent > MAX_ENTRIES) { if (sent > MAX_ENTRIES) {
send(sender, send(sender,
"To view more, try: " + Utils.bold(getNick() + ": " + Commands.VIEW_CMD + ' ' + (i + 1)), "To view more, try: " + bold(getNick() + ": " + Commands.VIEW_CMD + ' ' + (i + 1)),
isPrivate); isPrivate);
break; break;

View file

@ -1,3 +1,35 @@
/*
* TwitterOAuth.java
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot; package net.thauvin.erik.mobibot;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

View file

@ -1,7 +1,7 @@
/* /*
* Utils.java * Utils.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -34,6 +34,7 @@ package net.thauvin.erik.mobibot;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jibble.pircbot.Colors; import org.jibble.pircbot.Colors;
import org.jsoup.Jsoup;
import java.io.File; import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -233,8 +234,7 @@ public final class Utils {
* @return The unescaped string. * @return The unescaped string.
*/ */
public static String unescapeXml(final String str) { public static String unescapeXml(final String str) {
return str.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">").replace("&quot;", "\"").replace( return Jsoup.parse(str).text();
"&apos;", "'").replace("&#39;", "'");
} }
/** /**
@ -254,9 +254,9 @@ public final class Utils {
final long weeks = days / 7; final long weeks = days / 7;
days %= 7; days %= 7;
final long hours = TimeUnit.MILLISECONDS.toHours(uptime) - TimeUnit.DAYS.toHours( final long hours = TimeUnit.MILLISECONDS.toHours(uptime) - TimeUnit.DAYS.toHours(
TimeUnit.MILLISECONDS.toDays(uptime)); TimeUnit.MILLISECONDS.toDays(uptime));
final long minutes = TimeUnit.MILLISECONDS.toMinutes(uptime) - TimeUnit.HOURS.toMinutes( final long minutes = TimeUnit.MILLISECONDS.toMinutes(uptime) - TimeUnit.HOURS.toMinutes(
TimeUnit.MILLISECONDS.toHours(uptime)); TimeUnit.MILLISECONDS.toHours(uptime));
if (years > 0) { if (years > 0) {
info.append(years).append(plural(years, " year ", " years ")); info.append(years).append(plural(years, " year ", " years "));

View file

@ -1,7 +1,7 @@
/* /*
* AbstractModule.java * AbstractModule.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -57,11 +57,13 @@ public abstract class AbstractModule {
* *
* @param bot The bot's instance. * @param bot The bot's instance.
* @param sender The sender. * @param sender The sender.
* @param cmd The command.
* @param args The command arguments. * @param args The command arguments.
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message. * @param isPrivate Set to <code>true</code> if the response should be sent as a private message.
*/ */
public abstract void commandResponse(final Mobibot bot, public abstract void commandResponse(final Mobibot bot,
final String sender, final String sender,
final String cmd,
final String args, final String args,
final boolean isPrivate); final boolean isPrivate);

View file

@ -1,7 +1,7 @@
/* /*
* Calc.java * Calc.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -81,7 +81,11 @@ public class Calc extends AbstractModule {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (StringUtils.isNotBlank(args)) { if (StringUtils.isNotBlank(args)) {
bot.send(calc(args)); bot.send(calc(args));

View file

@ -1,7 +1,7 @@
/* /*
* CurrencyConverter.java * CurrencyConverter.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -87,44 +87,6 @@ public final class CurrencyConverter extends ThreadedModule {
commands.add(CURRENCY_CMD); commands.add(CURRENCY_CMD);
} }
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) {
synchronized (this) {
if (!pubDate.equals(Utils.today())) {
EXCHANGE_RATES.clear();
}
}
super.commandResponse(bot, sender, args, isPrivate);
}
/**
* Converts the specified currencies.
*/
@SuppressFBWarnings("REDOS")
@Override
void run(final Mobibot bot, final String sender, final String query) {
if (StringUtils.isNotBlank(sender) && StringUtils.isNotBlank(query)) {
if (query.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+")) {
try {
final Message msg = convertCurrency(query);
if (msg.isError()) {
helpResponse(bot, sender, CURRENCY_CMD + ' ' + query, false);
}
bot.send(sender, msg);
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage());
}
} else {
helpResponse(bot, sender, CURRENCY_CMD + ' ' + query, true);
}
}
}
/** /**
* Converts from a currency to another. * Converts from a currency to another.
* *
@ -157,8 +119,8 @@ public final class CurrencyConverter extends ThreadedModule {
for (final Element rawCube : cubes) { for (final Element rawCube : cubes) {
cube = rawCube; cube = rawCube;
EXCHANGE_RATES.put( EXCHANGE_RATES.put(
cube.getAttribute("currency").getValue(), cube.getAttribute("currency").getValue(),
cube.getAttribute("rate").getValue()); cube.getAttribute("rate").getValue());
} }
EXCHANGE_RATES.put("EUR", "1"); EXCHANGE_RATES.put("EUR", "1");
@ -166,7 +128,7 @@ public final class CurrencyConverter extends ThreadedModule {
throw new ModuleException(query, "An error has occurred while parsing the exchange rates table.", e); throw new ModuleException(query, "An error has occurred while parsing the exchange rates table.", e);
} catch (IOException e) { } catch (IOException e) {
throw new ModuleException( throw new ModuleException(
query, "An error has occurred while fetching the exchange rates table.", e); query, "An error has occurred while fetching the exchange rates table.", e);
} }
} }
@ -181,23 +143,23 @@ public final class CurrencyConverter extends ThreadedModule {
} else { } else {
try { try {
final double amt = Double.parseDouble(cmds[0].replace(",", "")); final double amt = Double.parseDouble(cmds[0].replace(",", ""));
final double from = Double.parseDouble(EXCHANGE_RATES.get(cmds[1] final double from =
.toUpperCase(Constants.LOCALE))); Double.parseDouble(EXCHANGE_RATES.get(cmds[1].toUpperCase(Constants.LOCALE)));
final double to = Double.parseDouble(EXCHANGE_RATES.get(cmds[3].toUpperCase(Constants.LOCALE))); final double to = Double.parseDouble(EXCHANGE_RATES.get(cmds[3].toUpperCase(Constants.LOCALE)));
return new PublicMessage( return new PublicMessage(
NumberFormat.getCurrencyInstance(Locale.US).format(amt).substring(1) NumberFormat.getCurrencyInstance(Locale.US).format(amt).substring(1)
+ ' ' + ' '
+ cmds[1].toUpperCase(Constants.LOCALE) + cmds[1].toUpperCase(Constants.LOCALE)
+ " = " + " = "
+ NumberFormat.getCurrencyInstance(Locale.US) + NumberFormat.getCurrencyInstance(Locale.US)
.format((amt * to) / from) .format((amt * to) / from)
.substring(1) .substring(1)
+ ' ' + ' '
+ cmds[3].toUpperCase(Constants.LOCALE)); + cmds[3].toUpperCase(Constants.LOCALE));
} catch (Exception e) { } catch (Exception e) {
throw new ModuleException("convertCurrency(" + query + ')', throw new ModuleException("convertCurrency(" + query + ')',
"The supported currencies are: " + EXCHANGE_RATES.keySet(), e); "The supported currencies are: " + EXCHANGE_RATES.keySet(), e);
} }
} }
} else if (CURRENCY_RATES_KEYWORD.equals(query)) { } else if (CURRENCY_RATES_KEYWORD.equals(query)) {
@ -219,6 +181,48 @@ public final class CurrencyConverter extends ThreadedModule {
return new ErrorMessage("The supported currencies are: " + EXCHANGE_RATES.keySet()); return new ErrorMessage("The supported currencies are: " + EXCHANGE_RATES.keySet());
} }
/**
* {@inheritDoc}
*/
@Override
public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
synchronized (this) {
if (!pubDate.equals(Utils.today())) {
EXCHANGE_RATES.clear();
}
}
super.commandResponse(bot, sender, cmd, args, isPrivate);
}
/**
* Converts the specified currencies.
*/
@SuppressFBWarnings("REDOS")
@Override
void run(final Mobibot bot, final String sender, final String cmd, final String query) {
if (StringUtils.isNotBlank(sender) && StringUtils.isNotBlank(query)) {
if (query.matches("\\d+([,\\d]+)?(\\.\\d+)? [a-zA-Z]{3}+ to [a-zA-Z]{3}+")) {
try {
final Message msg = convertCurrency(query);
if (msg.isError()) {
helpResponse(bot, sender, CURRENCY_CMD + ' ' + query, false);
}
bot.send(sender, msg);
} catch (ModuleException e) {
bot.getLogger().warn(e.getDebugMessage(), e);
bot.send(sender, e.getMessage());
}
} else {
helpResponse(bot, sender, CURRENCY_CMD + ' ' + query, true);
}
}
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View file

@ -1,7 +1,7 @@
/* /*
* Dice.java * Dice.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -57,15 +57,14 @@ public final class Dice extends AbstractModule {
} }
/** /**
* Rolls the dice. * {@inheritDoc}
*
* @param bot The bot's instance.
* @param sender The sender.
* @param args The command arguments.
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message.
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final SecureRandom r = new SecureRandom(); final SecureRandom r = new SecureRandom();
int i = r.nextInt(6) + 1; int i = r.nextInt(6) + 1;
@ -73,15 +72,15 @@ public final class Dice extends AbstractModule {
final int playerTotal = i + y; final int playerTotal = i + y;
bot.send(bot.getChannel(), bot.send(bot.getChannel(),
sender + " rolled two dice: " + Utils.bold(i) + " and " + Utils.bold(y) + " for a total of " sender + " rolled two dice: " + Utils.bold(i) + " and " + Utils.bold(y) + " for a total of "
+ Utils.bold(playerTotal)); + Utils.bold(playerTotal));
i = r.nextInt(6) + 1; i = r.nextInt(6) + 1;
y = r.nextInt(6) + 1; y = r.nextInt(6) + 1;
final int total = i + y; final int total = i + y;
bot.action( bot.action(
"rolled two dice: " + Utils.bold(i) + " and " + Utils.bold(y) + " for a total of " + Utils.bold(total)); "rolled two dice: " + Utils.bold(i) + " and " + Utils.bold(y) + " for a total of " + Utils.bold(total));
if (playerTotal < total) { if (playerTotal < total) {
bot.action("wins."); bot.action("wins.");

View file

@ -1,7 +1,7 @@
/* /*
* GoogleSearch.java * GoogleSearch.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -96,7 +96,7 @@ public final class GoogleSearch extends ThreadedModule {
* Searches Google. * Searches Google.
*/ */
@Override @Override
void run(final Mobibot bot, final String sender, final String query) { void run(final Mobibot bot, final String sender, final String cmd, final String query) {
if (StringUtils.isNotBlank(query)) { if (StringUtils.isNotBlank(query)) {
try { try {
final List<Message> results = searchGoogle(query, properties.get(GOOGLE_API_KEY_PROP), final List<Message> results = searchGoogle(query, properties.get(GOOGLE_API_KEY_PROP),

View file

@ -1,7 +1,7 @@
/* /*
* Joke.java * Joke.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -56,7 +56,7 @@ public final class Joke extends ThreadedModule {
private static final String JOKE_CMD = "joke"; private static final String JOKE_CMD = "joke";
// The ICNDB URL. // The ICNDB URL.
private static final String JOKE_URL = private static final String JOKE_URL =
"http://api.icndb.com/jokes/random?escape=javascript&exclude=[explicit]&limitTo=[nerdy]"; "http://api.icndb.com/jokes/random?escape=javascript&exclude=[explicit]&limitTo=[nerdy]";
/** /**
* Creates a new {@link Joke} instance. * Creates a new {@link Joke} instance.
@ -79,7 +79,7 @@ public final class Joke extends ThreadedModule {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
try (final BufferedReader reader = try (final BufferedReader reader =
new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
sb.append(line); sb.append(line);
@ -88,8 +88,8 @@ public final class Joke extends ThreadedModule {
final JSONObject json = new JSONObject(sb.toString()); final JSONObject json = new JSONObject(sb.toString());
return new PublicMessage( return new PublicMessage(
json.getJSONObject("value").get("joke").toString().replace("\\'", "'") json.getJSONObject("value").get("joke").toString().replace("\\'", "'")
.replace("\\\"", "\"")); .replace("\\\"", "\""));
} }
} catch (Exception e) { } catch (Exception e) {
throw new ModuleException("randomJoke()", "An error has occurred retrieving a random joke.", e); throw new ModuleException("randomJoke()", "An error has occurred retrieving a random joke.", e);
@ -100,15 +100,19 @@ public final class Joke extends ThreadedModule {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
new Thread(() -> run(bot, sender, args)).start(); final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
new Thread(() -> run(bot, sender, cmd, args)).start();
} }
/** /**
* Returns a random joke from <a href="http://www.icndb.com/">The Internet Chuck Norris Database</a>. * Returns a random joke from <a href="http://www.icndb.com/">The Internet Chuck Norris Database</a>.
*/ */
@Override @Override
void run(final Mobibot bot, final String sender, final String arg) { void run(final Mobibot bot, final String sender, final String cmd, final String arg) {
try { try {
bot.send(Utils.cyan(randomJoke().getMessage())); bot.send(Utils.cyan(randomJoke().getMessage()));
} catch (ModuleException e) { } catch (ModuleException e) {

View file

@ -1,7 +1,7 @@
/* /*
* Lookup.java * Lookup.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -143,15 +143,14 @@ public final class Lookup extends AbstractModule {
} }
/** /**
* Process a command. * {@inheritDoc}
*
* @param bot The bot's instance.
* @param sender The sender.
* @param args The command arguments.
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message.
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (args.matches("(\\S.)+(\\S)+")) { if (args.matches("(\\S.)+(\\S)+")) {
try { try {
bot.send(Lookup.lookup(args)); bot.send(Lookup.lookup(args));

View file

@ -1,7 +1,7 @@
/* /*
* Ping.java * Ping.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -50,19 +50,19 @@ public class Ping extends AbstractModule {
* The ping responses. * The ping responses.
*/ */
static final List<String> PINGS = static final List<String> PINGS =
Arrays.asList( Arrays.asList(
"is barely alive.", "is barely alive.",
"is trying to stay awake.", "is trying to stay awake.",
"has gone fishing.", "has gone fishing.",
"is somewhere over the rainbow.", "is somewhere over the rainbow.",
"has fallen and can't get up.", "has fallen and can't get up.",
"is running. You better go chase it.", "is running. You better go chase it.",
"has just spontaneously combusted.", "has just spontaneously combusted.",
"is talking to itself... don't interrupt. That's rude.", "is talking to itself... don't interrupt. That's rude.",
"is bartending at an AA meeting.", "is bartending at an AA meeting.",
"is hibernating.", "is hibernating.",
"is saving energy: apathetic mode activated.", "is saving energy: apathetic mode activated.",
"is busy. Go away!"); "is busy. Go away!");
/** /**
* The ping command. * The ping command.
*/ */
@ -80,7 +80,11 @@ public class Ping extends AbstractModule {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final SecureRandom r = new SecureRandom(); final SecureRandom r = new SecureRandom();
bot.action(PINGS.get(r.nextInt(PINGS.size()))); bot.action(PINGS.get(r.nextInt(PINGS.size())));
} }

View file

@ -0,0 +1,116 @@
/*
* RockPaperScissors.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
import kotlin.random.Random
/**
* Simple module example in Kotlin.
*/
class RockPaperScissors : AbstractModule() {
init {
with(commands) {
add(Shapes.ROCK.value)
add(Shapes.SCISSORS.value)
add(Shapes.PAPER.value)
}
}
enum class Shapes(val value: String) {
ROCK("rock"), PAPER("paper"), SCISSORS("scissors")
}
enum class Results {
WIN, LOSE, DRAW
}
companion object {
/**
* Returns the the randomly picked shape and result.
*/
fun winLoseOrDraw(hand: Shapes): Pair<Shapes, Results> {
val botHand = Shapes.values()[Random.nextInt(0, Shapes.values().size)]
val result: Results
if (botHand == hand) {
result = Results.DRAW
} else {
when (botHand) {
Shapes.ROCK -> {
result = if (hand == Shapes.PAPER) {
Results.WIN
} else {
Results.LOSE
}
}
Shapes.PAPER -> {
result = if (hand == Shapes.ROCK) {
Results.LOSE
} else {
Results.WIN
}
}
Shapes.SCISSORS -> {
result = if (hand == Shapes.ROCK) {
Results.WIN
} else {
Results.LOSE
}
}
}
}
return Pair(botHand, result)
}
}
override fun commandResponse(bot: Mobibot?, sender: String?, cmd: String?, args: String?, isPrivate: Boolean) {
val result = winLoseOrDraw(Shapes.valueOf(cmd!!.toUpperCase()))
val picked = "picked ${Utils.bold(result.first.value)}."
when (result.second) {
Results.WIN -> bot!!.action("$picked You win.")
Results.LOSE -> bot!!.action("$picked You lose.")
else -> bot!!.action("$picked We have a draw.")
}
}
override fun helpResponse(bot: Mobibot?, sender: String?, args: String?, isPrivate: Boolean) {
bot!!.send(sender, "To play Rock Paper Scissors:")
bot.send(
sender,
bot.helpIndent("${bot.nick}: ${Shapes.ROCK.value} or ${Shapes.PAPER.value} or ${Shapes.SCISSORS.value}")
)
}
}

View file

@ -1,7 +1,7 @@
/* /*
* StockQuote.java * StockQuote.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -211,7 +211,7 @@ public final class StockQuote extends ThreadedModule {
* Returns the specified stock quote from Alpha Advantage. * Returns the specified stock quote from Alpha Advantage.
*/ */
@Override @Override
void run(final Mobibot bot, final String sender, final String symbol) { void run(final Mobibot bot, final String sender, final String cmd, final String symbol) {
if (StringUtils.isNotBlank(symbol)) { if (StringUtils.isNotBlank(symbol)) {
try { try {
final List<Message> messages = getQuote(symbol, properties.get(ALPHAVANTAGE_API_KEY_PROP)); final List<Message> messages = getQuote(symbol, properties.get(ALPHAVANTAGE_API_KEY_PROP));

View file

@ -1,7 +1,7 @@
/* /*
* ThreadedModule.java * ThreadedModule.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -46,9 +46,13 @@ public abstract class ThreadedModule extends AbstractModule {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
if (isEnabled() && args.length() > 0) { if (isEnabled() && args.length() > 0) {
new Thread(() -> run(bot, sender, args)).start(); new Thread(() -> run(bot, sender, cmd, args)).start();
} else { } else {
helpResponse(bot, sender, args, isPrivate); helpResponse(bot, sender, args, isPrivate);
} }
@ -57,5 +61,5 @@ public abstract class ThreadedModule extends AbstractModule {
/** /**
* Runs the thread. * Runs the thread.
*/ */
abstract void run(Mobibot bot, String sender, String args); abstract void run(Mobibot bot, String sender, @SuppressWarnings("unused") String cmd, String args);
} }

View file

@ -1,7 +1,7 @@
/* /*
* Twitter.java * Twitter.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -148,7 +148,7 @@ public final class Twitter extends ThreadedModule {
* Posts to twitter. * Posts to twitter.
*/ */
@Override @Override
void run(final Mobibot bot, final String sender, final String message) { void run(final Mobibot bot, final String sender, final String cmd, final String message) {
try { try {
bot.send(sender, post(sender, message, false).getMessage()); bot.send(sender, post(sender, message, false).getMessage());
} catch (ModuleException e) { } catch (ModuleException e) {

View file

@ -1,7 +1,7 @@
/* /*
* War.java * War.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -49,9 +49,9 @@ public final class War extends AbstractModule {
private static final String WAR_CMD = "war"; private static final String WAR_CMD = "war";
// The deck of card. // The deck of card.
private static final String[] WAR_DECK = private static final String[] WAR_DECK =
new String[]{"Ace", "King", "Queen", "Jack", "10", "9", "8", "7", "6", "5", "4", "3", "2"}; new String[]{ "Ace", "King", "Queen", "Jack", "10", "9", "8", "7", "6", "5", "4", "3", "2" };
// The suits for the deck of card. // The suits for the deck of card.
private static final String[] WAR_SUITS = new String[]{"Hearts", "Spades", "Diamonds", "Clubs"}; private static final String[] WAR_SUITS = new String[]{ "Hearts", "Spades", "Diamonds", "Clubs" };
/** /**
* The default constructor. * The default constructor.
@ -62,15 +62,14 @@ public final class War extends AbstractModule {
} }
/** /**
* Plays war. * {@inheritDoc}
*
* @param bot The bot's instance.
* @param sender The sender.
* @param args The command arguments.
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message.
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final SecureRandom r = new SecureRandom(); final SecureRandom r = new SecureRandom();
int i; int i;
@ -81,7 +80,7 @@ public final class War extends AbstractModule {
y = r.nextInt(WAR_DECK.length); y = r.nextInt(WAR_DECK.length);
bot.send(bot.getChannel(), bot.send(bot.getChannel(),
sender + " drew the " + Utils.bold(WAR_DECK[i]) + " of " + WAR_SUITS[r.nextInt(WAR_SUITS.length)]); sender + " drew the " + Utils.bold(WAR_DECK[i]) + " of " + WAR_SUITS[r.nextInt(WAR_SUITS.length)]);
bot.action("drew the " + Utils.bold(WAR_DECK[y]) + " of " + WAR_SUITS[r.nextInt(WAR_SUITS.length)]); bot.action("drew the " + Utils.bold(WAR_DECK[y]) + " of " + WAR_SUITS[r.nextInt(WAR_SUITS.length)]);
if (i != y) { if (i != y) {

View file

@ -1,7 +1,7 @@
/* /*
* Weather2.java * Weather2.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -217,7 +217,7 @@ public class Weather2 extends ThreadedModule {
* Fetches the weather data from a specific city. * Fetches the weather data from a specific city.
*/ */
@Override @Override
void run(final Mobibot bot, final String sender, final String args) { void run(final Mobibot bot, final String sender, final String cmd, final String args) {
if (StringUtils.isNotBlank(args)) { if (StringUtils.isNotBlank(args)) {
try { try {
final List<Message> messages = getWeather(args, properties.get(OWM_API_KEY_PROP)); final List<Message> messages = getWeather(args, properties.get(OWM_API_KEY_PROP));

View file

@ -1,7 +1,7 @@
/* /*
* WorldTime.java * WorldTime.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -146,9 +146,9 @@ public final class WorldTime extends AbstractModule {
countries.put("INTERNET", BEATS_KEYWORD); countries.put("INTERNET", BEATS_KEYWORD);
countries.put("BEATS", BEATS_KEYWORD); countries.put("BEATS", BEATS_KEYWORD);
ZoneId.getAvailableZoneIds().stream().filter(tz -> ZoneId.getAvailableZoneIds().stream()
!tz.contains("/") && tz.length() == 3 && !countries.containsKey(tz)).forEach(tz -> .filter(tz -> !tz.contains("/") && tz.length() == 3 && !countries.containsKey(tz))
countries.put(tz, tz)); .forEach(tz -> countries.put(tz, tz));
COUNTRIES_MAP = Collections.unmodifiableMap(countries); COUNTRIES_MAP = Collections.unmodifiableMap(countries);
} }
@ -162,15 +162,58 @@ public final class WorldTime extends AbstractModule {
} }
/** /**
* Responds with the current time in the specified timezone/country. * Returns the current Internet (beat) Time.
* *
* @param bot The bot's instance. * @return The Internet Time string.
* @param sender The sender. */
* @param args The command arguments. private static String internetTime() {
* @param isPrivate Set to <code>true</code> if the response should be sent as a private message. final ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("UTC+01:00"));
final int beats = (int) ((zdt.get(ChronoField.SECOND_OF_MINUTE) + (zdt.get(ChronoField.MINUTE_OF_HOUR) * 60)
+ (zdt.get(ChronoField.HOUR_OF_DAY) * 3600)) / 86.4);
return String.format("%c%03d", '@', beats);
}
/**
* Returns the world time.
*
* <ul>
* <li>PST</li>
* <li>BEATS</li>
* </ul>
*
* @param query The query.
* @return The {@link Message} containing the world time.
*/
@SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
static Message worldTime(final String query) {
final String tz = (COUNTRIES_MAP.get((query.substring(query.indexOf(' ') + 1).trim()
.toUpperCase(Constants.LOCALE))));
final String response;
if (tz != null) {
if (BEATS_KEYWORD.equals(tz)) {
response = ("The current Internet Time is: " + internetTime() + ' ' + BEATS_KEYWORD);
} else {
response = ZonedDateTime.now().withZoneSameInstant(ZoneId.of(tz)).format(
DateTimeFormatter.ofPattern("'The time is 'HH:mm' on 'EEEE, d MMMM yyyy' in '"))
+ tz.substring(tz.indexOf('/') + 1).replace('_', ' ');
}
} else {
return new ErrorMessage("The supported countries/zones are: " + COUNTRIES_MAP.keySet());
}
return new PublicMessage(response);
}
/**
* {@inheritDoc}
*/ */
@Override @Override
public void commandResponse(final Mobibot bot, final String sender, final String args, final boolean isPrivate) { public void commandResponse(final Mobibot bot,
final String sender,
final String cmd,
final String args,
final boolean isPrivate) {
final Message msg = worldTime(args); final Message msg = worldTime(args);
if (isPrivate) { if (isPrivate) {
@ -203,48 +246,4 @@ public final class WorldTime extends AbstractModule {
public boolean isPrivateMsgEnabled() { public boolean isPrivateMsgEnabled() {
return true; return true;
} }
/**
* Returns the current Internet (beat) Time.
*
* @return The Internet Time string.
*/
private static String internetTime() {
final ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("UTC+01:00"));
final int beats = (int) ((zdt.get(ChronoField.SECOND_OF_MINUTE) + (zdt.get(ChronoField.MINUTE_OF_HOUR) * 60)
+ (zdt.get(ChronoField.HOUR_OF_DAY) * 3600)) / 86.4);
return String.format("%c%03d", '@', beats);
}
/**
* Returns the world time.
*
* <ul>
* <li>PST</li>
* <li>BEATS</li>
* </ul>
*
* @param query The query.
* @return The {@link Message} containing the world time.
*/
@SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
static Message worldTime(final String query) {
final String tz = (COUNTRIES_MAP.get((query.substring(query.indexOf(' ') + 1).trim()
.toUpperCase(Constants.LOCALE))));
final String response;
if (tz != null) {
if (BEATS_KEYWORD.equals(tz)) {
response = ("The current Internet Time is: " + internetTime() + ' ' + BEATS_KEYWORD);
} else {
response = ZonedDateTime.now().withZoneSameInstant(ZoneId.of(tz)).format(
DateTimeFormatter.ofPattern("'The time is 'HH:mm' on 'EEEE, d MMMM yyyy' in '"))
+ tz.substring(tz.indexOf('/') + 1).replace('_', ' ');
}
} else {
return new ErrorMessage("The supported countries/zones are: " + COUNTRIES_MAP.keySet());
}
return new PublicMessage(response);
}
} }

View file

@ -1,7 +1,7 @@
/* /*
* EntryLinkTest.java * EntryLinkTest.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without

View file

@ -1,7 +1,7 @@
/* /*
* AbstractModuleTest.java * AbstractModuleTest.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without

View file

@ -0,0 +1,67 @@
/*
* RockPaperScissorsTest.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of this project nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.modules.RockPaperScissors.Results
import net.thauvin.erik.mobibot.modules.RockPaperScissors.Shapes
import org.assertj.core.api.Assertions.assertThat
import org.testng.annotations.Test
class RockPaperScissorsTest {
@Test(invocationCount = 5)
fun testWinLoseOrDraw() {
var play = RockPaperScissors.winLoseOrDraw(Shapes.SCISSORS)
// println("SCISSORS vs ${play.first}: ${play.second}")
when (play.first) {
Shapes.SCISSORS -> assertThat(play.second).`as`("SCISSORS vs ${play.first}").isEqualTo(Results.DRAW)
Shapes.ROCK -> assertThat(play.second).`as`("SCISSORS vs ${play.first}").isEqualTo(Results.LOSE)
else -> assertThat(play.second).`as`("SCISSORS vs ${play.first}").isEqualTo(Results.WIN)
}
play = RockPaperScissors.winLoseOrDraw(Shapes.ROCK)
// println("ROCK vs ${play.first}: ${play.second}")
when (play.first) {
Shapes.SCISSORS -> assertThat(play.second).`as`("ROCK vs ${play.first}").isEqualTo(Results.WIN)
Shapes.ROCK -> assertThat(play.second).`as`("ROCK vs ${play.first}").isEqualTo(Results.DRAW)
else -> assertThat(play.second).`as`("ROCK vs ${play.first}").isEqualTo(Results.LOSE)
}
play = RockPaperScissors.winLoseOrDraw(Shapes.PAPER)
// println("PAPER vs ${play.first}: ${play.second}")
when (play.first) {
Shapes.SCISSORS -> assertThat(play.second).`as`("PAPER vs ${play.first}").isEqualTo(Results.LOSE)
Shapes.ROCK -> assertThat(play.second).`as`("PAPER vs ${play.first}").isEqualTo(Results.WIN)
else -> assertThat(play.second).`as`("PAPER vs ${play.first}").isEqualTo(Results.DRAW)
}
}
}

View file

@ -1,7 +1,7 @@
/* /*
* TellMessageTest.java * TellMessageTest.java
* *
* Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without

View file

@ -1,9 +1,9 @@
#Generated by the Semver Plugin for Gradle #Generated by the Semver Plugin for Gradle
#Wed Mar 18 16:00:11 PDT 2020 #Mon Mar 23 05:21:18 PDT 2020
version.buildmeta=581 version.buildmeta=682
version.major=0 version.major=0
version.minor=7 version.minor=7
version.patch=3 version.patch=3
version.prerelease=beta version.prerelease=beta
version.project=mobibot version.project=mobibot
version.semver=0.7.3-beta+581 version.semver=0.7.3-beta+682