diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 1f720ec..fb7f4a8 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,17 +1,6 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/mobibot.iml b/.idea/mobibot.iml
index 51a4995..85e216a 100644
--- a/.idea/mobibot.iml
+++ b/.idea/mobibot.iml
@@ -1,5 +1,5 @@
-
+
diff --git a/.idea/modules/mobibot.main.iml b/.idea/modules/mobibot.main.iml
index c6fe00f..4c00984 100644
--- a/.idea/modules/mobibot.main.iml
+++ b/.idea/modules/mobibot.main.iml
@@ -1,5 +1,5 @@
-
+
@@ -8,7 +8,7 @@
-
+
@@ -20,6 +20,9 @@
@@ -39,46 +43,59 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/.idea/modules/mobibot.test.iml b/.idea/modules/mobibot.test.iml
index f864b22..822bffe 100644
--- a/.idea/modules/mobibot.test.iml
+++ b/.idea/modules/mobibot.test.iml
@@ -1,5 +1,5 @@
-
+
@@ -8,7 +8,7 @@
-
+
@@ -17,6 +17,7 @@
+
@@ -27,6 +28,9 @@
+
+
+
@@ -34,6 +38,7 @@
+
@@ -46,6 +51,12 @@
+
+
+
+
+
+
@@ -53,10 +64,20 @@
+
+
+
+
+
+
+
+
+
+
@@ -100,6 +121,7 @@
+
\ No newline at end of file
diff --git a/ReleaseInfo.mustache b/ReleaseInfo.mustache
new file mode 100644
index 0000000..b2672e4
--- /dev/null
+++ b/ReleaseInfo.mustache
@@ -0,0 +1,32 @@
+/*
+* This file is automatically generated.
+* Do not modify! -- ALL CHANGES WILL BE ERASED!
+*/
+package {{packageName}}
+
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.Instant
+
+/**
+* Provides semantic version information.
+*
+* @author [Semantic Version Annotation Processor](https://github.com/ethauvin/semver)
+*/
+object ReleaseInfo{
+ const val PROJECT = "{{project}}"
+ const val VERSION = "{{version}}"
+
+ @JvmField
+ val BUILDDATE = LocalDateTime.ofInstant(Instant.ofEpochMilli({{epoch}}L), ZoneId.systemDefault())
+
+ const val MAJOR = {{major}}
+ const val MINOR = {{minor}}
+ const val PATCH = {{patch}}
+ const val BUILDMETA = "{{buildMeta}}"
+ const val PRERELEASE = "{{preRelease}}"
+
+ const val WEBSITE = "https://www.mobitopia.org/mobibot/"
+ const val AUTHOR = "Erik C. Thauvin"
+ const val AUTHOR_URL = "https://erik.thauvin.net/"
+}
diff --git a/build.gradle b/build.gradle
index 6b905f9..9ff3602 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,7 @@ plugins {
id 'java'
id 'net.thauvin.erik.gradle.semver' version '1.0.4'
id 'org.jetbrains.kotlin.jvm' version '1.4.20'
+ id 'org.jetbrains.kotlin.kapt' version '1.4.20'
id 'org.sonarqube' version '3.0'
id 'pmd'
}
@@ -33,8 +34,8 @@ repositories {
}
dependencies {
- annotationProcessor semverProcessor
- compileOnly semverProcessor
+ kapt(semverProcessor)
+ implementation(semverProcessor)
implementation 'pircbot:pircbot:1.5.0'
compileOnly 'pircbot:pircbot:1.5.0:sources'
@@ -77,14 +78,18 @@ java {
targetCompatibility = JavaVersion.VERSION_11
}
+kapt {
+ arguments {
+ arg("semver.project.dir", projectDir)
+ }
+}
+
application {
mainClassName = packageName + '.Mobibot'
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
- options.annotationProcessorGeneratedSourcesDirectory = file("${projectDir}/src/generated/java")
- options.compilerArgs += ["-Asemver.project.dir=$projectDir"]
}
compileJava {
diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml
index 59b5db4..46ddfa2 100644
--- a/config/detekt/baseline.xml
+++ b/config/detekt/baseline.xml
@@ -3,8 +3,10 @@
ComplexMethod:EntriesMgr.kt$EntriesMgr.Companion$ fun saveEntries( bot: Mobibot, entries: List<EntryLink>, history: MutableList<String>, isDayBackup: Boolean )
+ ComplexMethod:Mobibot.kt$Mobibot$override fun onPrivateMessage( sender: String, login: String, hostname: String, message: String )
ComplexMethod:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message>
LongMethod:EntriesMgr.kt$EntriesMgr.Companion$ fun saveEntries( bot: Mobibot, entries: List<EntryLink>, history: MutableList<String>, isDayBackup: Boolean )
+ LongMethod:Mobibot.kt$Mobibot.Companion$ @JvmStatic fun main(args: Array<String>)
LongMethod:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message>
LongMethod:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message>
LongParameterList:Comment.kt$Comment$( bot: Mobibot, cmd: String, sender: String, isOp: Boolean, entry: EntryLink, index: Int, commentIndex: Int )
@@ -22,6 +24,11 @@
MagicNumber:Cycle.kt$Cycle$10
MagicNumber:Dice.kt$Dice$7
MagicNumber:Ignore.kt$Ignore$8
+ MagicNumber:Mobibot.kt$Mobibot$10
+ MagicNumber:Mobibot.kt$Mobibot$1000L
+ MagicNumber:Mobibot.kt$Mobibot$3
+ MagicNumber:Mobibot.kt$Mobibot$5
+ MagicNumber:Mobibot.kt$Mobibot$8
MagicNumber:Modules.kt$Modules$7
MagicNumber:Recap.kt$Recap.Companion$10
MagicNumber:Tell.kt$Tell$50
@@ -50,11 +57,14 @@
NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$ @JvmStatic fun convertCurrency(query: String): Message
NestedBlockDepth:EntriesMgr.kt$EntriesMgr.Companion$ @Throws(IOException::class, FeedException::class) fun loadEntries(file: String, channel: String, entries: ArrayList<EntryLink>): String
NestedBlockDepth:EntriesMgr.kt$EntriesMgr.Companion$ fun saveEntries( bot: Mobibot, entries: List<EntryLink>, history: MutableList<String>, isDayBackup: Boolean )
- NestedBlockDepth:EntryLink.kt$EntryLink$ fun setTags(tags: List<String?>)
+ NestedBlockDepth:EntryLink.kt$EntryLink$ private fun setTags(tags: List<String?>)
NestedBlockDepth:FeedReader.kt$FeedReader$ override fun run()
NestedBlockDepth:GoogleSearch.kt$GoogleSearch$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean)
NestedBlockDepth:LinksMgr.kt$LinksMgr$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean )
NestedBlockDepth:Lookup.kt$Lookup$override fun commandResponse( sender: String, cmd: String, args: String, isPrivate: Boolean )
+ NestedBlockDepth:Mobibot.kt$Mobibot$ fun connect()
+ NestedBlockDepth:Mobibot.kt$Mobibot$override fun onMessage( channel: String, sender: String, login: String, hostname: String, message: String )
+ NestedBlockDepth:Mobibot.kt$Mobibot$override fun onPrivateMessage( sender: String, login: String, hostname: String, message: String )
NestedBlockDepth:StockQuote.kt$StockQuote$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean)
NestedBlockDepth:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message>
NestedBlockDepth:Tell.kt$Tell$ @JvmOverloads fun send(nickname: String, isMessage: Boolean = false)
@@ -63,14 +73,18 @@
NestedBlockDepth:Weather2.kt$Weather2$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean)
NestedBlockDepth:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message>
NestedBlockDepth:WorldTime.kt$WorldTime$override fun commandResponse( sender: String, cmd: String, args: String, isPrivate: Boolean )
+ ReturnCount:Mobibot.kt$Mobibot$override fun onMessage( channel: String, sender: String, login: String, hostname: String, message: String )
ReturnCount:Utils.kt$Utils.Companion$ @JvmStatic fun colorize(s: String?, color: String): String
ThrowsCount:GoogleSearch.kt$GoogleSearch.Companion$ @JvmStatic @Throws(ModuleException::class) fun searchGoogle(query: String, apiKey: String?, cseKey: String?): List<Message>
ThrowsCount:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List<Message>
ThrowsCount:StockQuote.kt$StockQuote.Companion$@Throws(ModuleException::class) private fun getJsonResponse(response: String, debugMessage: String): JSONObject
ThrowsCount:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List<Message>
TooGenericExceptionCaught:FeedReader.kt$FeedReader$e: Exception
+ TooGenericExceptionCaught:Mobibot.kt$Mobibot$e: Exception
+ TooGenericExceptionCaught:Mobibot.kt$Mobibot$ex: Exception
TooGenericExceptionCaught:StockQuote.kt$StockQuote.Companion$e: NullPointerException
TooGenericExceptionCaught:Weather2.kt$Weather2.Companion$e: NullPointerException
+ TooManyFunctions:Mobibot.kt$Mobibot : PircBot
TooManyFunctions:Tell.kt$Tell : AbstractCommand
TooManyFunctions:Utils.kt$Utils$Companion
diff --git a/mobibot.mustache b/mobibot.mustache
deleted file mode 100644
index a090f01..0000000
--- a/mobibot.mustache
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * This file is automatically generated.
- * Do not modify! -- ALL CHANGES WILL BE ERASED!
- */
-package {{packageName}};
-
-import java.time.*;
-
-/**
- * Provides semantic version information.
- *
- * @author Semantic Version Annotation Processor
- */
-public final class {{className}} {
- public static final String PROJECT = "{{project}}";
- public static final String VERSION = "{{version}}";
- public static final LocalDateTime BUILDDATE =
- LocalDateTime.ofInstant(Instant.ofEpochMilli({{epoch}}L), ZoneId.systemDefault());
-
- public static final int MAJOR = {{major}};
- public static final int MINOR = {{minor}};
- public static final int PATCH = {{patch}};
- public static final String BUILDMETA = "{{buildMeta}}";
- public static final String PRERELEASE = "{{preRelease}}";
-
- public static final String WEBSITE = "https://www.mobitopia.org/mobibot/";
- public static final String AUTHOR = "Erik C. Thauvin";
- public static final String AUTHOR_URL = "https://erik.thauvin.net/";
-
- /**
- * Disables the default constructor.
- */
- private {{className}}() {
- throw new UnsupportedOperationException("Illegal constructor call.");
- }
-}
diff --git a/src/generated/java/net/thauvin/erik/mobibot/ReleaseInfo.java b/src/generated/java/net/thauvin/erik/mobibot/ReleaseInfo.java
deleted file mode 100644
index 900222b..0000000
--- a/src/generated/java/net/thauvin/erik/mobibot/ReleaseInfo.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * This file is automatically generated.
- * Do not modify! -- ALL CHANGES WILL BE ERASED!
- */
-package net.thauvin.erik.mobibot;
-
-import java.time.*;
-
-/**
- * Provides semantic version information.
- *
- * @author Semantic Version Annotation Processor
- */
-public final class ReleaseInfo {
- public static final String PROJECT = "mobibot";
- public static final String VERSION = "0.8.0-beta+305";
- public static final LocalDateTime BUILDDATE =
- LocalDateTime.ofInstant(Instant.ofEpochMilli(1607122730368L), ZoneId.systemDefault());
-
- public static final int MAJOR = 0;
- public static final int MINOR = 8;
- public static final int PATCH = 0;
- public static final String BUILDMETA = "305";
- public static final String PRERELEASE = "beta";
-
- public static final String WEBSITE = "https://www.mobitopia.org/mobibot/";
- public static final String AUTHOR = "Erik C. Thauvin";
- public static final String AUTHOR_URL = "https://erik.thauvin.net/";
-
- /**
- * Disables the default constructor.
- */
- private ReleaseInfo() {
- throw new UnsupportedOperationException("Illegal constructor call.");
- }
-}
diff --git a/src/main/java/net/thauvin/erik/mobibot/Mobibot.java b/src/main/java/net/thauvin/erik/mobibot/Mobibot.java
deleted file mode 100644
index 420833d..0000000
--- a/src/main/java/net/thauvin/erik/mobibot/Mobibot.java
+++ /dev/null
@@ -1,956 +0,0 @@
-/*
- * Mobibot.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;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import net.thauvin.erik.mobibot.commands.AbstractCommand;
-import net.thauvin.erik.mobibot.commands.AddLog;
-import net.thauvin.erik.mobibot.commands.ChannelFeed;
-import net.thauvin.erik.mobibot.commands.Cycle;
-import net.thauvin.erik.mobibot.commands.Ignore;
-import net.thauvin.erik.mobibot.commands.Info;
-import net.thauvin.erik.mobibot.commands.Me;
-import net.thauvin.erik.mobibot.commands.Modules;
-import net.thauvin.erik.mobibot.commands.Msg;
-import net.thauvin.erik.mobibot.commands.Nick;
-import net.thauvin.erik.mobibot.commands.Recap;
-import net.thauvin.erik.mobibot.commands.Say;
-import net.thauvin.erik.mobibot.commands.Users;
-import net.thauvin.erik.mobibot.commands.Versions;
-import net.thauvin.erik.mobibot.commands.links.Comment;
-import net.thauvin.erik.mobibot.commands.links.LinksMgr;
-import net.thauvin.erik.mobibot.commands.links.Posting;
-import net.thauvin.erik.mobibot.commands.links.Tags;
-import net.thauvin.erik.mobibot.commands.links.View;
-import net.thauvin.erik.mobibot.commands.tell.Tell;
-import net.thauvin.erik.mobibot.entries.EntriesMgr;
-import net.thauvin.erik.mobibot.entries.EntryLink;
-import net.thauvin.erik.mobibot.modules.AbstractModule;
-import net.thauvin.erik.mobibot.modules.Calc;
-import net.thauvin.erik.mobibot.modules.CurrencyConverter;
-import net.thauvin.erik.mobibot.modules.Dice;
-import net.thauvin.erik.mobibot.modules.GoogleSearch;
-import net.thauvin.erik.mobibot.modules.Joke;
-import net.thauvin.erik.mobibot.modules.Lookup;
-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.Twitter;
-import net.thauvin.erik.mobibot.modules.War;
-import net.thauvin.erik.mobibot.modules.Weather2;
-import net.thauvin.erik.mobibot.modules.WorldTime;
-import net.thauvin.erik.mobibot.msg.Message;
-import net.thauvin.erik.pinboard.PinboardPoster;
-import net.thauvin.erik.semver.Version;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.Configurator;
-import org.jibble.pircbot.PircBot;
-import org.jibble.pircbot.User;
-
-import java.io.BufferedOutputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Properties;
-import java.util.Timer;
-import java.util.logging.ConsoleHandler;
-import java.util.regex.Pattern;
-
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
-import static org.apache.commons.lang3.StringUtils.lowerCase;
-
-/**
- * Implements the #mobitopia bot.
- *
- * @author Erik C. Thauvin
- * @created Jan 31, 2004
- * @since 1.0
- */
-@SuppressWarnings("WeakerAccess")
-@Version(properties = "version.properties",
- className = "ReleaseInfo")
-public class Mobibot extends PircBot {
- // Logger
- private static final Logger LOGGER = LogManager.getLogger(Mobibot.class);
- // Maximum number of times the bot will try to reconnect, if disconnected
- private static final int MAX_RECONNECT = 10;
- // Timer
- private static final Timer TIMER = new Timer(true);
- // Commands and Modules
- private final Addons addons = new Addons();
- // Main channel
- private final String ircChannel;
- // IRC port
- private final int ircPort;
- // IRC server
- private final String ircServer;
- // Logger default level
- private final Level loggerLevel;
- // Log directory
- private final String logsDir;
- // Tell command
- private final Tell tell;
- // Today's date
- private final String today = Utils.today();
- // Twitter module
- private final Twitter twitter;
- // Backlogs URL
- private String backLogsUrl = "";
- // Ident message
- private String identMsg = "";
- // Ident nick
- private String identNick = "";
- // NickServ ident password
- private String identPwd = "";
- // Pinboard posts handler
- private PinboardPoster pinboard;
- // Weblog URL
- private String weblogUrl = "";
-
- /**
- * Creates a new {@link Mobibot} instance.
- *
- * @param channel The irc channel.
- * @param nickname The bot's nickname.
- * @param logsDirPath The path to the logs directory.
- * @param p The bot's properties.
- */
- public Mobibot(final String nickname, final String channel, final String logsDirPath, final Properties p) {
- super();
- System.getProperties().setProperty("sun.net.client.defaultConnectTimeout",
- String.valueOf(Constants.CONNECT_TIMEOUT));
- System.getProperties().setProperty("sun.net.client.defaultReadTimeout",
- String.valueOf(Constants.CONNECT_TIMEOUT));
-
- setName(nickname);
-
- ircServer = p.getProperty("server", Constants.DEFAULT_SERVER);
- ircPort = Utils.getIntProperty(p.getProperty("port"), Constants.DEFAULT_PORT);
- ircChannel = channel;
- logsDir = logsDirPath;
-
- // Set the logger level
- loggerLevel = LOGGER.getLevel();
-
- // Load the current entries and backlogs, if any
- try {
- LinksMgr.startup(logsDir + EntriesMgr.CURRENT_XML, logsDir + EntriesMgr.NAV_XML, ircChannel);
- LOGGER.debug("Last feed: {}", LinksMgr.getStartDate());
- } catch (Exception e) {
- LOGGER.error("An error occurred while loading the logs.", e);
- }
-
- // Initialize the bot
- setVerbose(true);
- setAutoNickChange(true);
- setLogin(p.getProperty("login", getName()));
- setVersion(ReleaseInfo.PROJECT + ' ' + ReleaseInfo.VERSION);
- // setMessageDelay(1000);
- setIdentity(p.getProperty("ident", ""), p.getProperty("ident-nick", ""), p.getProperty("ident-msg", ""));
-
- // Set the URLs
- setWeblogUrl(p.getProperty("weblog", ""));
- setBacklogsUrl(Utils.ensureDir(p.getProperty("backlogs", weblogUrl), true));
-
- // Set the pinboard authentication
- setPinboardAuth(p.getProperty("pinboard-api-token"));
-
- // Load the commands
- addons.add(new AddLog(this), p);
- addons.add(new ChannelFeed(this, getChannelName()), p);
- addons.add(new Cycle(this), p);
- addons.add(new Ignore(this), p);
- addons.add(new Info(this), p);
- addons.add(new Me(this), p);
- addons.add(new Modules(this), p);
- addons.add(new Msg(this), p);
- addons.add(new Nick(this), p);
- addons.add(new Recap(this), p);
- addons.add(new Say(this), p);
- addons.add(new Users(this), p);
- addons.add(new Versions(this), p);
-
- // Tell command
- tell = new Tell(this);
- addons.add(tell, p);
-
- // Load the links commands
- addons.add(new Comment(this), p);
- addons.add(new Posting(this), p);
- addons.add(new Tags(this), p);
- addons.add(new LinksMgr(this), p);
- addons.add(new View(this), p);
-
- // Load the modules
- addons.add(new Calc(this), p);
- addons.add(new CurrencyConverter(this), p);
- addons.add(new Dice(this), p);
- addons.add(new GoogleSearch(this), p);
- addons.add(new Joke(this), p);
- addons.add(new Lookup(this), p);
- addons.add(new Ping(this), p);
- addons.add(new RockPaperScissors(this), p);
- addons.add(new StockQuote(this), p);
- addons.add(new War(this), p);
- addons.add(new Weather2(this), p);
- addons.add(new WorldTime(this), p);
-
- // Twitter module
- twitter = new Twitter(this);
- addons.add(twitter, p);
-
- // Sort the addons
- addons.sort();
-
- // Save the entries
- LinksMgr.saveEntries(this, true);
- }
-
- /**
- * The Truth Is Out There...
- *
- * @param args The command line arguments.
- */
- @SuppressFBWarnings(
- { "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE",
- "DM_DEFAULT_ENCODING",
- "IOI_USE_OF_FILE_STREAM_CONSTRUCTORS" })
- @SuppressWarnings({ "PMD.SystemPrintln", "PMD.AvoidFileStream", "PMD.CloseResource" })
- public static void main(final String[] args) {
- // Setup the command line options
- final Options options = new Options()
- .addOption(Constants.HELP_ARG.substring(0, 1),
- Constants.HELP_ARG,
- false,
- "print this help message")
- .addOption(Constants.DEBUG_ARG.substring(0, 1), Constants.DEBUG_ARG, false,
- "print debug & logging data directly to the console")
- .addOption(Option.builder(Constants.PROPS_ARG.substring(0, 1)).hasArg()
- .argName("file")
- .desc("use " + "alternate properties file")
- .longOpt(Constants.PROPS_ARG).build())
- .addOption(Constants.VERSION_ARG.substring(0, 1),
- Constants.VERSION_ARG,
- false,
- "print version info");
-
- // Parse the command line
- final CommandLineParser parser = new DefaultParser();
- CommandLine commandLine = null;
-
- try {
- commandLine = parser.parse(options, args);
- } catch (ParseException e) {
- System.err.println("CLI Parsing failed. Reason: " + e.getMessage());
- e.printStackTrace(System.err);
- System.exit(1);
- }
-
- if (commandLine.hasOption(Constants.HELP_ARG.charAt(0))) {
- // Output the usage
- new HelpFormatter().printHelp(Mobibot.class.getName(), options);
- } else if (commandLine.hasOption(Constants.VERSION_ARG.charAt(0))) {
- System.out.println(ReleaseInfo.PROJECT + ' ' + ReleaseInfo.VERSION
- + " (" + Utils.isoLocalDate(ReleaseInfo.BUILDDATE) + ')');
- System.out.println(ReleaseInfo.WEBSITE);
- } else {
- final Properties p = new Properties();
- try (final InputStream fis = Files.newInputStream(
- Paths.get(commandLine.getOptionValue(Constants.PROPS_ARG.charAt(0), "./mobibot.properties")))) {
- // Load the properties files
- p.load(fis);
- } catch (FileNotFoundException e) {
- System.err.println("Unable to find properties file.");
- e.printStackTrace(System.err);
- System.exit(1);
- } catch (IOException e) {
- System.err.println("Unable to open properties file.");
- e.printStackTrace(System.err);
- System.exit(1);
- }
-
- final String nickname = p.getProperty("nick", lowerCase(Mobibot.class.getName()));
- final String channel = p.getProperty("channel");
- final String logsDir = Utils.ensureDir(p.getProperty("logs", "."), false);
-
- // Redirect the stdout and stderr
- if (!commandLine.hasOption(Constants.DEBUG_ARG.charAt(0))) {
- try {
- final PrintStream stdout = new PrintStream(new BufferedOutputStream(new FileOutputStream(
- logsDir + channel.substring(1) + '.' + Utils.today() + ".log", true)), true);
- System.setOut(stdout);
- } catch (IOException e) {
- System.err.println("Unable to open output (stdout) log file.");
- e.printStackTrace(System.err);
- System.exit(1);
- }
-
- try {
- final PrintStream stderr =
- new PrintStream(new BufferedOutputStream(
- new FileOutputStream(logsDir + nickname + ".err", true)), true);
- System.setErr(stderr);
- } catch (IOException e) {
- System.err.println("Unable to open error (stderr) log file.");
- e.printStackTrace(System.err);
- System.exit(1);
- }
- }
-
- // Create the bot
- final Mobibot bot = new Mobibot(nickname, channel, logsDir, p);
-
- // Connect
- bot.connect();
- }
- }
-
- /**
- * Sends an action to the current channel.
- *
- * @param action The action.
- */
- public final void action(final String action) {
- action(ircChannel, action);
- }
-
- /**
- * Sends an action to the channel.
- *
- * @param channel The channel.
- * @param action The action.
- */
- private void action(final String channel, final String action) {
- if (isNotBlank(channel) && isNotBlank(action)) {
- sendAction(channel, action);
- }
- }
-
- /**
- * Adds pin on pinboard.
- *
- * @param entry The entry to add.
- */
- public final void addPin(final EntryLink entry) {
- if (pinboard != null) {
- PinboardUtils.addPin(pinboard, ircServer, entry);
- }
- }
-
- /**
- * Connects to the server and joins the channel.
- */
- @SuppressFBWarnings({ "DM_EXIT", "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE" })
- public final void connect() {
- try {
- connect(ircServer, ircPort);
- } catch (Exception e) {
- int retries = 0;
-
- while ((retries++ < MAX_RECONNECT) && !isConnected()) {
- sleep(10);
-
- try {
- connect(ircServer, ircPort);
- } catch (Exception ex) {
- if (retries == MAX_RECONNECT) {
- LOGGER.debug("Unable to reconnect to {} after {} retries.", ircServer, MAX_RECONNECT, ex);
- e.printStackTrace(System.err);
- System.exit(1);
- }
- }
- }
- }
- identify();
- joinChannel();
- }
-
- /**
- * Deletes pin on pinboard.
- *
- * @param entry The entry to delete.
- */
- public final void deletePin(final int index, final EntryLink entry) {
- if (pinboard != null) {
- PinboardUtils.deletePin(pinboard, entry);
- }
- if (twitter.isAutoPost()) {
- twitter.removeEntry(index);
- }
- }
-
- /**
- * Returns the backlogs URL.
- *
- * @return The backlogs URL.
- */
- public final String getBacklogsUrl() {
- return backLogsUrl;
- }
-
- /**
- * Returns the current channel.
- *
- * @return The current channel.
- */
- public final String getChannel() {
- return ircChannel;
- }
-
- /**
- * Returns the current channel name.
- *
- * @return The current channel name.
- */
- @SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
- public final String getChannelName() {
- return ircChannel.substring(1);
- }
-
- /**
- * Returns the irc server.
- *
- * @return The irc server.
- */
- public final String getIrcServer() {
- return ircServer;
- }
-
- /**
- * Returns the bot's logger.
- *
- * @return The bot's logger.
- */
- public final Logger getLogger() {
- return LOGGER;
- }
-
- /**
- * Returns the log directory.
- *
- * @return the log directory.
- */
- public final String getLogsDir() {
- return logsDir;
- }
-
- /**
- * Returns the enabled modules names.
- *
- * @return The modules names.
- */
- public final List getModulesNames() {
- return addons.getModulesNames();
- }
-
- /**
- * Returns the Tell command.
- *
- * @return The tell command.
- */
- public final Tell getTell() {
- return tell;
- }
-
- /**
- * Returns the bot's timer.
- *
- * @return The timer.
- */
- public final Timer getTimer() {
- return TIMER;
- }
-
- /**
- * Get today's date for the feed.
- *
- * @return Today's date.
- */
- public String getToday() {
- return today;
- }
-
- /**
- * Returns the Twitter command.
- *
- * @return The Twitter command.
- */
- public final Twitter getTwitter() {
- return twitter;
- }
-
- /**
- * Returns the weblog URL.
- *
- * @return The weblog URL.
- */
- public final String getWeblogUrl() {
- return weblogUrl;
- }
-
- /**
- * Responds with the commands help, if any.
- *
- * @param sender The nick of the person requesting Constants.
- * @param topic The help topic.
- * @param isPrivate The private flag.
- * @return {@code true} if the topic was found, {@code false} otherwise.
- */
- private boolean helpCommands(final String sender, final String topic, final boolean isPrivate) {
- for (final AbstractCommand command : addons.getCommands()) {
- if (command.isVisible() && command.getName().startsWith(topic)) {
- return command.helpResponse(topic, sender, isOp(sender), isPrivate);
- }
- }
- return false;
- }
-
- /**
- * Responds with the default Constants.
- *
- * @param sender The nick of the person requesting Constants.
- * @param isOp The channel operator flag.
- * @param isPrivate The private flag.
- */
- public void helpDefault(final String sender, final boolean isOp, final boolean isPrivate) {
- send(sender, "Type a URL on " + ircChannel + " to post it.", isPrivate);
- send(sender, "For more information on a specific command, type:", isPrivate);
- send(sender,
- Utils.helpIndent(Utils.helpFormat("%c " + Constants.HELP_CMD + " ", getNick(), isPrivate)),
- isPrivate);
- send(sender, "The commands are:", isPrivate);
- sendList(sender, addons.getNames(), 8, isPrivate, true);
- if (isOp) {
- send(sender, "The op commands are:", isPrivate);
- sendList(sender, addons.getOps(), 8, isPrivate, true);
- }
- }
-
- /**
- * Responds with the modules help, if any.
- *
- * @param sender The nick of the person requesting Constants.
- * @param topic The help topic.
- * @param isPrivate The private flag.
- * @return {@code true} if the topic was found, {@code false} otherwise.
- */
- private boolean helpModules(final String sender, final String topic, final boolean isPrivate) {
- for (final AbstractModule module : addons.getModules()) {
- for (final String cmd : module.commands) {
- if (topic.equals(cmd)) {
- module.helpResponse(sender, isPrivate);
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Responds with the bot's Constants.
- *
- * @param sender The nick of the person who sent the private message.
- * @param topic The help topic, if any.
- * @param isPrivate The private flag.
- */
- private void helpResponse(final String sender, final String topic, final boolean isPrivate) {
- final boolean isOp = isOp(sender);
- if (StringUtils.isBlank(topic)) {
- helpDefault(sender, isOp, isPrivate);
- } else {
- // Command, Modules or Default
- if (!helpCommands(sender, topic, isPrivate) && !helpModules(sender, lowerCase(topic).trim(), isPrivate)) {
- helpDefault(sender, isOp, isPrivate);
- }
- }
- }
-
- /**
- * Identifies the bot.
- */
- private void identify() {
- // Identify with NickServ
- if (isNotBlank(identPwd)) {
- identify(identPwd);
- }
-
- // Identify with a specified nick
- if (isNotBlank(identNick) && isNotBlank(identMsg)) {
- sendMessage(identNick, identMsg);
- }
- }
-
- /**
- * Returns true
if the specified sender is an Op on the {@link #ircChannel channel}.
- *
- * @param sender The sender.
- * @return true, if the sender is an Op.
- */
- public boolean isOp(final String sender) {
- final User[] users = getUsers(ircChannel);
-
- for (final User user : users) {
- if (user.getNick().equals(sender)) {
- return user.isOp();
- }
- }
-
- return false;
- }
-
- /**
- * Joins the bot's channel.
- */
- public final void joinChannel() {
- joinChannel(ircChannel);
- twitter.notification(getName() + " " + ReleaseInfo.VERSION + " has joined " + getChannel());
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected final void onDisconnect() {
- if (isNotBlank(weblogUrl)) {
- setVersion(weblogUrl);
- }
- sleep(5);
- connect();
- }
-
- /**
- * {@inheritDoc}
- */
- @SuppressFBWarnings(value = "CC_CYCLOMATIC_COMPLEXITY",
- justification = "Working on it.")
- @Override
- protected final void onMessage(final String channel, final String sender, final String login, final String hostname,
- final String message) {
- LOGGER.debug(">>> {} : {}", sender, message);
-
- tell.send(sender, true);
-
- if (message.matches("(?i)" + Pattern.quote(getNick()) + ":.*")) { // mobibot:
- final String[] cmds = message.substring(message.indexOf(':') + 1).trim().split(" ", 2);
- final String cmd = lowerCase(cmds[0]);
-
- String args = "";
-
- if (cmds.length > 1) {
- args = cmds[1].trim();
- }
-
- if (cmd.startsWith(Constants.HELP_CMD)) { // mobibot: help
- helpResponse(sender, args, false);
- } else {
- // Commands
- for (final AbstractCommand command : addons.getCommands()) {
- if (command.isPublic() && command.getName().startsWith(cmd)) {
- command.commandResponse(sender, login, args, isOp(sender), false);
- return;
- }
- }
- // Modules
- for (final AbstractModule module : addons.getModules()) { // modules
- for (final String c : module.commands) {
- if (cmd.startsWith(c)) {
- module.commandResponse(sender, cmd, args, false);
- return;
- }
- }
- }
- }
- } else {
- // Commands, e.g.: https://www.example.com/
- for (final AbstractCommand command : addons.getCommands()) {
- if (command.matches(message)) {
- command.commandResponse(sender, login, message, isOp(sender), false);
- return;
- }
- }
- }
-
- Recap.storeRecap(sender, message, false);
- }
-
- /**
- * {@inheritDoc}
- */
- @SuppressFBWarnings(value = { "DM_EXIT", "CC_CYCLOMATIC_COMPLEXITY" },
- justification = "Yes, we want to bail out.")
- @Override
- protected final void onPrivateMessage(final String sender, final String login, final String hostname,
- final String message) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(">>> {} : {}", sender, message);
- }
-
- final String[] cmds = message.split(" ", 2);
- final String cmd = lowerCase(cmds[0]);
- String args = "";
-
- if (cmds.length > 1) {
- args = cmds[1].trim();
- }
-
- final boolean isOp = isOp(sender);
-
- if (cmd.startsWith(Constants.HELP_CMD)) { // help
- helpResponse(sender, args, true);
- } else if (isOp && "kill".equals(cmd)) { // kill
- twitter.notification(getName() + " killed by " + sender + " on " + getChannel());
- sendRawLine("QUIT : Poof!");
- System.exit(0);
- } else if (isOp && Constants.DEBUG_CMD.equals(cmd)) { // debug
- if (LOGGER.isDebugEnabled()) {
- Configurator.setLevel(LOGGER.getName(), loggerLevel);
- } else {
- Configurator.setLevel(LOGGER.getName(), Level.DEBUG);
- }
- send(sender, "Debug logging is " + (LOGGER.isDebugEnabled() ? "enabled." : "disabled."), true);
- } else if (isOp && Constants.DIE_CMD.equals(cmd)) { // die
- send(sender + " has just signed my death sentence.");
- TIMER.cancel();
- twitter.shutdown();
- twitter.notification(getName() + " stopped by " + sender + " on " + getChannel());
- sleep(3);
- quitServer("The Bot Is Out There!");
- System.exit(0);
- } else {
- for (final AbstractCommand command : addons.getCommands()) {
- if (command.getName().startsWith(cmd)) {
- command.commandResponse(sender, login, args, isOp, true);
- return;
- }
- }
- for (final AbstractModule module : addons.getModules()) {
- if (module.isPrivateMsgEnabled()) {
- for (final String c : module.commands) {
- if (cmd.equals(c)) {
- module.commandResponse(sender, cmd, args, true);
- return;
- }
- }
- }
- }
- helpDefault(sender, isOp, true);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected final void onAction(final String sender, final String login, final String hostname, final String target,
- final String action) {
- if (ircChannel.equals(target)) {
- Recap.storeRecap(sender, action, true);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onJoin(final String channel, final String sender, final String login, final String hostname) {
- tell.send(sender);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void onNickChange(final String oldNick, final String login, final String hostname, final String newNick) {
- tell.send(newNick);
- }
-
- /**
- * Sends a private message or notice.
- *
- * @param sender The channel or nick of the person who sent the message.
- * @param message The actual message.
- * @param isPrivate Set to true
if the response should be a private message, otherwise a notice is
- * sent.
- */
- public final void send(final String sender, final String message, final boolean isPrivate) {
- if (isNotBlank(message) && isNotBlank(sender)) {
- if (isPrivate) {
- LOGGER.debug("Sending message to {} : {}", sender, message);
- sendMessage(sender, message);
- } else {
- LOGGER.debug("Sending notice to {} : {}", sender, message);
- sendNotice(sender, message);
- }
- }
- }
-
- /**
- * Sends a notice to the channel.
- *
- * @param notice The notice message.
- */
- public final void send(final String notice) {
- send(getChannel(), notice, false);
-
- }
-
- /**
- * Sends a message.
- *
- * @param who The channel or nick of the person who sent the command.
- * @param message The message.
- */
- public final void send(final String who, final Message message) {
- send(message.isNotice() ? who : getChannel(), message.getMsg(), message.getColor(), message.isPrivate());
- }
-
- /**
- * Sends a message.
- *
- * @param who The channel or nick of the person who sent the command.
- * @param message The actual message.
- * @param color The message's color.
- * @param isPrivate The private flag.
- */
- public final void send(final String who, final String message, final String color, final boolean isPrivate) {
- send(who, Utils.colorize(message, color), isPrivate);
- }
-
- /**
- * Send a formatted commands/modules, etc. list.
- *
- * @param nick The nick to send the list to.
- * @param list The list to format.
- * @param size The number of items per line.
- * @param isPrivate The private flag.
- * @param isBold The bold flag
- */
- public final void sendList(final String nick,
- final List list,
- final int size,
- final boolean isPrivate,
- final boolean isBold) {
- for (int i = 0; i < list.size(); i += size) {
- send(nick, Utils.helpIndent(
- String.join(" ", list.subList(i, Math.min(list.size(), i + size))), isBold), isPrivate);
- }
- }
-
- /**
- * Sets the backlogs URL.
- *
- * @param url The backlogs URL.
- */
- final void setBacklogsUrl(final String url) {
- backLogsUrl = url;
- }
-
- /**
- * Sets the bot's identification.
- *
- * @param pwd The password for NickServ, if any.
- * @param nick The ident nick name.
- * @param msg The ident message.
- */
- final void setIdentity(final String pwd, final String nick, final String msg) {
- identPwd = pwd;
- identNick = nick;
- identMsg = msg;
- }
-
- /**
- * Sets the pinboard authentication.
- *
- * @param apiToken The API token
- */
- final void setPinboardAuth(final String apiToken) {
- if (isNotBlank(apiToken)) {
- pinboard = new PinboardPoster(apiToken);
- if (LOGGER.isDebugEnabled()) {
- final ConsoleHandler consoleHandler = new ConsoleHandler();
- consoleHandler.setLevel(java.util.logging.Level.FINE);
- pinboard.getLogger().addHandler(consoleHandler);
- pinboard.getLogger().setLevel(java.util.logging.Level.FINE);
- }
- }
- }
-
- /**
- * Sets the weblog URL.
- *
- * @param url The weblog URL.
- */
- final void setWeblogUrl(final String url) {
- weblogUrl = url;
- }
-
- /**
- * Sleeps for the specified number of seconds.
- *
- * @param secs The number of seconds to sleep for.
- */
- public final void sleep(final int secs) {
- try {
- Thread.sleep(secs * 1000L);
- } catch (InterruptedException ignore) {
- // Do nothing
- }
- }
-
- /**
- * Updates pin on pinboard.
- *
- * @param oldUrl The old pin url.
- * @param entry The entry to update.
- */
- public final void updatePin(final String oldUrl, final EntryLink entry) {
- if (pinboard != null) {
- PinboardUtils.updatePin(pinboard, ircServer, oldUrl, entry);
- }
- }
-}
diff --git a/src/main/java/net/thauvin/erik/mobibot/Mobibot.kt b/src/main/java/net/thauvin/erik/mobibot/Mobibot.kt
new file mode 100644
index 0000000..a93f2ae
--- /dev/null
+++ b/src/main/java/net/thauvin/erik/mobibot/Mobibot.kt
@@ -0,0 +1,776 @@
+/*
+ * Mobibot.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
+
+import net.thauvin.erik.mobibot.PinboardUtils.addPin
+import net.thauvin.erik.mobibot.PinboardUtils.deletePin
+import net.thauvin.erik.mobibot.PinboardUtils.updatePin
+import net.thauvin.erik.mobibot.Utils.Companion.colorize
+import net.thauvin.erik.mobibot.Utils.Companion.ensureDir
+import net.thauvin.erik.mobibot.Utils.Companion.getIntProperty
+import net.thauvin.erik.mobibot.Utils.Companion.helpFormat
+import net.thauvin.erik.mobibot.Utils.Companion.helpIndent
+import net.thauvin.erik.mobibot.Utils.Companion.isoLocalDate
+import net.thauvin.erik.mobibot.Utils.Companion.today
+import net.thauvin.erik.mobibot.commands.AddLog
+import net.thauvin.erik.mobibot.commands.ChannelFeed
+import net.thauvin.erik.mobibot.commands.Cycle
+import net.thauvin.erik.mobibot.commands.Ignore
+import net.thauvin.erik.mobibot.commands.Info
+import net.thauvin.erik.mobibot.commands.Me
+import net.thauvin.erik.mobibot.commands.Modules
+import net.thauvin.erik.mobibot.commands.Msg
+import net.thauvin.erik.mobibot.commands.Nick
+import net.thauvin.erik.mobibot.commands.Recap
+import net.thauvin.erik.mobibot.commands.Recap.Companion.storeRecap
+import net.thauvin.erik.mobibot.commands.Say
+import net.thauvin.erik.mobibot.commands.Users
+import net.thauvin.erik.mobibot.commands.Versions
+import net.thauvin.erik.mobibot.commands.links.Comment
+import net.thauvin.erik.mobibot.commands.links.LinksMgr
+import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.saveEntries
+import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.startDate
+import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.startup
+import net.thauvin.erik.mobibot.commands.links.Posting
+import net.thauvin.erik.mobibot.commands.links.Tags
+import net.thauvin.erik.mobibot.commands.links.View
+import net.thauvin.erik.mobibot.commands.tell.Tell
+import net.thauvin.erik.mobibot.entries.EntriesMgr
+import net.thauvin.erik.mobibot.entries.EntryLink
+import net.thauvin.erik.mobibot.modules.Calc
+import net.thauvin.erik.mobibot.modules.CurrencyConverter
+import net.thauvin.erik.mobibot.modules.Dice
+import net.thauvin.erik.mobibot.modules.GoogleSearch
+import net.thauvin.erik.mobibot.modules.Joke
+import net.thauvin.erik.mobibot.modules.Lookup
+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.Twitter
+import net.thauvin.erik.mobibot.modules.War
+import net.thauvin.erik.mobibot.modules.Weather2
+import net.thauvin.erik.mobibot.modules.WorldTime
+import net.thauvin.erik.mobibot.msg.Message
+import net.thauvin.erik.pinboard.PinboardPoster
+import net.thauvin.erik.semver.Version
+import org.apache.commons.cli.CommandLine
+import org.apache.commons.cli.CommandLineParser
+import org.apache.commons.cli.DefaultParser
+import org.apache.commons.cli.HelpFormatter
+import org.apache.commons.cli.Option
+import org.apache.commons.cli.Options
+import org.apache.commons.cli.ParseException
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.LogManager
+import org.apache.logging.log4j.Logger
+import org.apache.logging.log4j.core.config.Configurator
+import org.jibble.pircbot.PircBot
+import java.io.BufferedOutputStream
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.PrintStream
+import java.lang.String.join
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.util.*
+import java.util.logging.ConsoleHandler
+import java.util.regex.Pattern
+import kotlin.system.exitProcess
+
+/**
+ * Implements the #mobitopia bot.
+ */
+@Version(properties = "version.properties", className = "ReleaseInfo", template = "ReleaseInfo.mustache", type = "kt")
+class Mobibot(nickname: String, channel: String, logsDirPath: String, p: Properties) : PircBot() {
+ // Commands and Modules
+ private val addons = Addons()
+
+ /** Main channel. */
+ val channel: String
+
+ // IRC port
+ private val ircPort: Int
+
+ /** IRC server. */
+ val ircServer: String
+
+ /** Logger. */
+ val logger: Logger = LogManager.getLogger(Mobibot::class.java)
+
+ // Logger default level
+ private val loggerLevel: Level
+
+ /** Log directory. */
+ val logsDir: String
+
+ // Pinboard posts handler
+ private val pinboard: PinboardPoster = PinboardPoster()
+
+ /** Tell command. */
+ val tell: Tell
+
+ /** Today's date. */
+ val today = today()
+
+ /** Twitter module. */
+ val twitter: Twitter
+
+ /** The backlogs URL. */
+ var backlogsUrl = ""
+
+ // Ident message
+ private var identMsg = ""
+
+ // Ident nick
+ private var identNick = ""
+
+ // NickServ ident password
+ private var identPwd = ""
+
+ // Is pinboard enabled?
+ private var isPinboardEnabled = false
+
+ /** Timer. */
+ val timer = Timer(true)
+
+ /** Weblog URL */
+ var weblogUrl = ""
+
+ /** The current channel name. */
+ private val channelName: String
+ get() = channel.substring(1)
+
+ /** The enabled modules names. */
+ val modulesNames: List
+ get() = addons.modulesNames
+
+ /**
+ * Sends an action to the current channel.
+ */
+ fun action(action: String) {
+ action(channel, action)
+ }
+
+ /**
+ * Sends an action to the channel.
+ */
+ private fun action(channel: String, action: String) {
+ if (channel.isNotBlank() && action.isNotBlank()) {
+ sendAction(channel, action)
+ }
+ }
+
+ /**
+ * Adds pin on pinboard.
+ */
+ fun addPin(entry: EntryLink) {
+ if (isPinboardEnabled) {
+ addPin(pinboard, ircServer, entry)
+ }
+ }
+
+ /**
+ * Connects to the server and joins the channel.
+ */
+ fun connect() {
+ try {
+ connect(ircServer, ircPort)
+ } catch (e: Exception) {
+ var retries = 0
+ while (retries++ < MAX_RECONNECT && !isConnected) {
+ sleep(10)
+ try {
+ connect(ircServer, ircPort)
+ } catch (ex: Exception) {
+ if (retries == MAX_RECONNECT) {
+ logger.debug("Unable to reconnect to $ircServer, after $MAX_RECONNECT retries.", ex)
+ e.printStackTrace(System.err)
+ exitProcess(1)
+ }
+ }
+ }
+ }
+ identify()
+ joinChannel()
+ }
+
+ /**
+ * Deletes pin on pinboard.
+ */
+ fun deletePin(index: Int, entry: EntryLink) {
+ if (isPinboardEnabled) {
+ deletePin(pinboard, entry)
+ }
+ if (twitter.isAutoPost) {
+ twitter.removeEntry(index)
+ }
+ }
+
+ /**
+ * Responds with the commands help, if any.
+ */
+ private fun helpCommands(sender: String, topic: String, isPrivate: Boolean): Boolean {
+ for (command in addons.commands) {
+ if (command.isVisible && command.name.startsWith(topic)) {
+ return command.helpResponse(topic, sender, isOp(sender), isPrivate)
+ }
+ }
+ return false
+ }
+
+ /**
+ * Responds with the default help.
+ */
+ fun helpDefault(sender: String, isOp: Boolean, isPrivate: Boolean) {
+ send(sender, "Type a URL on $channel to post it.", isPrivate)
+ send(sender, "For more information on a specific command, type:", isPrivate)
+ send(
+ sender,
+ helpIndent(helpFormat("""%c ${Constants.HELP_CMD} """, nick, isPrivate)),
+ isPrivate
+ )
+ send(sender, "The commands are:", isPrivate)
+ sendList(sender, addons.names, 8, isPrivate, true)
+ if (isOp) {
+ send(sender, "The op commands are:", isPrivate)
+ sendList(sender, addons.ops, 8, isPrivate, true)
+ }
+ }
+
+ /**
+ * Responds with the modules help, if any.
+ */
+ private fun helpModules(sender: String, topic: String, isPrivate: Boolean): Boolean {
+ for (module in addons.modules) {
+ for (cmd in module.commands) {
+ if (topic == cmd) {
+ module.helpResponse(sender, isPrivate)
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * Responds with the default, commands or modules help.
+ */
+ private fun helpResponse(sender: String, topic: String, isPrivate: Boolean) {
+ val isOp = isOp(sender)
+ if (topic.isBlank()) {
+ helpDefault(sender, isOp, isPrivate)
+ } else {
+ // Command, Modules or Default
+ if (!helpCommands(sender, topic, isPrivate) && !helpModules(
+ sender,
+ topic.toLowerCase().trim(),
+ isPrivate
+ )
+ ) {
+ helpDefault(sender, isOp, isPrivate)
+ }
+ }
+ }
+
+ /**
+ * Identifies the bot.
+ */
+ private fun identify() {
+ // Identify with NickServ
+ if (identPwd.isNotBlank()) {
+ identify(identPwd)
+ }
+ // Identify with a specified nick
+ if (identNick.isNotBlank() && identMsg.isNotBlank()) {
+ sendMessage(identNick, identMsg)
+ }
+ }
+
+ /**
+ * Returns {@code true} if the specified sender is an Op on the [channel][.ircChannel].
+ */
+ fun isOp(sender: String): Boolean {
+ for (user in getUsers(channel)) {
+ if (user.nick == sender) {
+ return user.isOp
+ }
+ }
+ return false
+ }
+
+ /**
+ * Joins the bot's channel.
+ */
+ private fun joinChannel() {
+ joinChannel(channel)
+ twitter.notification("$name ${ReleaseInfo.VERSION} has joined $channel")
+ }
+
+ override fun onDisconnect() {
+ if (weblogUrl.isNotBlank()) {
+ version = weblogUrl
+ }
+ sleep(5)
+ connect()
+ }
+
+ override fun onMessage(
+ channel: String,
+ sender: String,
+ login: String,
+ hostname: String,
+ message: String
+ ) {
+ logger.debug(">>> $sender: $message")
+ tell.send(sender, true)
+ if (message.matches("(?i)${Pattern.quote(nick)}:.*".toRegex())) { // mobibot:
+ val cmds = message.substring(message.indexOf(':') + 1).trim().split(" ".toRegex(), 2).toTypedArray()
+ val cmd = cmds[0].toLowerCase()
+ val args = if (cmds.size > 1) {
+ cmds[1].trim()
+ } else ""
+ if (cmd.startsWith(Constants.HELP_CMD)) { // mobibot: help
+ helpResponse(sender, args, false)
+ } else {
+ // Commands
+ for (command in addons.commands) {
+ if (command.isPublic && command.name.startsWith(cmd)) {
+ command.commandResponse(sender, login, args, isOp(sender), false)
+ return
+ }
+ }
+ // Modules
+ for (module in addons.modules) { // modules
+ for (c in module.commands) {
+ if (cmd.startsWith(c)) {
+ module.commandResponse(sender, cmd, args, false)
+ return
+ }
+ }
+ }
+ }
+ } else {
+ // Commands, e.g.: https://www.example.com/
+ for (command in addons.commands) {
+ if (command.matches(message)) {
+ command.commandResponse(sender, login, message, isOp(sender), false)
+ return
+ }
+ }
+ }
+ storeRecap(sender, message, false)
+ }
+
+ override fun onPrivateMessage(
+ sender: String,
+ login: String,
+ hostname: String,
+ message: String
+ ) {
+ if (logger.isDebugEnabled) {
+ logger.debug(">>> $sender : $message")
+ }
+ val cmds = message.split(" ".toRegex(), 2).toTypedArray()
+ val cmd = cmds[0].toLowerCase()
+ val args = if (cmds.size > 1) {
+ cmds[1].trim()
+ } else ""
+ val isOp = isOp(sender)
+ if (cmd.startsWith(Constants.HELP_CMD)) { // help
+ helpResponse(sender, args, true)
+ } else if (isOp && "kill" == cmd) { // kill
+ twitter.notification("$name killed by $sender on $channel")
+ sendRawLine("QUIT : Poof!")
+ exitProcess(0)
+ } else if (isOp && Constants.DEBUG_CMD == cmd) { // debug
+ if (logger.isDebugEnabled) {
+ Configurator.setLevel(logger.name, loggerLevel)
+ } else {
+ Configurator.setLevel(logger.name, Level.DEBUG)
+ }
+ send(sender, "Debug logging is " + if (logger.isDebugEnabled) "enabled." else "disabled.", true)
+ } else if (isOp && Constants.DIE_CMD == cmd) { // die
+ send("$sender has just signed my death sentence.")
+ timer.cancel()
+ twitter.shutdown()
+ twitter.notification("$name stopped by $sender on $channel")
+ sleep(3)
+ quitServer("The Bot Is Out There!")
+ exitProcess(0)
+ } else {
+ for (command in addons.commands) {
+ if (command.name.startsWith(cmd)) {
+ command.commandResponse(sender, login, args, isOp, true)
+ return
+ }
+ }
+ for (module in addons.modules) {
+ if (module.isPrivateMsgEnabled) {
+ for (c in module.commands) {
+ if (cmd == c) {
+ module.commandResponse(sender, cmd, args, true)
+ return
+ }
+ }
+ }
+ }
+ helpDefault(sender, isOp, true)
+ }
+ }
+
+ override fun onAction(sender: String, login: String, hostname: String, target: String, action: String) {
+ if (channel == target) {
+ storeRecap(sender, action, true)
+ }
+ }
+
+ override fun onJoin(channel: String, sender: String, login: String, hostname: String) {
+ tell.send(sender)
+ }
+
+ override fun onNickChange(oldNick: String, login: String, hostname: String, newNick: String) {
+ tell.send(newNick)
+ }
+
+ /**
+ * Sends a private message or notice.
+ */
+ fun send(sender: String, message: String?, isPrivate: Boolean) {
+ if (message != null && sender.isNotBlank()) {
+ if (isPrivate) {
+ logger.debug("Sending message to $sender : $message")
+ sendMessage(sender, message)
+ } else {
+ logger.debug("Sending notice to $sender: $message")
+ sendNotice(sender, message)
+ }
+ }
+ }
+
+ /**
+ * Sends a notice to the channel.
+ */
+ fun send(notice: String?) {
+ if (notice != null) send(channel, notice, false)
+ }
+
+ /**
+ * Sends a message.
+ */
+ fun send(who: String, message: Message) {
+ send(if (message.isNotice) who else channel, message.msg, message.color, message.isPrivate)
+ }
+
+ /**
+ * Sends a message.
+ */
+ fun send(who: String, message: String, color: String, isPrivate: Boolean) {
+ send(who, colorize(message, color), isPrivate)
+ }
+
+ /**
+ * Send a formatted commands/modules, etc. list.
+ */
+ fun sendList(
+ nick: String,
+ list: List,
+ size: Int,
+ isPrivate: Boolean,
+ isBold: Boolean
+ ) {
+ var i = 0
+ while (i < list.size) {
+ send(
+ nick,
+ helpIndent(join(" ", list.subList(i, list.size.coerceAtMost(i + size))), isBold),
+ isPrivate
+ )
+ i += size
+ }
+ }
+
+ /**
+ * Sets the bot's identification.
+ */
+ private fun setIdentity(pwd: String, nick: String, msg: String) {
+ identPwd = pwd
+ identNick = nick
+ identMsg = msg
+ }
+
+ /**
+ * Sets the pinboard authentication.
+ */
+ private fun setPinboardAuth(apiToken: String) {
+ if (apiToken.isNotBlank()) {
+ pinboard.apiToken = apiToken
+ isPinboardEnabled = true
+ if (logger.isDebugEnabled) {
+ val consoleHandler = ConsoleHandler()
+ consoleHandler.level = java.util.logging.Level.FINE
+ pinboard.logger.addHandler(consoleHandler)
+ pinboard.logger.level = java.util.logging.Level.FINE
+ }
+ }
+ }
+
+ /**
+ * Sleeps for the specified number of seconds.
+ */
+ fun sleep(secs: Int) {
+ try {
+ Thread.sleep(secs * 1000L)
+ } catch (ignore: InterruptedException) {
+ // Do nothing
+ }
+ }
+
+ /**
+ * Updates pin on pinboard.
+ */
+ fun updatePin(oldUrl: String, entry: EntryLink) {
+ if (isPinboardEnabled) {
+ updatePin(pinboard, ircServer, oldUrl, entry)
+ }
+ }
+
+ companion object {
+ // Maximum number of times the bot will try to reconnect, if disconnected
+ private const val MAX_RECONNECT = 10
+
+ /**
+ * The Truth is Out There!
+ */
+ @JvmStatic
+ fun main(args: Array) {
+ // Setup the command line options
+ val options = Options()
+ .addOption(
+ Constants.HELP_ARG.substring(0, 1),
+ Constants.HELP_ARG,
+ false,
+ "print this help message"
+ )
+ .addOption(
+ Constants.DEBUG_ARG.substring(0, 1), Constants.DEBUG_ARG, false,
+ "print debug & logging data directly to the console"
+ )
+ .addOption(
+ Option.builder(Constants.PROPS_ARG.substring(0, 1)).hasArg()
+ .argName("file")
+ .desc("use " + "alternate properties file")
+ .longOpt(Constants.PROPS_ARG).build()
+ )
+ .addOption(
+ Constants.VERSION_ARG.substring(0, 1),
+ Constants.VERSION_ARG,
+ false,
+ "print version info"
+ )
+
+ // Parse the command line
+ val parser: CommandLineParser = DefaultParser()
+ val commandLine: CommandLine
+ try {
+ commandLine = parser.parse(options, args)
+ } catch (e: ParseException) {
+ System.err.println("CLI Parsing failed. Reason: ${e.message}")
+ e.printStackTrace(System.err)
+ exitProcess(1)
+ }
+ when {
+ commandLine.hasOption(Constants.HELP_ARG[0]) -> {
+ // Output the usage
+ HelpFormatter().printHelp(Mobibot::class.java.name, options)
+ }
+ commandLine.hasOption(Constants.VERSION_ARG[0]) -> {
+ println("${ReleaseInfo.PROJECT} ${ReleaseInfo.VERSION} (${isoLocalDate(ReleaseInfo.BUILDDATE)})")
+ println(ReleaseInfo.WEBSITE)
+ }
+ else -> {
+ val p = Properties()
+ try {
+ Files.newInputStream(
+ Paths.get(commandLine.getOptionValue(Constants.PROPS_ARG[0], "./mobibot.properties"))
+ ).use { fis ->
+ // Load the properties files
+ p.load(fis)
+ }
+ } catch (e: FileNotFoundException) {
+ System.err.println("Unable to find properties file.")
+ e.printStackTrace(System.err)
+ exitProcess(1)
+ } catch (e: IOException) {
+ System.err.println("Unable to open properties file.")
+ e.printStackTrace(System.err)
+ exitProcess(1)
+ }
+ val nickname = p.getProperty("nick", Mobibot::class.java.name.toLowerCase())
+ val channel = p.getProperty("channel")
+ val logsDir = ensureDir(p.getProperty("logs", "."), false)
+
+ // Redirect the stdout and stderr
+ if (!commandLine.hasOption(Constants.DEBUG_ARG[0])) {
+ try {
+ val stdout = PrintStream(
+ BufferedOutputStream(
+ FileOutputStream(
+ logsDir + channel.substring(1) + '.' + today() + ".log", true
+ )
+ ), true
+ )
+ System.setOut(stdout)
+ } catch (e: IOException) {
+ System.err.println("Unable to open output (stdout) log file.")
+ e.printStackTrace(System.err)
+ exitProcess(1)
+ }
+ try {
+ val stderr = PrintStream(
+ BufferedOutputStream(
+ FileOutputStream("$logsDir$nickname.err", true)
+ ), true
+ )
+ System.setErr(stderr)
+ } catch (e: IOException) {
+ System.err.println("Unable to open error (stderr) log file.")
+ e.printStackTrace(System.err)
+ exitProcess(1)
+ }
+ }
+
+ // Create the bot
+ val bot = Mobibot(nickname, channel, logsDir, p)
+
+ // Connect
+ bot.connect()
+ }
+ }
+ }
+ }
+
+ /**
+ * Initialize the bot.
+ */
+ init {
+ System.getProperties().setProperty(
+ "sun.net.client.defaultConnectTimeout",
+ java.lang.String.valueOf(Constants.CONNECT_TIMEOUT)
+ )
+ System.getProperties().setProperty(
+ "sun.net.client.defaultReadTimeout",
+ java.lang.String.valueOf(Constants.CONNECT_TIMEOUT)
+ )
+ name = nickname
+ ircServer = p.getProperty("server", Constants.DEFAULT_SERVER)
+ ircPort = getIntProperty(p.getProperty("port"), Constants.DEFAULT_PORT)
+ this.channel = channel
+ logsDir = logsDirPath
+
+ // Set the logger level
+ loggerLevel = logger.level
+
+ // Load the current entries and backlogs, if any
+ try {
+ startup(logsDir + EntriesMgr.CURRENT_XML, logsDir + EntriesMgr.NAV_XML, this.channel)
+ logger.debug("Last feed: $startDate")
+ } catch (e: Exception) {
+ logger.error("An error occurred while loading the logs.", e)
+ }
+
+ // Initialize the bot
+ setVerbose(true)
+ setAutoNickChange(true)
+ login = p.getProperty("login", name)
+ version = ReleaseInfo.PROJECT + ' ' + ReleaseInfo.VERSION
+ // setMessageDelay(1000);
+ setIdentity(p.getProperty("ident", ""), p.getProperty("ident-nick", ""), p.getProperty("ident-msg", ""))
+
+ // Set the URLs
+ weblogUrl = p.getProperty("weblog", "")
+ backlogsUrl = ensureDir(p.getProperty("backlogs", weblogUrl), true)
+
+ // Set the pinboard authentication
+ setPinboardAuth(p.getProperty("pinboard-api-token"))
+
+ // Load the commands
+ addons.add(AddLog(this), p)
+ addons.add(ChannelFeed(this, channelName), p)
+ addons.add(Cycle(this), p)
+ addons.add(Ignore(this), p)
+ addons.add(Info(this), p)
+ addons.add(Me(this), p)
+ addons.add(Modules(this), p)
+ addons.add(Msg(this), p)
+ addons.add(Nick(this), p)
+ addons.add(Recap(this), p)
+ addons.add(Say(this), p)
+ addons.add(Users(this), p)
+ addons.add(Versions(this), p)
+
+ // Tell command
+ tell = Tell(this)
+ addons.add(tell, p)
+
+ // Load the links commands
+ addons.add(Comment(this), p)
+ addons.add(Posting(this), p)
+ addons.add(Tags(this), p)
+ addons.add(LinksMgr(this), p)
+ addons.add(View(this), p)
+
+ // Load the modules
+ addons.add(Calc(this), p)
+ addons.add(CurrencyConverter(this), p)
+ addons.add(Dice(this), p)
+ addons.add(GoogleSearch(this), p)
+ addons.add(Joke(this), p)
+ addons.add(Lookup(this), p)
+ addons.add(Ping(this), p)
+ addons.add(RockPaperScissors(this), p)
+ addons.add(StockQuote(this), p)
+ addons.add(War(this), p)
+ addons.add(Weather2(this), p)
+ addons.add(WorldTime(this), p)
+
+ // Twitter module
+ twitter = Twitter(this)
+ addons.add(twitter, p)
+
+ // Sort the addons
+ addons.sort()
+
+ // Save the entries
+ saveEntries(this, true)
+ }
+}
diff --git a/src/main/java/net/thauvin/erik/mobibot/commands/links/Posting.kt b/src/main/java/net/thauvin/erik/mobibot/commands/links/Posting.kt
index fcdc20e..474108f 100644
--- a/src/main/java/net/thauvin/erik/mobibot/commands/links/Posting.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/commands/links/Posting.kt
@@ -134,7 +134,7 @@ class Posting(bot: Mobibot) : AbstractCommand(bot) {
}
private fun removeEntry(sender: String, login: String, isOp: Boolean, index: Int) {
- val entry: EntryLink = LinksMgr.entries[index]
+ val entry: EntryLink = entries[index]
if (entry.login == login || isOp) {
bot.deletePin(index, entry)
entries.removeAt(index)
diff --git a/src/main/java/net/thauvin/erik/mobibot/commands/tell/Tell.kt b/src/main/java/net/thauvin/erik/mobibot/commands/tell/Tell.kt
index 0b485eb..280d9aa 100644
--- a/src/main/java/net/thauvin/erik/mobibot/commands/tell/Tell.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/commands/tell/Tell.kt
@@ -41,7 +41,6 @@ import net.thauvin.erik.mobibot.Utils.Companion.reverseColor
import net.thauvin.erik.mobibot.Utils.Companion.utcDateTime
import net.thauvin.erik.mobibot.commands.AbstractCommand
import net.thauvin.erik.mobibot.commands.links.View
-import org.apache.commons.lang3.StringUtils
import java.util.concurrent.CopyOnWriteArrayList
/**
@@ -143,7 +142,7 @@ class Tell(bot: Mobibot) : AbstractCommand(bot) {
isPrivate: Boolean
) {
if (isEnabled()) {
- if (StringUtils.isBlank(args)) {
+ if (args.isBlank()) {
helpResponse(args, sender, isOp, isPrivate)
} else if (args.startsWith(View.VIEW_CMD)) {
if (bot.isOp(sender) && "${View.VIEW_CMD} $TELL_ALL_KEYWORD" == args) {
@@ -178,12 +177,13 @@ class Tell(bot: Mobibot) : AbstractCommand(bot) {
// New message.
private fun newMessage(sender: String, args: String, isOp: Boolean, isPrivate: Boolean) {
val split = args.split(" ".toRegex(), 2).toTypedArray()
- if (split.size == 2 && StringUtils.isNotBlank(split[1]) && split[1].contains(" ")) {
+ if (split.size == 2 && split[1].isNotBlank() && split[1].contains(" ")) {
if (messages.size < maxSize) {
val message = TellMessage(sender, split[0], split[1].trim())
messages.add(message)
save()
- bot.send(sender, "Message [ID ${message.id}] was queued for ${bold(message.recipient)}", isPrivate
+ bot.send(
+ sender, "Message [ID ${message.id}] was queued for ${bold(message.recipient)}", isPrivate
)
} else {
bot.send(sender, "Sorry, the messages queue is currently full.", isPrivate)
diff --git a/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessage.kt b/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessage.kt
index 4dd3f40..09dc5da 100644
--- a/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessage.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessage.kt
@@ -74,12 +74,12 @@ class TellMessage internal constructor(
* Returns {@code true) if the message was received.
*/
var isReceived = false
- set(value) {
- if (value) {
- receptionDate = LocalDateTime.now(Clock.systemUTC())
- }
- field = value
- }
+ set(value) {
+ if (value) {
+ receptionDate = LocalDateTime.now(Clock.systemUTC())
+ }
+ field = value
+ }
/**
* Return the message creating date.
diff --git a/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgr.kt b/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgr.kt
index 44f487d..3f8710a 100644
--- a/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgr.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/commands/tell/TellMessagesMgr.kt
@@ -32,18 +32,17 @@
package net.thauvin.erik.mobibot.commands.tell
import org.apache.logging.log4j.Logger
-import java.time.LocalDateTime
-import java.io.ObjectInputStream
import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
import java.io.FileNotFoundException
import java.io.IOException
-import java.lang.ClassNotFoundException
-import java.io.BufferedOutputStream
+import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.nio.file.Files
import java.nio.file.Paths
import java.time.Clock
-import java.util.ArrayList
+import java.time.LocalDateTime
+import java.util.*
/**
* The Tell Messages Manager.
diff --git a/src/main/java/net/thauvin/erik/mobibot/entries/EntriesMgr.kt b/src/main/java/net/thauvin/erik/mobibot/entries/EntriesMgr.kt
index a526542..f8e9a43 100644
--- a/src/main/java/net/thauvin/erik/mobibot/entries/EntriesMgr.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/entries/EntriesMgr.kt
@@ -41,7 +41,6 @@ import com.rometools.rome.io.SyndFeedInput
import com.rometools.rome.io.SyndFeedOutput
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils.Companion.isoLocalDate
-import org.apache.commons.lang3.StringUtils
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStreamWriter
@@ -143,7 +142,7 @@ class EntriesMgr private constructor() {
if (bot.logger.isDebugEnabled) {
bot.logger.debug("Saving the feeds...")
}
- if (StringUtils.isNotBlank(bot.logsDir) && StringUtils.isNotBlank(bot.weblogUrl)) {
+ if (bot.logsDir.isNotBlank() && bot.weblogUrl.isNotBlank()) {
try {
val output = SyndFeedOutput()
var rss: SyndFeed = SyndFeedImpl()
@@ -208,7 +207,7 @@ class EntriesMgr private constructor() {
), StandardCharsets.UTF_8
).use { fw -> output.output(rss, fw) }
if (isDayBackup) {
- if (StringUtils.isNotBlank(bot.backlogsUrl)) {
+ if (bot.backlogsUrl.isNotBlank()) {
if (!history.contains(bot.today)) {
history.add(bot.today)
while (history.size > MAX_BACKLOGS) {
diff --git a/src/main/java/net/thauvin/erik/mobibot/entries/EntryLink.kt b/src/main/java/net/thauvin/erik/mobibot/entries/EntryLink.kt
index 9601d17..c041249 100644
--- a/src/main/java/net/thauvin/erik/mobibot/entries/EntryLink.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/entries/EntryLink.kt
@@ -53,7 +53,7 @@ class EntryLink : Serializable {
var channel: String
// Creation date
- var date = Calendar.getInstance().time
+ var date: Date = Calendar.getInstance().time
// Link's URL
var link: String
@@ -178,12 +178,12 @@ class EntryLink : Serializable {
/**
* Sets the tags.
*/
- fun setTags(tags: List) {
- if (!tags.isEmpty()) {
+ private fun setTags(tags: List) {
+ if (tags.isNotEmpty()) {
var category: SyndCategoryImpl
for (tag in tags) {
- if (StringUtils.isNoneBlank(tag)) {
- val t = StringUtils.lowerCase(tag)
+ if (!tag.isNullOrBlank()) {
+ val t = tag.toLowerCase()
val mod = t[0]
if (mod == '-') {
// Don't remove the channel tag
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/AbstractModule.kt b/src/main/java/net/thauvin/erik/mobibot/modules/AbstractModule.kt
index dd40a8c..f4f36a7 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/AbstractModule.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/AbstractModule.kt
@@ -33,7 +33,6 @@ package net.thauvin.erik.mobibot.modules
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
-import org.apache.commons.lang3.StringUtils
import java.util.*
import java.util.concurrent.ConcurrentHashMap
@@ -113,7 +112,7 @@ abstract class AbstractModule(val bot: Mobibot) {
open val isValidProperties: Boolean
get() {
for (s in propertyKeys) {
- if (StringUtils.isBlank(properties[s])) {
+ if (properties[s].isNullOrBlank()) {
return false
}
}
@@ -124,7 +123,7 @@ abstract class AbstractModule(val bot: Mobibot) {
* Sets a property key and value.
*/
fun setProperty(key: String, value: String) {
- if (StringUtils.isNotBlank(key)) {
+ if (key.isNotBlank()) {
properties[key] = value
}
}
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/Calc.kt b/src/main/java/net/thauvin/erik/mobibot/modules/Calc.kt
index 22a6c7e..b1ea6a2 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/Calc.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/Calc.kt
@@ -34,7 +34,6 @@ package net.thauvin.erik.mobibot.modules
import net.objecthunter.exp4j.ExpressionBuilder
import net.thauvin.erik.mobibot.Mobibot
import net.thauvin.erik.mobibot.Utils
-import org.apache.commons.lang3.StringUtils
import java.text.DecimalFormat
/**
@@ -47,7 +46,7 @@ class Calc(bot: Mobibot) : AbstractModule(bot) {
args: String,
isPrivate: Boolean
) {
- if (StringUtils.isNotBlank(args)) {
+ if (args.isNotBlank()) {
bot.send(calc(args))
} else {
helpResponse(sender, isPrivate)
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt b/src/main/java/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt
index c72e73d..487981c 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/CurrencyConverter.kt
@@ -157,8 +157,8 @@ class CurrencyConverter(bot: Mobibot) : ThreadedModule(bot) {
if (cmds[3] == cmds[1] || "0" == cmds[0]) {
PublicMessage("You're kidding, right?")
} else {
- val to = StringUtils.upperCase(cmds[1])
- val from = StringUtils.upperCase(cmds[3])
+ val to = cmds[1].toUpperCase()
+ val from = cmds[3].toUpperCase()
if (EXCHANGE_RATES.containsKey(to) && EXCHANGE_RATES.containsKey(from)) {
try {
val amt = cmds[0].replace(",", "").toDouble()
@@ -166,10 +166,10 @@ class CurrencyConverter(bot: Mobibot) : ThreadedModule(bot) {
val doubleTo = EXCHANGE_RATES[from]!!.toDouble()
PublicMessage(
NumberFormat.getCurrencyInstance(Constants.LOCALE).format(amt).substring(1)
- + " ${StringUtils.upperCase(cmds[1])} = "
+ + " ${cmds[1].toUpperCase()} = "
+ NumberFormat.getCurrencyInstance(Constants.LOCALE)
.format(amt * doubleTo / doubleFrom).substring(1)
- + " ${StringUtils.upperCase(cmds[3])}"
+ + " ${cmds[3].toUpperCase()}"
)
} catch (e: NumberFormatException) {
ErrorMessage("Let's try with some real numbers next time, okay?")
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/GoogleSearch.kt b/src/main/java/net/thauvin/erik/mobibot/modules/GoogleSearch.kt
index 891a6f2..b9c78d0 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/GoogleSearch.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/GoogleSearch.kt
@@ -52,7 +52,7 @@ class GoogleSearch(bot: Mobibot) : ThreadedModule(bot) {
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
with(bot) {
- if (StringUtils.isNotBlank(args)) {
+ if (args.isNotBlank()) {
try {
val results = searchGoogle(
args, properties[GOOGLE_API_KEY_PROP],
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/Joke.kt b/src/main/java/net/thauvin/erik/mobibot/modules/Joke.kt
index 722ab75..26383e2 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/Joke.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/Joke.kt
@@ -56,11 +56,13 @@ class Joke(bot: Mobibot) : ThreadedModule(bot) {
/**
* Returns a random joke from [The Internet Chuck Norris Database](http://www.icndb.com/).
*/
- override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) = try {
- bot.send(Utils.cyan(randomJoke().msg))
- } catch (e: ModuleException) {
- bot.logger.warn(e.debugMessage, e)
- bot.send(sender, e.message, isPrivate)
+ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
+ try {
+ bot.send(Utils.cyan(randomJoke().msg))
+ } catch (e: ModuleException) {
+ bot.logger.warn(e.debugMessage, e)
+ bot.send(sender, e.message, isPrivate)
+ }
}
companion object {
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/StockQuote.kt b/src/main/java/net/thauvin/erik/mobibot/modules/StockQuote.kt
index 434ca54..702077a 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/StockQuote.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/StockQuote.kt
@@ -37,7 +37,6 @@ import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
import net.thauvin.erik.mobibot.msg.PublicMessage
-import org.apache.commons.lang3.StringUtils
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
@@ -53,7 +52,7 @@ class StockQuote(bot: Mobibot) : ThreadedModule(bot) {
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
with(bot) {
- if (StringUtils.isNotBlank(args)) {
+ if (args.isNotBlank()) {
try {
val messages = getQuote(args, properties[ALPHAVANTAGE_API_KEY_PROP])
for (msg in messages) {
@@ -88,7 +87,7 @@ class StockQuote(bot: Mobibot) : ThreadedModule(bot) {
@Throws(ModuleException::class)
private fun getJsonResponse(response: String, debugMessage: String): JSONObject {
- return if (StringUtils.isNotBlank(response)) {
+ return if (response.isNotBlank()) {
val json = JSONObject(response)
try {
val info = json.getString("Information")
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/Twitter.kt b/src/main/java/net/thauvin/erik/mobibot/modules/Twitter.kt
index 392cf85..ef648c8 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/Twitter.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/Twitter.kt
@@ -39,7 +39,6 @@ import net.thauvin.erik.mobibot.commands.links.LinksMgr
import net.thauvin.erik.mobibot.entries.EntriesUtils
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
-import org.apache.commons.lang3.StringUtils
import twitter4j.TwitterException
import twitter4j.TwitterFactory
import twitter4j.conf.ConfigurationBuilder
@@ -88,7 +87,7 @@ class Twitter(bot: Mobibot) : ThreadedModule(bot) {
*/
fun notification(msg: String) {
with(bot) {
- if (isEnabled && StringUtils.isNotBlank(handle)) {
+ if (isEnabled && !handle.isNullOrBlank()) {
Thread {
try {
post(message = msg, isDm = true)
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/Weather2.kt b/src/main/java/net/thauvin/erik/mobibot/modules/Weather2.kt
index 9fc9847..023a4d9 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/Weather2.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/Weather2.kt
@@ -41,7 +41,6 @@ import net.thauvin.erik.mobibot.msg.ErrorMessage
import net.thauvin.erik.mobibot.msg.Message
import net.thauvin.erik.mobibot.msg.NoticeMessage
import net.thauvin.erik.mobibot.msg.PublicMessage
-import org.apache.commons.lang3.StringUtils
import org.jibble.pircbot.Colors
import java.util.*
import kotlin.math.roundToInt
@@ -54,7 +53,7 @@ class Weather2(bot: Mobibot) : ThreadedModule(bot) {
* Fetches the weather data from a specific city.
*/
override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean) {
- if (StringUtils.isNotBlank(args)) {
+ if (args.isNotBlank()) {
try {
val messages = getWeather(args, properties[OWM_API_KEY_PROP])
if (messages[0].isError) {
diff --git a/src/main/java/net/thauvin/erik/mobibot/modules/WorldTime.kt b/src/main/java/net/thauvin/erik/mobibot/modules/WorldTime.kt
index cafe903..835fcca 100644
--- a/src/main/java/net/thauvin/erik/mobibot/modules/WorldTime.kt
+++ b/src/main/java/net/thauvin/erik/mobibot/modules/WorldTime.kt
@@ -190,7 +190,7 @@ class WorldTime(bot: Mobibot) : AbstractModule(bot) {
with(bot) {
if (args.isEmpty()) {
send(sender, "The supported countries/zones are: ", isPrivate)
- sendList(sender, ArrayList(COUNTRIES_MAP.keys), 17, false, false)
+ sendList(sender, ArrayList(COUNTRIES_MAP.keys), 17, isPrivate = false, isBold = false)
} else {
val msg = worldTime(args)
if (isPrivate) {
diff --git a/src/test/java/net/thauvin/erik/mobibot/commands/tell/TellMessageTest.kt b/src/test/java/net/thauvin/erik/mobibot/commands/tell/TellMessageTest.kt
index 5dbf7f3..75a1139 100644
--- a/src/test/java/net/thauvin/erik/mobibot/commands/tell/TellMessageTest.kt
+++ b/src/test/java/net/thauvin/erik/mobibot/commands/tell/TellMessageTest.kt
@@ -31,11 +31,11 @@
*/
package net.thauvin.erik.mobibot.commands.tell
-import java.time.temporal.Temporal
-import java.time.LocalDateTime
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
import java.time.Duration
+import java.time.LocalDateTime
+import java.time.temporal.Temporal
/**
* The `TellMessageTest` class.
diff --git a/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.kt b/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.kt
index 019538f..8592447 100644
--- a/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.kt
+++ b/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.kt
@@ -62,7 +62,7 @@ class GoogleSearchTest : LocalProperties() {
.`as`("no query").isInstanceOf(ModuleException::class.java).hasNoCause()
} catch (e: ModuleException) {
// Avoid displaying api keys in CI logs
- if ("true" == System.getenv("CI") && !apiKey.isNullOrBlank() && !cseKey.isNullOrBlank()) {
+ if ("true" == System.getenv("CI") && !apiKey.isBlank() && !cseKey.isBlank()) {
throw ModuleException(e.debugMessage, e.getSanitizedMessage(apiKey, cseKey))
} else {
throw e
diff --git a/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.kt b/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.kt
index ea78c34..c47509b 100644
--- a/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.kt
+++ b/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.kt
@@ -63,7 +63,7 @@ class StockQuoteTest : LocalProperties() {
.isInstanceOf(ModuleException::class.java).hasNoCause()
} catch (e: ModuleException) {
// Avoid displaying api keys in CI logs
- if ("true" == System.getenv("CI") && !apiKey.isNullOrBlank()) {
+ if ("true" == System.getenv("CI") && !apiKey.isBlank()) {
throw ModuleException(e.debugMessage, e.getSanitizedMessage(apiKey))
} else {
throw e
diff --git a/version.properties b/version.properties
index 25393d2..0f713d3 100644
--- a/version.properties
+++ b/version.properties
@@ -1,9 +1,9 @@
#Generated by the Semver Plugin for Gradle
-#Fri Dec 04 15:43:01 PST 2020
-version.buildmeta=312
+#Fri Dec 04 22:59:43 PST 2020
+version.buildmeta=337
version.major=0
version.minor=8
version.patch=0
version.prerelease=beta
version.project=mobibot
-version.semver=0.8.0-beta+312
+version.semver=0.8.0-beta+337