Converter Entry Links manipulation classes to Kotlin.

This commit is contained in:
Erik C. Thauvin 2020-12-02 22:57:25 -08:00
parent 310ffb91da
commit 7759e278e8
22 changed files with 818 additions and 1213 deletions

View file

@ -2,7 +2,9 @@
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<CurrentIssues>
<ID>ComplexMethod:EntriesMgr.kt$EntriesMgr.Companion$ fun saveEntries( bot: Mobibot, entries: List&lt;EntryLink&gt;, history: MutableList&lt;String&gt;, isDayBackup: Boolean )</ID>
<ID>ComplexMethod:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>LongMethod:EntriesMgr.kt$EntriesMgr.Companion$ fun saveEntries( bot: Mobibot, entries: List&lt;EntryLink&gt;, history: MutableList&lt;String&gt;, isDayBackup: Boolean )</ID>
<ID>LongMethod:StockQuote.kt$StockQuote.Companion$ @JvmStatic @Throws(ModuleException::class) fun getQuote(symbol: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>LongMethod:Weather2.kt$Weather2.Companion$ @JvmStatic @Throws(ModuleException::class) fun getWeather(query: String, apiKey: String?): List&lt;Message&gt;</ID>
<ID>LongParameterList:Comment.kt$Comment$( bot: Mobibot, cmd: String, sender: String, isOp: Boolean, entry: EntryLink, index: Int, commentIndex: Int )</ID>
@ -43,6 +45,9 @@
<ID>NestedBlockDepth:Addons.kt$Addons$ fun add(command: AbstractCommand, props: Properties)</ID>
<ID>NestedBlockDepth:Comment.kt$Comment$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean )</ID>
<ID>NestedBlockDepth:CurrencyConverter.kt$CurrencyConverter.Companion$ @JvmStatic fun convertCurrency(query: String): Message</ID>
<ID>NestedBlockDepth:EntriesMgr.kt$EntriesMgr.Companion$ @Throws(IOException::class, FeedException::class) fun loadEntries(file: String, channel: String, entries: ArrayList&lt;EntryLink&gt;): String</ID>
<ID>NestedBlockDepth:EntriesMgr.kt$EntriesMgr.Companion$ fun saveEntries( bot: Mobibot, entries: List&lt;EntryLink&gt;, history: MutableList&lt;String&gt;, isDayBackup: Boolean )</ID>
<ID>NestedBlockDepth:EntryLink.kt$EntryLink$ fun setTags(tags: List&lt;String?&gt;)</ID>
<ID>NestedBlockDepth:FeedReader.kt$FeedReader$ override fun run()</ID>
<ID>NestedBlockDepth:GoogleSearch.kt$GoogleSearch$ override fun run(sender: String, cmd: String, args: String, isPrivate: Boolean)</ID>
<ID>NestedBlockDepth:LinksMgr.kt$LinksMgr$override fun commandResponse( sender: String, login: String, args: String, isOp: Boolean, isPrivate: Boolean )</ID>

View file

@ -44,17 +44,10 @@ 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 {
@ -72,9 +65,6 @@ object PinboardUtils {
/**
* Deletes a pin.
*
* @param poster The PinboardPoster instance.
* @param entry The entry to delete.
*/
@JvmStatic
fun deletePin(poster: PinboardPoster, entry: EntryLink) = runBlocking {
@ -86,53 +76,45 @@ object PinboardUtils {
/**
* 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) {
with(entry) {
if (oldUrl != link) {
poster.deletePin(oldUrl)
poster.addPin(
entry.link,
entry.title,
link,
title,
postedBy(entry, ircServer),
entry.pinboardTags,
formatDate(entry.date)
pinboardTags,
formatDate(date)
)
} else {
poster.addPin(
entry.link,
entry.title,
link,
title,
postedBy(entry, ircServer),
entry.pinboardTags,
formatDate(entry.date),
pinboardTags,
formatDate(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.
* Returns the pinboard.in extended attribution line.
*/
private fun postedBy(entry: EntryLink, ircServer: String): String {
return "Posted by ${entry.nick} on ${entry.channel} ( $ircServer )"

View file

@ -51,14 +51,16 @@ class Cycle(bot: Mobibot) : AbstractCommand(bot) {
isOp: Boolean,
isPrivate: Boolean
) {
with(bot) {
if (isOp) {
bot.send("$sender has just asked me to leave. I'll be back!")
bot.sleep(wait)
bot.partChannel(bot.channel)
bot.sleep(wait)
bot.joinChannel(bot.channel)
send("$sender has just asked me to leave. I'll be back!")
sleep(wait)
partChannel(channel)
sleep(wait)
joinChannel(channel)
} else {
bot.helpDefault(sender, isOp, isPrivate)
helpDefault(sender, isOp, isPrivate)
}
}
}
}

View file

@ -52,17 +52,17 @@ class Modules(bot: Mobibot) : AbstractCommand(bot) {
isOp: Boolean,
isPrivate: Boolean
) {
with(bot) {
if (isOp) {
with(bot.modulesNames) {
if (isEmpty()) {
bot.send(sender, "There are no enabled modules.", isPrivate)
if (modulesNames.isEmpty()) {
send(sender, "There are no enabled modules.", isPrivate)
} else {
bot.send(sender, "The enabled modules are: ", isPrivate)
bot.sendList(sender, this, 7, isPrivate, false)
}
send(sender, "The enabled modules are: ", isPrivate)
sendList(sender, modulesNames, 7, isPrivate, false)
}
} else {
bot.helpDefault(sender, isOp, isPrivate)
helpDefault(sender, isOp, isPrivate)
}
}
}
}

View file

@ -71,7 +71,7 @@ class Comment(bot: Mobibot) : AbstractCommand(bot) {
if (index < LinksMgr.entriesCount) {
val entry: EntryLink = LinksMgr.getEntry(index)
val commentIndex = cmds[1].toInt() - 1
if (commentIndex < entry.commentsCount) {
if (commentIndex < entry.comments.size) {
when (val cmd = cmds[2].trim()) {
"" -> showComment(bot, entry, index, commentIndex) // L1.1:
"-" -> deleteComment(bot, sender, isOp, entry, index, commentIndex) // L11:-
@ -140,7 +140,7 @@ class Comment(bot: Mobibot) : AbstractCommand(bot) {
) {
if (isOp || sender == entry.getComment(commentIndex).nick) {
entry.deleteComment(commentIndex)
bot.send("Comment ${Constants.LINK_CMD}${index + 1}.${commentIndex + 1} removed.")
bot.send("Comment ${EntriesUtils.buildLinkCmd(index)}.${commentIndex + 1} removed.")
LinksMgr.saveEntries(bot, false)
} else {
bot.send(sender, "Please ask a channel op to delete this comment for you.", false)

View file

@ -40,6 +40,7 @@ import net.thauvin.erik.mobibot.commands.Ignore
import net.thauvin.erik.mobibot.entries.EntriesMgr
import net.thauvin.erik.mobibot.entries.EntriesUtils
import net.thauvin.erik.mobibot.entries.EntryLink
import org.apache.logging.log4j.LogManager
import org.jsoup.Jsoup
import java.io.IOException
@ -166,7 +167,7 @@ class LinksMgr(bot: Mobibot) : AbstractCommand(bot) {
bot.send(sender, "Please specify a title, by typing:", isPrivate)
bot.send(
sender,
Utils.helpIndent(Constants.LINK_CMD + (index + 1) + ":|This is the title"),
Utils.helpIndent("${EntriesUtils.buildLinkCmd(index)}:|This is the title"),
isPrivate
)
}

View file

@ -137,7 +137,7 @@ class Posting(bot: Mobibot) : AbstractCommand(bot) {
if (entry.login == login || isOp) {
bot.deletePin(index, entry)
LinksMgr.removeEntry(index)
bot.send("Entry ${Constants.LINK_CMD}${index + 1} removed.")
bot.send("Entry ${EntriesUtils.buildLinkCmd(index)} removed.")
LinksMgr.saveEntries(bot, false)
} else {
bot.send(sender, "Please ask a channel op to remove this entry for you.", false)

View file

@ -104,7 +104,9 @@ public class Tell extends AbstractCommand {
*/
@SuppressWarnings("WeakerAccess")
final boolean clean() {
if (getBot().getLogger().isDebugEnabled()) {
getBot().getLogger().debug("Cleaning the messages.");
}
return TellMessagesMgr.clean(messages, maxDays);
}

View file

@ -94,8 +94,9 @@ final class TellMessagesMgr {
try {
try (final ObjectInput input = new ObjectInputStream(
new BufferedInputStream(Files.newInputStream(Paths.get(file))))) {
if (logger.isDebugEnabled()) {
logger.debug("Loading the messages.");
}
return ((List<TellMessage>) input.readObject());
}
} catch (FileNotFoundException ignore) {
@ -118,7 +119,9 @@ final class TellMessagesMgr {
try {
try (final BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get(file)))) {
try (final ObjectOutput output = new ObjectOutputStream(bos)) {
if (logger.isDebugEnabled()) {
logger.debug("Saving the messages.");
}
output.writeObject(messages);
}
}

View file

@ -1,331 +0,0 @@
/*
* EntriesMgr.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.entries;
import com.rometools.rome.feed.synd.SyndContent;
import com.rometools.rome.feed.synd.SyndContentImpl;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndEntryImpl;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.feed.synd.SyndFeedImpl;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.SyndFeedOutput;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Mobibot;
import net.thauvin.erik.mobibot.Utils;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
/**
* Manages the feed entries.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created 2014-04-28
* @since 1.0
*/
public final class EntriesMgr {
/**
* The name of the file containing the current entries.
*/
public static final String CURRENT_XML = "current.xml";
/**
* The name of the file containing the backlog entries.
*/
public static final String NAV_XML = "nav.xml";
/**
* The .xml extension
*/
public static final String XML_EXT = ".xml";
// Maximum number of backlogs to keep
private static final int MAX_BACKLOGS = 10;
/**
* Disables the default constructor.
*
* @throws UnsupportedOperationException If the constructor is called.
*/
private EntriesMgr() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
/**
* Loads the backlogs.
*
* @param file The file containing the backlogs.
* @param history The history list.
* @throws IOException If the file was not found or could not be read.
* @throws FeedException If an error occurred while reading the feed.
*/
public static void loadBacklogs(final String file, final Collection<String> history)
throws IOException, FeedException {
history.clear();
final SyndFeedInput input = new SyndFeedInput();
try (final InputStreamReader reader =
new InputStreamReader(Files.newInputStream(Paths.get(file)), StandardCharsets.UTF_8)) {
final SyndFeed feed = input.build(reader);
final List<SyndEntry> items = feed.getEntries();
SyndEntry item;
for (int i = items.size() - 1; i >= 0; i--) {
item = items.get(i);
history.add(item.getTitle());
}
}
}
/**
* Loads the current entries.
*
* @param file The file containing the current entries.
* @param channel The channel
* @param entries The entries.
* @return The feed's last published date.
* @throws IOException If the file was not found or could not be read.
* @throws FeedException If an error occurred while reading the feed.
*/
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public static String loadEntries(final String file, final String channel, final Collection<EntryLink> entries)
throws IOException, FeedException {
entries.clear();
final SyndFeedInput input = new SyndFeedInput();
final String today;
try (final InputStreamReader reader = new InputStreamReader(
Files.newInputStream(Paths.get(file)), StandardCharsets.UTF_8)) {
final SyndFeed feed = input.build(reader);
today = Utils.isoLocalDate(feed.getPublishedDate());
final List<SyndEntry> items = feed.getEntries();
SyndEntry item;
SyndContent description;
String[] comments;
String author;
EntryLink entry;
for (int i = items.size() - 1; i >= 0; i--) {
item = items.get(i);
author = item.getAuthor()
.substring(item.getAuthor().lastIndexOf('(') + 1, item.getAuthor().length() - 1);
entry = new EntryLink(item.getLink(),
item.getTitle(),
author,
channel,
item.getPublishedDate(),
item.getCategories());
description = item.getDescription();
comments = description.getValue().split("<br/>");
int split;
for (final String comment : comments) {
split = comment.indexOf(": ");
if (split != -1) {
entry.addComment(comment.substring(split + 2).trim(), comment.substring(0, split).trim());
}
}
entries.add(entry);
}
}
return today;
}
/**
* Saves the entries.
*
* @param bot The bot object.
* @param entries The entries array.
* @param history The history array.
* @param isDayBackup Set the true if the daily backup file should also be created.
*/
@SuppressFBWarnings(value = { "CE_CLASS_ENVY", "CC_CYCLOMATIC_COMPLEXITY" },
justification = "Yes, it does.")
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public static void saveEntries(final Mobibot bot,
final List<EntryLink> entries,
final List<String> history,
final boolean isDayBackup) {
bot.getLogger().debug("Saving the feeds...");
if (StringUtils.isNotBlank(bot.getLogsDir()) && StringUtils.isNotBlank(bot.getWeblogUrl())) {
try {
final SyndFeedOutput output = new SyndFeedOutput();
SyndFeed rss = new SyndFeedImpl();
final List<SyndEntry> items = new ArrayList<>(0);
SyndEntry item;
SyndContent description;
try (final Writer fw = new OutputStreamWriter(
Files.newOutputStream(Paths.get(bot.getLogsDir() + CURRENT_XML)), StandardCharsets.UTF_8)) {
rss.setFeedType("rss_2.0");
rss.setTitle(bot.getChannel() + " IRC Links");
rss.setDescription("Links from " + bot.getIrcServer() + " on " + bot.getChannel());
rss.setLink(bot.getWeblogUrl());
rss.setPublishedDate(Calendar.getInstance().getTime());
rss.setLanguage("en");
EntryLink entry;
StringBuilder buff;
EntryComment comment;
for (int i = (entries.size() - 1); i >= 0; --i) {
entry = entries.get(i);
buff = new StringBuilder()
.append("Posted by <b>")
.append(entry.getNick())
.append("</b> on <a href=\"irc://")
.append(bot.getIrcServer()).append('/')
.append(entry.getChannel())
.append("\"><b>")
.append(entry.getChannel())
.append("</b></a>");
if (entry.getCommentsCount() > 0) {
buff.append(" <br/><br/>");
final EntryComment[] comments = entry.getComments();
for (int j = 0; j < comments.length; j++) {
comment = comments[j];
if (j > 0) {
buff.append(" <br/>");
}
buff.append(comment.getNick()).append(": ").append(comment.getComment());
}
}
item = new SyndEntryImpl();
item.setLink(entry.getLink());
description = new SyndContentImpl();
description.setValue(buff.toString());
item.setDescription(description);
item.setTitle(entry.getTitle());
item.setPublishedDate(entry.getDate());
item.setAuthor(
bot.getChannel().substring(1) + '@' + bot.getIrcServer() + " (" + entry.getNick()
+ ')');
item.setCategories(entry.getTags());
items.add(item);
}
rss.setEntries(items);
bot.getLogger().debug("Writing the entries feed.");
output.output(rss, fw);
}
try (final Writer fw = new OutputStreamWriter(
Files.newOutputStream(Paths.get(
bot.getLogsDir() + bot.getToday() + XML_EXT)), StandardCharsets.UTF_8)) {
output.output(rss, fw);
}
if (isDayBackup) {
if (StringUtils.isNotBlank(bot.getBacklogsUrl())) {
if (!history.contains(bot.getToday())) {
history.add(bot.getToday());
while (history.size() > MAX_BACKLOGS) {
history.remove(0);
}
}
try (final Writer fw = new OutputStreamWriter(
Files.newOutputStream(Paths.get(bot.getLogsDir() + NAV_XML)), StandardCharsets.UTF_8)) {
rss = new SyndFeedImpl();
rss.setFeedType("rss_2.0");
rss.setTitle(bot.getChannel() + " IRC Links Backlogs");
rss.setDescription("Backlogs of Links from " + bot.getIrcServer() + " on "
+ bot.getChannel());
rss.setLink(bot.getBacklogsUrl());
rss.setPublishedDate(Calendar.getInstance().getTime());
String date;
items.clear();
for (int i = (history.size() - 1); i >= 0; --i) {
date = history.get(i);
item = new SyndEntryImpl();
item.setLink(bot.getBacklogsUrl() + date + ".xml");
item.setTitle(date);
description = new SyndContentImpl();
description.setValue("Links for " + date);
item.setDescription(description);
items.add(item);
}
rss.setEntries(items);
bot.getLogger().debug("Writing the backlog feed.");
output.output(rss, fw);
}
} else {
bot.getLogger().warn("Unable to generate the backlogs feed. No property configured.");
}
}
} catch (FeedException | IOException e) {
bot.getLogger().warn("Unable to generate the entries feed.", e);
}
} else {
bot.getLogger().warn("Unable to generate the entries feed. A required property is missing.");
}
}
}

View file

@ -0,0 +1,261 @@
/*
* EntriesMgr.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.entries
import com.rometools.rome.feed.synd.SyndContentImpl
import com.rometools.rome.feed.synd.SyndEntry
import com.rometools.rome.feed.synd.SyndEntryImpl
import com.rometools.rome.feed.synd.SyndFeed
import com.rometools.rome.feed.synd.SyndFeedImpl
import com.rometools.rome.io.FeedException
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
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
import kotlin.collections.ArrayList
/**
* Manages the feed entries.
*/
class EntriesMgr private constructor() {
companion object {
/**
* The name of the file containing the current entries.
*/
const val CURRENT_XML = "current.xml"
/**
* The name of the file containing the backlog entries.
*/
const val NAV_XML = "nav.xml"
/**
* The .xml extension
*/
const val XML_EXT = ".xml"
// Maximum number of backlogs to keep
private const val MAX_BACKLOGS = 10
/**
* Loads the backlogs.
*/
@Throws(IOException::class, FeedException::class)
fun loadBacklogs(file: String, history: ArrayList<String>) {
history.clear()
val input = SyndFeedInput()
InputStreamReader(Files.newInputStream(Paths.get(file)), StandardCharsets.UTF_8).use { reader ->
val feed = input.build(reader)
val items = feed.entries
for (i in items.indices.reversed()) {
history.add(items[i].title)
}
}
}
/**
* Loads the current entries.
*/
@Throws(IOException::class, FeedException::class)
fun loadEntries(file: String, channel: String, entries: ArrayList<EntryLink>): String {
entries.clear()
val input = SyndFeedInput()
var today: String
InputStreamReader(
Files.newInputStream(Paths.get(file)), StandardCharsets.UTF_8
).use { reader ->
val feed = input.build(reader)
today = isoLocalDate(feed.publishedDate)
val items = feed.entries
var entry: EntryLink
for (i in items.indices.reversed()) {
with(items[i]) {
entry = EntryLink(
link,
title,
author.substring(author.lastIndexOf('(') + 1, author.length - 1),
channel,
publishedDate,
categories
)
var split: List<String>
for (comment in description.value.split("<br/>")) {
split = comment.split(": ".toRegex(), 2)
if (split.size == 2) {
entry.addComment(comment = split[1].trim(), nick = split[0].trim())
}
}
}
entries.add(entry)
}
}
return today
}
/**
* Saves the entries.
*/
fun saveEntries(
bot: Mobibot,
entries: List<EntryLink>,
history: MutableList<String>,
isDayBackup: Boolean
) {
if (bot.logger.isDebugEnabled) {
bot.logger.debug("Saving the feeds...")
}
if (StringUtils.isNotBlank(bot.logsDir) && StringUtils.isNotBlank(bot.weblogUrl)) {
try {
val output = SyndFeedOutput()
var rss: SyndFeed = SyndFeedImpl()
val items: MutableList<SyndEntry> = ArrayList(0)
var item: SyndEntry
OutputStreamWriter(
Files.newOutputStream(Paths.get(bot.logsDir + CURRENT_XML)), StandardCharsets.UTF_8
).use { fw ->
rss.apply {
feedType = "rss_2.0"
title = bot.channel + " IRC Links"
description = "Links from ${bot.ircServer} on ${bot.channel}"
link = bot.weblogUrl
publishedDate = Calendar.getInstance().time
language = "en"
}
var buff: StringBuilder
var comment: EntryComment
for (i in entries.size - 1 downTo 0) {
with(entries[i]) {
buff = StringBuilder()
.append("Posted by <b>")
.append(nick)
.append("</b> on <a href=\"irc://")
.append(bot.ircServer).append('/')
.append(channel)
.append("\"><b>")
.append(channel)
.append("</b></a>")
if (comments.size > 0) {
buff.append(" <br/><br/>")
val comments = comments
for (j in comments.indices) {
comment = comments[j]
if (j > 0) {
buff.append(" <br/>")
}
buff.append(comment.nick).append(": ").append(comment.comment)
}
}
item = SyndEntryImpl()
item.link = link
item.description = SyndContentImpl().apply { value = buff.toString() }
item.title = title
item.publishedDate = date
item.author = "${bot.channel.substring(1)}@${bot.ircServer} ($nick)"
item.categories = tags
items.add(item)
}
}
rss.entries = items
if (bot.logger.isDebugEnabled) {
bot.logger.debug("Writing the entries feed.")
}
output.output(rss, fw)
}
OutputStreamWriter(
Files.newOutputStream(
Paths.get(
bot.logsDir + bot.today + XML_EXT
)
), StandardCharsets.UTF_8
).use { fw -> output.output(rss, fw) }
if (isDayBackup) {
if (StringUtils.isNotBlank(bot.backlogsUrl)) {
if (!history.contains(bot.today)) {
history.add(bot.today)
while (history.size > MAX_BACKLOGS) {
history.removeAt(0)
}
}
OutputStreamWriter(
Files.newOutputStream(Paths.get(bot.logsDir + NAV_XML)), StandardCharsets.UTF_8
).use { fw ->
rss = SyndFeedImpl()
rss.apply {
feedType = "rss_2.0"
title = "${bot.channel} IRC Links Backlogs"
description = "Backlogs of Links from ${bot.ircServer} on ${bot.channel}"
link = bot.backlogsUrl
publishedDate = Calendar.getInstance().time
}
var date: String
items.clear()
for (i in history.size - 1 downTo 0) {
date = history[i]
item = SyndEntryImpl()
item.apply {
link = bot.backlogsUrl + date + ".xml"
title = date
description = SyndContentImpl().apply { value = "Links for $date" }
}
items.add(item)
}
rss.entries = items
if (bot.logger.isDebugEnabled) {
bot.logger.debug("Writing the backlog feed.")
}
output.output(rss, fw)
}
} else {
bot.logger.warn("Unable to generate the backlogs feed. No property configured.")
}
}
} catch (e: FeedException) {
bot.logger.warn("Unable to generate the entries feed.", e)
} catch (e: IOException) {
bot.logger.warn("Unable to generate the entries feed.", e)
}
} else {
bot.logger.warn("Unable to generate the entries feed. A required property is missing.")
}
}
}
}

View file

@ -1,120 +0,0 @@
/*
* EntriesUtils.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.entries;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.Constants;
import net.thauvin.erik.mobibot.Utils;
/**
* The <code>Utils</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-19
* @since 1.0
*/
public final class EntriesUtils {
/**
* Disables the default constructor.
*/
private EntriesUtils() {
throw new UnsupportedOperationException("Illegal constructor call.");
}
/**
* Builds an entry's comment for display on the channel.
*
* @param entryIndex The entry's index.
* @param commentIndex The comment's index.
* @param comment The {@link EntryComment comment} object.
* @return The entry's comment.
*/
public static String buildComment(final int entryIndex, final int commentIndex, final EntryComment comment) {
return (Constants.LINK_CMD + (entryIndex + 1) + '.' + (commentIndex + 1) + ": [" + comment.getNick() + "] "
+ comment.getComment());
}
/**
* Builds an entry's link for display on the channel.
*
* @param index The entry's index.
* @param entry The {@link EntryLink entry} object.
* @return The entry's link.
* @see #buildLink(int, EntryLink, boolean)
*/
public static String buildLink(final int index, final EntryLink entry) {
return buildLink(index, entry, false);
}
/**
* Builds an entry's link for display on the channel.
*
* @param index The entry's index.
* @param entry The {@link EntryLink entry} object.
* @param isView Set to true to display the number of comments.
* @return The entry's link.
*/
@SuppressFBWarnings(value = "CE_CLASS_ENVY",
justification = "Yes, it does.")
public static String buildLink(final int index, final EntryLink entry, final boolean isView) {
final StringBuilder buff = new StringBuilder().append(Constants.LINK_CMD).append(index + 1)
.append(": ").append('[').append(entry.getNick()).append(']');
if (isView && entry.hasComments()) {
buff.append("[+").append(entry.getCommentsCount()).append(']');
}
buff.append(' ');
if (Constants.NO_TITLE.equals(entry.getTitle())) {
buff.append(entry.getTitle());
} else {
buff.append(Utils.bold(entry.getTitle()));
}
buff.append(" ( ").append(Utils.green(entry.getLink())).append(" )");
return buff.toString();
}
/**
* Build an entry's tags/categories for display on the channel.
*
* @param entryIndex The entry's index.
* @param entry The {@link EntryLink entry} object.
* @return The entry's tags.
*/
public static String buildTags(final int entryIndex, final EntryLink entry) {
return (Constants.LINK_CMD + (entryIndex + 1) + "T: " + entry.getPinboardTags().replace(",", ", "));
}
}

View file

@ -0,0 +1,83 @@
/*
* EntriesUtils.kt
*
* 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.entries
import net.thauvin.erik.mobibot.Constants
import net.thauvin.erik.mobibot.Utils.Companion.bold
import net.thauvin.erik.mobibot.Utils.Companion.green
/**
* The `Utils` class.
*/
class EntriesUtils private constructor() {
companion object {
/**
* Build link cmd based on its index. e.g: L1
*/
fun buildLinkCmd(index: Int): String = Constants.LINK_CMD + (index + 1)
/**
* Builds an entry's comment for display on the channel.
*/
fun buildComment(entryIndex: Int, commentIndex: Int, comment: EntryComment): String =
(buildLinkCmd(entryIndex) + '.' + (commentIndex + 1) + ": [" + comment.nick + "] "
+ comment.comment)
/**
* Builds an entry's link for display on the channel.
*/
@JvmOverloads
fun buildLink(entryIndex: Int, entry: EntryLink, isView: Boolean = false): String {
val buff = StringBuilder().append(buildLinkCmd(entryIndex)).append(": ")
.append('[').append(entry.nick).append(']')
if (isView && entry.hasComments()) {
buff.append("[+").append(entry.comments.size).append(']')
}
buff.append(' ')
with(entry) {
if (Constants.NO_TITLE == title) {
buff.append(title)
} else {
buff.append(bold(title))
}
buff.append(" ( ").append(green(link)).append(" )")
}
return buff.toString()
}
/**
* Build an entry's tags/categories for display on the channel.
*/
fun buildTags(entryIndex: Int, entry: EntryLink): String =
buildLinkCmd(entryIndex) + "T: " + entry.pinboardTags.replace(",", ", ")
}
}

View file

@ -1,130 +0,0 @@
/*
* EntryComment.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.entries;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* The class used to store comments associated to a specific entry.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Jan 31, 2004
* @since 1.0
*/
@SuppressWarnings({ "PMD.DataClass" })
public class EntryComment implements Serializable {
// Serial version UID
static final long serialVersionUID = 1L;
// Creation date
private final LocalDateTime date = LocalDateTime.now();
private String comment = "";
private String nick = "";
/**
* Creates a new comment.
*
* @param comment The new comment.
* @param nick The nickname of the comment's author.
*/
public EntryComment(final String comment, final String nick) {
this.comment = comment;
this.nick = nick;
}
/**
* Creates a new comment.
*/
@SuppressWarnings("UnusedDeclaration")
protected EntryComment() {
// Required for serialization
}
/**
* Returns the comment.
*
* @return The comment.
*/
public final String getComment() {
return comment;
}
/**
* Returns the comment's creation date.
*
* @return The date.
*/
@SuppressWarnings("UnusedDeclaration")
public final LocalDateTime getDate() {
return date;
}
/**
* Returns the nickname of the author of the comment.
*
* @return The nickname.
*/
public final String getNick() {
return nick;
}
/**
* Sets the comment.
*
* @param comment The actual comment.
*/
@SuppressWarnings("UnusedDeclaration")
public final void setComment(final String comment) {
this.comment = comment;
}
/**
* Sets the nickname of the author of the comment.
*
* @param nick The new nickname.
*/
public final void setNick(final String nick) {
this.nick = nick;
}
@Override
public String toString() {
return "EntryComment{"
+ "comment='" + comment + '\''
+ ", date=" + date
+ ", nick='" + nick + '\''
+ '}';
}
}

View file

@ -0,0 +1,54 @@
/*
* EntryComment.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.entries
import java.io.Serializable
import java.time.LocalDateTime
/**
* Entry comments data class.
*/
data class EntryComment(var comment: String, var nick: String) : Serializable {
/**
* Creation date.
*/
val date: LocalDateTime = LocalDateTime.now()
override fun toString(): String {
return ("EntryComment{comment='$comment', date=$date, nick='$nick'}")
}
companion object {
// Serial version UID
const val serialVersionUID = 1L
}
}

View file

@ -1,418 +0,0 @@
/*
* EntryLink.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.entries;
import com.rometools.rome.feed.synd.SyndCategory;
import com.rometools.rome.feed.synd.SyndCategoryImpl;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.thauvin.erik.mobibot.commands.links.LinksMgr;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The class used to store link entries.
*
* @author <a href="https://erik.thauvin.net" target="_blank">Erik C. Thauvin</a>
* @created Jan 31, 2004
* @since 1.0
*/
public class EntryLink implements Serializable {
// Serial version UID
static final long serialVersionUID = 1L;
// Link's comments
private final List<EntryComment> comments = new CopyOnWriteArrayList<>();
// Tags/categories
private final List<SyndCategory> tags = new CopyOnWriteArrayList<>();
// Channel
private String channel;
// Creation date
private Date date = Calendar.getInstance().getTime();
// Link's URL
private String link;
// Author's login
private String login = "";
// Author's nickname
private String nick;
// Link's title
private String title;
/**
* Creates a new entry.
*
* @param link The new entry's link.
* @param title The new entry's title.
* @param nick The nickname of the author of the link.
* @param login The login of the author of the link.
* @param channel The channel.
* @param tags The entry's tags/categories.
*/
public EntryLink(final String link,
final String title,
final String nick,
final String login,
final String channel,
final List<String> tags) {
this.link = link;
this.title = title;
this.nick = nick;
this.login = login;
this.channel = channel;
setTags(tags);
}
/**
* Creates a new entry.
*
* @param link The new entry's link.
* @param title The new entry's title.
* @param nick The nickname of the author of the link.
* @param channel The channel.
* @param date The entry date.
* @param tags The entry's tags/categories.
*/
public EntryLink(final String link,
final String title,
final String nick,
final String channel,
final Date date,
final List<SyndCategory> tags) {
this.link = link;
this.title = title;
this.nick = nick;
this.channel = channel;
this.date = new Date(date.getTime());
this.tags.addAll(tags);
}
/**
* Adds a new comment.
*
* @param comment The actual comment.
* @param nick The nickname of the author of the comment.
* @return The total number of comments for this entry.
*/
public final int addComment(final String comment, final String nick) {
comments.add(new EntryComment(comment, nick));
return (comments.size() - 1);
}
/**
* Deletes a specific comment.
*
* @param index The index of the comment to delete.
*/
public final void deleteComment(final int index) {
if (index < comments.size()) {
comments.remove(index);
}
}
/**
* Returns the channel the link was posted on.
*
* @return The channel
*/
public final String getChannel() {
return channel;
}
/**
* Returns a comment.
*
* @param index The comment's index.
* @return The specific comment.
*/
public final EntryComment getComment(final int index) {
return (comments.get(index));
}
/**
* Returns all the comments.
*
* @return The comments.
*/
public final EntryComment[] getComments() {
return (comments.toArray(new EntryComment[0]));
}
/**
* Returns the total number of comments.
*
* @return The count of comments.
*/
public final int getCommentsCount() {
return comments.size();
}
/**
* Returns the comment's creation date.
*
* @return The date.
*/
public final Date getDate() {
return new Date(date.getTime());
}
/**
* Returns the comment's link.
*
* @return The link.
*/
public final String getLink() {
return link;
}
/**
* Returns the comment's author login.
*
* @return The login;
*/
public final String getLogin() {
return login;
}
/**
* Returns the comment's author nickname.
*
* @return The nickname.
*/
public final String getNick() {
return nick;
}
/**
* Returns the tags formatted for pinboard.in
*
* @return The tags as a comma-delimited string.
*/
public final String getPinboardTags() {
final StringBuilder pinboardTags = new StringBuilder(nick);
for (final SyndCategory tag : tags) {
pinboardTags.append(',');
pinboardTags.append(tag.getName());
}
return pinboardTags.toString();
}
/**
* Returns the tags.
*
* @return The tags.
*/
public final List<SyndCategory> getTags() {
return tags;
}
/**
* Returns the comment's title.
*
* @return The title.
*/
public final String getTitle() {
return title;
}
/**
* Returns true if the entry has comments.
*
* @return true if there are comments, false otherwise.
*/
public final boolean hasComments() {
return !comments.isEmpty();
}
/**
* Returns true if the entry has tags.
*
* @return true if there are tags, false otherwise.
*/
public final boolean hasTags() {
return !tags.isEmpty();
}
/**
* Returns true if a string is contained in the link, title, or nick.
*
* @param match The string to match.
* @return {@code true} if matched, {@code false} otherwise.
*/
public Boolean matches(final String match) {
return (StringUtils.containsIgnoreCase(link, match)
|| StringUtils.containsIgnoreCase(title, match)
|| StringUtils.containsIgnoreCase(nick, match));
}
/**
* Sets the channel.
*
* @param channel The channel.
*/
@SuppressWarnings("UnusedDeclaration")
public final void setChannel(final String channel) {
this.channel = channel;
}
/**
* /** Sets a comment.
*
* @param index The comment's index.
* @param comment The actual comment.
* @param nick The nickname of the author of the comment.
*/
public final void setComment(final int index, final String comment, final String nick) {
if (index < comments.size()) {
comments.set(index, new EntryComment(comment, nick));
}
}
/**
* Sets the comment's link.
*
* @param link The new link.
*/
public final void setLink(final String link) {
this.link = link;
}
/**
* Sets the comment's author login.
*
* @param login The new login.
*/
@SuppressWarnings("UnusedDeclaration")
public final void setLogin(final String login) {
this.login = login;
}
/**
* Sets the comment's author nickname.
*
* @param nick The new nickname.
*/
public final void setNick(final String nick) {
this.nick = nick;
}
/**
* Sets the tags.
*
* @param tags The space-delimited tags.
*/
public final void setTags(final String tags) {
setTags(Arrays.asList(tags.split(LinksMgr.TAG_MATCH)));
}
/**
* Sets the tags.
*
* @param tags The tags list.
*/
@SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
public final void setTags(final List<String> tags) {
if (!tags.isEmpty()) {
SyndCategoryImpl category;
for (final String tag : tags) {
if (StringUtils.isNoneBlank(tag)) {
final String t = StringUtils.lowerCase(tag);
final char mod = t.charAt(0);
if (mod == '-') {
// Don't remove the channel tag
if (!channel.substring(1).equals(t.substring(1))) {
category = new SyndCategoryImpl();
category.setName(t.substring(1));
this.tags.remove(category);
}
} else {
category = new SyndCategoryImpl();
if (mod == '+') {
category.setName(t.substring(1));
} else {
category.setName(t);
}
if (!this.tags.contains(category)) {
this.tags.add(category);
}
}
}
}
}
}
/**
* Sets the comment's title.
*
* @param title The new title.
*/
public final void setTitle(final String title) {
this.title = title;
}
/**
* Returns a string representation of the object.
*
* @return A string representation of the object.
*/
@Override
public String toString() {
return "EntryLink{"
+ "channel='" + channel + '\''
+ ", comments=" + comments
+ ", date=" + date
+ ", link='" + link + '\''
+ ", login='" + login + '\''
+ ", nick='" + nick + '\''
+ ", tags=" + tags
+ ", title='" + title + '\''
+ '}';
}
}

View file

@ -0,0 +1,223 @@
/*
* EntryLink.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.entries
import com.rometools.rome.feed.synd.SyndCategory
import com.rometools.rome.feed.synd.SyndCategoryImpl
import net.thauvin.erik.mobibot.commands.links.LinksMgr
import org.apache.commons.lang3.StringUtils
import java.io.Serializable
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
/**
* The class used to store link entries.
*/
class EntryLink : Serializable {
// Link's comments
val comments: MutableList<EntryComment> = CopyOnWriteArrayList()
// Tags/categories
val tags: MutableList<SyndCategory> = CopyOnWriteArrayList()
// Channel
var channel: String
// Creation date
var date = Calendar.getInstance().time
// Link's URL
var link: String
// Author's login
var login = ""
// Author's nickname
var nick: String
// Link's title
var title: String
/**
* Creates a new entry.
*/
constructor(
link: String,
title: String,
nick: String,
login: String,
channel: String,
tags: List<String?>
) {
this.link = link
this.title = title
this.nick = nick
this.login = login
this.channel = channel
setTags(tags)
}
/**
* Creates a new entry.
*/
constructor(
link: String,
title: String,
nick: String,
channel: String,
date: Date,
tags: List<SyndCategory>
) {
this.link = link
this.title = title
this.nick = nick
this.channel = channel
this.date = Date(date.time)
this.tags.addAll(tags)
}
/**
* Adds a new comment.
*/
fun addComment(comment: String?, nick: String?): Int {
comments.add(EntryComment(comment!!, nick!!))
return comments.size - 1
}
/**
* Deletes a specific comment.
*/
fun deleteComment(index: Int) {
if (index < comments.size) {
comments.removeAt(index)
}
}
/**
* Returns a comment.
*/
fun getComment(index: Int): EntryComment = comments[index]
/**
* Returns the tags formatted for pinboard.in
*/
val pinboardTags: String
get() {
val pinboardTags = StringBuilder(nick)
for (tag in tags) {
pinboardTags.append(',')
pinboardTags.append(tag.name)
}
return pinboardTags.toString()
}
/**
* Returns true if the entry has comments.
*/
fun hasComments(): Boolean = comments.isNotEmpty()
/**
* Returns true if the entry has tags.
*/
fun hasTags(): Boolean = tags.isNotEmpty()
/**
* Returns true if a string is contained in the link, title, or nick.
*/
fun matches(match: String?): Boolean {
return (StringUtils.containsIgnoreCase(link, match)
|| StringUtils.containsIgnoreCase(title, match)
|| StringUtils.containsIgnoreCase(nick, match))
}
/**
* Sets a comment.
*/
fun setComment(index: Int, comment: String?, nick: String?) {
if (index < comments.size) {
comments[index] = EntryComment(comment!!, nick!!)
}
}
/**
* Sets the tags.
*/
fun setTags(tags: String) {
setTags(tags.split(LinksMgr.TAG_MATCH).toTypedArray().toList())
}
/**
* Sets the tags.
*/
fun setTags(tags: List<String?>) {
if (!tags.isEmpty()) {
var category: SyndCategoryImpl
for (tag in tags) {
if (StringUtils.isNoneBlank(tag)) {
val t = StringUtils.lowerCase(tag)
val mod = t[0]
if (mod == '-') {
// Don't remove the channel tag
if (channel.substring(1) != t.substring(1)) {
category = SyndCategoryImpl()
category.name = t.substring(1)
this.tags.remove(category)
}
} else {
category = SyndCategoryImpl()
if (mod == '+') {
category.name = t.substring(1)
} else {
category.name = t
}
if (!this.tags.contains(category)) {
this.tags.add(category)
}
}
}
}
}
}
/**
* Returns a string representation of the object.
*/
override fun toString(): String {
return ("EntryLink{channel='$channel', comments=$comments, date=$date, link='$link', login='$login'," +
"nick='$nick', tags=$tags, title='$title'}")
}
companion object {
// Serial version UID
const val serialVersionUID = 1L
}
}

View file

@ -37,6 +37,7 @@ import net.thauvin.erik.mobibot.TwitterTimer
import net.thauvin.erik.mobibot.Utils
import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.entriesCount
import net.thauvin.erik.mobibot.commands.links.LinksMgr.Companion.getEntry
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
@ -76,7 +77,7 @@ class Twitter(bot: Mobibot) : ThreadedModule(bot) {
override val isValidProperties: Boolean
get() {
for (s in propertyKeys) {
if (AUTOPOST_PROP != s && HANDLE_PROP != s && properties[s]!!.isBlank()) {
if (AUTOPOST_PROP != s && HANDLE_PROP != s && properties[s].isNullOrBlank()) {
return false
}
}
@ -130,7 +131,7 @@ class Twitter(bot: Mobibot) : ThreadedModule(bot) {
Thread {
try {
if (logger.isDebugEnabled) {
logger.debug("Posting {}{} to Twitter.", Constants.LINK_CMD, index + 1)
logger.debug("Posting {} to Twitter.", EntriesUtils.buildLinkCmd(index))
}
post(message = msg, isDm = false)
} catch (e: ModuleException) {
@ -146,7 +147,7 @@ class Twitter(bot: Mobibot) : ThreadedModule(bot) {
if (isAutoPost) {
addEntry(index)
if (bot.logger.isDebugEnabled) {
bot.logger.debug("Scheduling {}{} for posting on Twitter.", Constants.LINK_CMD, index + 1)
bot.logger.debug("Scheduling {} for posting on Twitter.", EntriesUtils.buildLinkCmd(index))
}
bot.timer.schedule(TwitterTimer(bot, index), Constants.TIMER_DELAY * 60L * 1000L)
}

View file

@ -138,8 +138,8 @@ class Weather2(bot: Mobibot) : ThreadedModule(bot) {
}
if (cwd.hasWindData()) {
with(cwd.windData) {
if (this != null && hasSpeed()) {
messages.add(NoticeMessage("Wind: ${wind(speed)}"))
if (this != null && hasSpeed() && speed != null) {
messages.add(NoticeMessage("Wind: ${wind(speed!!)}"))
}
}
}
@ -148,7 +148,9 @@ class Weather2(bot: Mobibot) : ThreadedModule(bot) {
val list = cwd.weatherList
if (list != null) {
for (w in list) {
condition.append(' ').append(w!!.getDescription().capitalize()).append('.')
if (w != null) {
condition.append(' ').append(w.getDescription().capitalize()).append('.')
}
}
messages.add(NoticeMessage(condition.toString()))
}
@ -182,9 +184,9 @@ class Weather2(bot: Mobibot) : ThreadedModule(bot) {
return messages
}
private fun wind(w: Double?): String {
val kmh = w!! * 1.60934
return "${Math.round(w)} mph, ${kmh.roundToInt()} km/h"
private fun wind(w: Double): String {
val kmh = w * 1.60934
return "${w.roundToInt()} mph, ${kmh.roundToInt()} km/h"
}
}

View file

@ -1,5 +1,5 @@
/*
* LocalProperties.java
* LocalProperties.kt
*
* Copyright (c) 2004-2020, Erik C. Thauvin (erik@thauvin.net)
* All rights reserved.
@ -29,54 +29,47 @@
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.thauvin.erik.mobibot
package net.thauvin.erik.mobibot;
import org.apache.commons.lang3.StringUtils;
import org.testng.annotations.BeforeSuite;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import org.testng.annotations.BeforeSuite
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
/**
* The <code>properties</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-08
* @since 1.0
* Access to `local.properties`.
*/
public class LocalProperties {
private static final Properties localProps = new Properties();
public static String getProperty(final String key) {
if (localProps.containsKey(key)) {
return localProps.getProperty(key);
} else {
final String env = System.getenv(keyToEnv(key));
if (env != null) {
localProps.setProperty(key, env);
}
return env;
}
}
private static String keyToEnv(final String key) {
return StringUtils.upperCase(key.replace('-', '_'));
}
open class LocalProperties {
@BeforeSuite(alwaysRun = true)
public void loadProperties() {
final Path localPath = Paths.get("local.properties");
fun loadProperties() {
val localPath = Paths.get("local.properties")
if (Files.exists(localPath)) {
try (final InputStream stream = Files.newInputStream(localPath)) {
localProps.load(stream);
} catch (IOException ignore) {
try {
Files.newInputStream(localPath).use { stream -> localProps.load(stream) }
} catch (ignore: IOException) {
// Do nothing
}
}
}
companion object {
private val localProps = Properties()
fun getProperty(key: String): String {
return if (localProps.containsKey(key)) {
localProps.getProperty(key)
} else {
val env = System.getenv(keyToEnv(key))
if (env != null) {
localProps.setProperty(key, env)
}
env
}
}
private fun keyToEnv(key: String): String {
return key.replace('-', '_').toUpperCase()
}
}
}

View file

@ -1,100 +0,0 @@
/*
* EntryLinkTest.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.entries;
import com.rometools.rome.feed.synd.SyndCategory;
import org.testng.annotations.Test;
import java.security.SecureRandom;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The <code>EntryUtilsTest</code> class.
*
* @author <a href="https://erik.thauvin.net/" target="_blank">Erik C. Thauvin</a>
* @created 2019-04-19
* @since 1.0
*/
public class EntryLinkTest {
private final EntryLink entryLink = new EntryLink("https://www.mobitopia.org/", "Mobitopia", "Skynx",
"JimH", "#mobitopia", List.of("tag1", "tag2", "tag3", "TAG4", "Tag5"));
@Test
public void testAddDeleteComment() {
int i = 0;
for (; i < 5; i++) {
entryLink.addComment("c" + i, "u" + i);
}
i = 0;
for (final EntryComment comment : entryLink.getComments()) {
assertThat(comment.getComment()).as("getComment(" + i + ')').isEqualTo("c" + i);
assertThat(comment.getNick()).as("getNick(" + i + ')').isEqualTo("u" + i);
i++;
}
final SecureRandom r = new SecureRandom();
while (entryLink.getCommentsCount() > 0) {
entryLink.deleteComment(r.nextInt(entryLink.getCommentsCount()));
}
assertThat(entryLink.hasComments()).as("hasComments()").isFalse();
entryLink.addComment("nothing", "nobody");
entryLink.setComment(0, "something", "somebody");
assertThat(entryLink.getComment(0).getNick()).as("getNick(somebody)").isEqualTo("somebody");
assertThat(entryLink.getComment(0).getComment()).as("getComment(something)").isEqualTo("something");
}
@Test
public void testTags() {
final List<SyndCategory> tags = entryLink.getTags();
int i = 0;
for (final SyndCategory tag : tags) {
assertThat(tag.getName()).as("tag.getName(" + i + ')').isEqualTo("tag" + (i + 1));
i++;
}
entryLink.setTags("-tag5");
entryLink.setTags("+mobitopia");
entryLink.setTags("tag4");
entryLink.setTags("-mobitopia");
assertThat(entryLink.getPinboardTags()).as("getPinboardTags()")
.isEqualTo(entryLink.getNick() + ",tag1,tag2,tag3,tag4,mobitopia");
}
}

View file

@ -0,0 +1,92 @@
/*
* EntryLinkTest.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.entries
import com.rometools.rome.feed.synd.SyndCategory
import org.assertj.core.api.Assertions
import org.testng.annotations.Test
import java.security.SecureRandom
/**
* The `EntryUtilsTest` class.
*
* @author [Erik C. Thauvin](https://erik.thauvin.net/)
* @created 2019-04-19
* @since 1.0
*/
class EntryLinkTest {
private val entryLink = EntryLink(
"https://www.mobitopia.org/", "Mobitopia", "Skynx", "JimH", "#mobitopia",
listOf("tag1", "tag2", "tag3", "TAG4", "Tag5")
)
@Test
fun testAddDeleteComment() {
var i = 0
while (i < 5) {
entryLink.addComment("c$i", "u$i")
i++
}
Assertions.assertThat(entryLink.comments.size).`as`("getComments().size() == 5").isEqualTo(i)
i = 0
for (comment in entryLink.comments) {
Assertions.assertThat(comment.comment).`as`("getComment($i)").isEqualTo("c$i")
Assertions.assertThat(comment.nick).`as`("getNick($i)").isEqualTo("u$i")
i++
}
val r = SecureRandom()
while (entryLink.comments.size > 0) {
entryLink.deleteComment(r.nextInt(entryLink.comments.size))
}
Assertions.assertThat(entryLink.hasComments()).`as`("hasComments()").isFalse
entryLink.addComment("nothing", "nobody")
entryLink.setComment(0, "something", "somebody")
Assertions.assertThat(entryLink.getComment(0).nick).`as`("getNick(somebody)").isEqualTo("somebody")
Assertions.assertThat(entryLink.getComment(0).comment).`as`("getComment(something)").isEqualTo("something")
}
@Test
fun testTags() {
val tags: List<SyndCategory> = entryLink.tags
for ((i, tag) in tags.withIndex()) {
Assertions.assertThat(tag.name).`as`("tag.getName($i)").isEqualTo("tag" + (i + 1))
}
Assertions.assertThat(entryLink.tags.size).`as`("getTags().size() is 5").isEqualTo(5)
Assertions.assertThat(entryLink.hasTags()).`as`("hasTags() is true").isTrue
entryLink.setTags("-tag5")
entryLink.setTags("+mobitopia")
entryLink.setTags("tag4")
entryLink.setTags("-mobitopia")
Assertions.assertThat(entryLink.pinboardTags).`as`("getPinboardTags()")
.isEqualTo(entryLink.nick + ",tag1,tag2,tag3,tag4,mobitopia")
}
}