From b729eb272900a85c9ba813b13920ecdad4c5ad53 Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Fri, 19 Jun 2020 14:19:23 -0700 Subject: [PATCH] Moved Pinboard to kotlin and added tests. --- detekt-baseline.xml | 12 +- .../net/thauvin/erik/mobibot/Mobibot.java | 18 +- .../net/thauvin/erik/mobibot/Pinboard.java | 167 ------------------ .../net/thauvin/erik/mobibot/PinboardUtils.kt | 141 +++++++++++++++ .../{modules => }/LocalProperties.java | 8 +- .../thauvin/erik/mobibot/PinboardUtilsTest.kt | 86 +++++++++ .../mobibot/modules/GoogleSearchTest.java | 1 + .../erik/mobibot/modules/StockQuoteTest.java | 1 + .../erik/mobibot/modules/TwitterTest.java | 1 + .../erik/mobibot/modules/Weather2Test.java | 1 + 10 files changed, 254 insertions(+), 182 deletions(-) delete mode 100644 src/main/java/net/thauvin/erik/mobibot/Pinboard.java create mode 100644 src/main/java/net/thauvin/erik/mobibot/PinboardUtils.kt rename src/test/java/net/thauvin/erik/mobibot/{modules => }/LocalProperties.java (93%) create mode 100644 src/test/java/net/thauvin/erik/mobibot/PinboardUtilsTest.kt diff --git a/detekt-baseline.xml b/detekt-baseline.xml index d61a8d8..93d9e37 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -1,7 +1,7 @@ - - + + LongParameterList:Comment.kt$Comment$( bot: Mobibot, cmd: String, sender: String, isOp: Boolean, entry: EntryLink, index: Int, commentIndex: Int ) LongParameterList:Comment.kt$Comment$( bot: Mobibot, sender: String, isOp: Boolean, entry: EntryLink, index: Int, commentIndex: Int ) LongParameterList:Comment.kt$Comment$(bot: Mobibot, cmd: String, sender: String, entry: EntryLink, index: Int, commentIndex: Int) @@ -12,8 +12,8 @@ MagicNumber:Recap.kt$Recap.Companion$10 MagicNumber:Users.kt$Users$8 MagicNumber:View.kt$View$8 - NestedBlockDepth:Addons.kt$Addons$add - NestedBlockDepth:Comment.kt$Comment$commandResponse - NestedBlockDepth:LinksMgr.kt$LinksMgr$commandResponse - + NestedBlockDepth:Addons.kt$Addons$ fun add(command: AbstractCommand, props: Properties) + NestedBlockDepth:Comment.kt$Comment$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean ) + NestedBlockDepth:LinksMgr.kt$LinksMgr$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean ) + diff --git a/src/main/java/net/thauvin/erik/mobibot/Mobibot.java b/src/main/java/net/thauvin/erik/mobibot/Mobibot.java index 945ad1f..df34caa 100644 --- a/src/main/java/net/thauvin/erik/mobibot/Mobibot.java +++ b/src/main/java/net/thauvin/erik/mobibot/Mobibot.java @@ -70,6 +70,7 @@ 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; @@ -96,6 +97,7 @@ 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; @@ -154,7 +156,7 @@ public class Mobibot extends PircBot { // NickServ ident password private String identPwd = ""; // Pinboard posts handler - private Pinboard pinboard; + private PinboardPoster pinboard; // Weblog URL private String weblogUrl = ""; @@ -383,7 +385,7 @@ public class Mobibot extends PircBot { */ public final void addPin(final EntryLink entry) { if (pinboard != null) { - pinboard.addPost(entry); + PinboardUtils.addPin(pinboard, ircServer, entry); } } @@ -423,7 +425,7 @@ public class Mobibot extends PircBot { */ public final void deletePin(final int index, final EntryLink entry) { if (pinboard != null) { - pinboard.deletePost(entry); + PinboardUtils.deletePin(pinboard, entry); } if (twitter.isAutoPost()) { twitter.removeEntry(index); @@ -914,7 +916,13 @@ public class Mobibot extends PircBot { */ final void setPinboardAuth(final String apiToken) { if (isNotBlank(apiToken)) { - pinboard = new Pinboard(this, 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); + } } } @@ -948,7 +956,7 @@ public class Mobibot extends PircBot { */ public final void updatePin(final String oldUrl, final EntryLink entry) { if (pinboard != null) { - pinboard.updatePost(oldUrl, entry); + PinboardUtils.updatePin(pinboard, ircServer, oldUrl, entry); } } } diff --git a/src/main/java/net/thauvin/erik/mobibot/Pinboard.java b/src/main/java/net/thauvin/erik/mobibot/Pinboard.java deleted file mode 100644 index 0291ff3..0000000 --- a/src/main/java/net/thauvin/erik/mobibot/Pinboard.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Pinboard.java - * - * Copyright (c) 2004-2019, 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.entries.EntryLink; -import net.thauvin.erik.pinboard.PinboardPoster; - -import javax.swing.SwingWorker; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Date; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The class to handle posts to pinboard.in. - * - * @author Erik C. Thauvin - * @created 2017-05-17 - * @since 1.0 - */ -class Pinboard { - private final String ircServer; - private final PinboardPoster pinboard; - - /** - * Creates a new {@link Pinboard} instance. - * - * @param bot The bot's instance. - * @param apiToken The API end point. - */ - Pinboard(final Mobibot bot, final String apiToken) { - pinboard = new PinboardPoster(apiToken); - ircServer = bot.getIrcServer(); - - if (bot.getLogger().isDebugEnabled()) { - final ConsoleHandler consoleHandler = new ConsoleHandler(); - consoleHandler.setLevel(Level.FINE); - final Logger logger = pinboard.getLogger(); - logger.addHandler(consoleHandler); - logger.setLevel(Level.FINE); - } - } - - /** - * Adds a post to pinboard.in. - * - * @param entry The entry to add. - */ - final void addPost(final EntryLink entry) { - final SwingWorker worker = new SwingWorker<>() { - @Override - protected Boolean doInBackground() { - return pinboard.addPin(entry.getLink(), - entry.getTitle(), - postedBy(entry), - entry.getPinboardTags(), - formatDate(entry.getDate())); - } - }; - - worker.execute(); - } - - /** - * Deletes a post to pinboard.in. - * - * @param entry The entry to delete. - */ - final void deletePost(final EntryLink entry) { - final String link = entry.getLink(); - - final SwingWorker worker = new SwingWorker<>() { - @Override - protected Boolean doInBackground() { - return pinboard.deletePin(link); - } - }; - - worker.execute(); - } - - /** - * Format a date to a UTC timestamp. - * - * @param date The date. - * @return The date in {@link DateTimeFormatter#ISO_INSTANT} format. - */ - private String formatDate(final Date date) { - return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).format(DateTimeFormatter.ISO_INSTANT); - } - - /** - * Returns he pinboard.in extended attribution line. - * - * @param entry The entry. - * @return The extended attribution line. - */ - private String postedBy(final EntryLink entry) { - return "Posted by " + entry.getNick() + " on " + entry.getChannel() + " (" + ircServer + ')'; - } - - /** - * Updates a post to pinboard.in. - * - * @param oldUrl The old post URL. - * @param entry The entry to add. - */ - final void updatePost(final String oldUrl, final EntryLink entry) { - final SwingWorker worker = new SwingWorker<>() { - @Override - protected Boolean doInBackground() { - if (!oldUrl.equals(entry.getLink())) { - pinboard.deletePin(oldUrl); - - return pinboard.addPin(entry.getLink(), - entry.getTitle(), - postedBy(entry), - entry.getPinboardTags(), - formatDate(entry.getDate())); - } else { - return pinboard.addPin(entry.getLink(), - entry.getTitle(), - postedBy(entry), - entry.getPinboardTags(), - formatDate(entry.getDate()), - true, - true); - } - } - }; - - worker.execute(); - } -} diff --git a/src/main/java/net/thauvin/erik/mobibot/PinboardUtils.kt b/src/main/java/net/thauvin/erik/mobibot/PinboardUtils.kt new file mode 100644 index 0000000..b12a85f --- /dev/null +++ b/src/main/java/net/thauvin/erik/mobibot/PinboardUtils.kt @@ -0,0 +1,141 @@ +/* + * Pinboard.java + * + * Copyright (c) 2004-2019, 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 kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import net.thauvin.erik.mobibot.entries.EntryLink +import net.thauvin.erik.pinboard.PinboardPoster +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.util.* + +/** + * The class to handle posts to pinboard.in. + * + * @author [Erik C. Thauvin](https://erik.thauvin.net) + * @created 2017-05-17 + * @since 1.0 + */ +object PinboardUtils { + /** + * Adds a pin. + * + * @param poster The PinboardPoster instance. + * @param entry The entry to add. + */ + @JvmStatic + fun addPin(poster: PinboardPoster, ircServer: String, entry: EntryLink) = runBlocking { + val add = GlobalScope.async { + poster.addPin( + entry.link, + entry.title, + postedBy(entry, ircServer), + entry.pinboardTags, + formatDate(entry.date) + ) + } + add.await() + } + + /** + * Deletes a pin. + * + * @param poster The PinboardPoster instance. + * @param entry The entry to delete. + */ + @JvmStatic + fun deletePin(poster: PinboardPoster, entry: EntryLink) = runBlocking { + val delete = GlobalScope.async { + poster.deletePin(entry.link) + } + delete.await() + } + + /** + * Updates a pin. + * + * @param poster The PinboardPoster instance. + * @param oldUrl The old post URL. + * @param entry The entry to add. + */ + @JvmStatic + fun updatePin(poster: PinboardPoster, ircServer: String, oldUrl: String, entry: EntryLink) = runBlocking { + val update = GlobalScope.async { + if (oldUrl != entry.link) { + poster.deletePin(oldUrl) + poster.addPin( + entry.link, + entry.title, + postedBy(entry, ircServer), + entry.pinboardTags, + formatDate(entry.date) + ) + } else { + poster.addPin( + entry.link, + entry.title, + postedBy(entry, ircServer), + entry.pinboardTags, + formatDate(entry.date), + replace = true, + shared = true + ) + } + } + update.await() + } + + /** + * Format a date to a UTC timestamp. + * + * @param date The date. + * @return The date in [DateTimeFormatter.ISO_INSTANT] format. + */ + private fun formatDate(date: Date): String { + return ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).format(DateTimeFormatter.ISO_INSTANT) + } + + /** + * Returns he pinboard.in extended attribution line. + * + * @param entry The entry. + * @return The extended attribution line. + */ + private fun postedBy(entry: EntryLink, ircServer: String): String { + return "Posted by ${entry.nick} on ${entry.channel} ( $ircServer )" + } +} + diff --git a/src/test/java/net/thauvin/erik/mobibot/modules/LocalProperties.java b/src/test/java/net/thauvin/erik/mobibot/LocalProperties.java similarity index 93% rename from src/test/java/net/thauvin/erik/mobibot/modules/LocalProperties.java rename to src/test/java/net/thauvin/erik/mobibot/LocalProperties.java index acd9dab..1585681 100644 --- a/src/test/java/net/thauvin/erik/mobibot/modules/LocalProperties.java +++ b/src/test/java/net/thauvin/erik/mobibot/LocalProperties.java @@ -1,7 +1,7 @@ /* * LocalProperties.java * - * Copyright (c) 2004-2019, Erik C. Thauvin (erik@thauvin.net) + * Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net) * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,7 +30,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.thauvin.erik.mobibot.modules; +package net.thauvin.erik.mobibot; import org.apache.commons.lang3.StringUtils; import org.testng.annotations.BeforeSuite; @@ -49,10 +49,10 @@ import java.util.Properties; * @created 2019-04-08 * @since 1.0 */ -class LocalProperties { +public class LocalProperties { private static final Properties localProps = new Properties(); - static String getProperty(final String key) { + public static String getProperty(final String key) { if (localProps.containsKey(key)) { return localProps.getProperty(key); } else { diff --git a/src/test/java/net/thauvin/erik/mobibot/PinboardUtilsTest.kt b/src/test/java/net/thauvin/erik/mobibot/PinboardUtilsTest.kt new file mode 100644 index 0000000..a8eed6c --- /dev/null +++ b/src/test/java/net/thauvin/erik/mobibot/PinboardUtilsTest.kt @@ -0,0 +1,86 @@ +/* + * PinboardTest.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.entries.EntryLink +import net.thauvin.erik.pinboard.PinboardPoster +import org.testng.Assert +import org.testng.annotations.Test +import java.io.IOException +import java.net.URI +import java.net.URISyntaxException +import java.net.URLEncoder +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.nio.charset.StandardCharsets + +class PinboardUtilsTest : LocalProperties() { + @Test + @Throws(InterruptedException::class, IOException::class, URISyntaxException::class) + fun pinboardTest() { + val apiToken = getProperty("pinboard-api-token") + val pinboard = PinboardPoster(apiToken) + val url = "https://www.example.com/" + val ircServer = "irc.test.com" + val entry = EntryLink(url, "Test Example", "ErikT", "", "#mobitopia", listOf("test")) + + PinboardUtils.addPin(pinboard, ircServer, entry) + Assert.assertTrue(validatePin(apiToken, ircServer, entry.link), "add") + entry.link = "https://www.foo.com/" + + PinboardUtils.updatePin(pinboard, ircServer, url, entry) + Assert.assertTrue(validatePin(apiToken, ircServer, entry.link), "update") + + PinboardUtils.deletePin(pinboard, entry) + Assert.assertFalse(validatePin(apiToken, url = entry.link), "delete") + } + + @Throws(IOException::class, URISyntaxException::class, InterruptedException::class) + private fun validatePin(apiToken: String, ircServer: String = "", url: String): Boolean { + val request = HttpRequest.newBuilder().uri( + URI( + "https://api.pinboard.in/v1/posts/get?auth_token=${apiToken}&tag=test&" + + URLEncoder.encode(url, StandardCharsets.UTF_8) + ) + ).GET().build() + + val response = HttpClient.newBuilder() + .build() + .send(request, HttpResponse.BodyHandlers.ofString()) + + return if (response.statusCode() == 200) { + response.body().contains(url) && response.body().contains(ircServer) + } else false + } +} diff --git a/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.java b/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.java index 7633f84..ecf0f0f 100644 --- a/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.java +++ b/src/test/java/net/thauvin/erik/mobibot/modules/GoogleSearchTest.java @@ -33,6 +33,7 @@ package net.thauvin.erik.mobibot.modules; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import net.thauvin.erik.mobibot.LocalProperties; import net.thauvin.erik.mobibot.msg.Message; import org.testng.annotations.Test; diff --git a/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.java b/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.java index 715fe64..a8961c6 100644 --- a/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.java +++ b/src/test/java/net/thauvin/erik/mobibot/modules/StockQuoteTest.java @@ -33,6 +33,7 @@ package net.thauvin.erik.mobibot.modules; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import net.thauvin.erik.mobibot.LocalProperties; import net.thauvin.erik.mobibot.msg.Message; import org.testng.annotations.Test; diff --git a/src/test/java/net/thauvin/erik/mobibot/modules/TwitterTest.java b/src/test/java/net/thauvin/erik/mobibot/modules/TwitterTest.java index 11d4123..88782ad 100644 --- a/src/test/java/net/thauvin/erik/mobibot/modules/TwitterTest.java +++ b/src/test/java/net/thauvin/erik/mobibot/modules/TwitterTest.java @@ -33,6 +33,7 @@ package net.thauvin.erik.mobibot.modules; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import net.thauvin.erik.mobibot.LocalProperties; import org.testng.annotations.Test; import java.net.InetAddress; diff --git a/src/test/java/net/thauvin/erik/mobibot/modules/Weather2Test.java b/src/test/java/net/thauvin/erik/mobibot/modules/Weather2Test.java index ffd5c60..6d8c15c 100644 --- a/src/test/java/net/thauvin/erik/mobibot/modules/Weather2Test.java +++ b/src/test/java/net/thauvin/erik/mobibot/modules/Weather2Test.java @@ -34,6 +34,7 @@ package net.thauvin.erik.mobibot.modules; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import net.aksingh.owmjapis.api.APIException; +import net.thauvin.erik.mobibot.LocalProperties; import net.thauvin.erik.mobibot.msg.Message; import org.testng.annotations.Test;