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 @@ \ 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 @@ @@ -27,6 +28,9 @@ \ 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