Skip to content

Commit

Permalink
Feature: Remind Command (#1708)
Browse files Browse the repository at this point in the history
Co-authored-by: Zickles <zicklesistaken@gmail.com>
Co-authored-by: Cal <cwolfson58@gmail.com>
Co-authored-by: hannibal2 <24389977+hannibal00212@users.noreply.github.com>
  • Loading branch information
4 people authored Aug 26, 2024
1 parent 9ac4617 commit 8852ced
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import at.hannibal2.skyhanni.features.misc.MarkedPlayerManager
import at.hannibal2.skyhanni.features.misc.discordrpc.DiscordRPCManager
import at.hannibal2.skyhanni.features.misc.limbo.LimboTimeTracker
import at.hannibal2.skyhanni.features.misc.massconfiguration.DefaultConfigFeatures
import at.hannibal2.skyhanni.features.misc.reminders.ReminderManager
import at.hannibal2.skyhanni.features.misc.update.UpdateManager
import at.hannibal2.skyhanni.features.misc.visualwords.VisualWordGui
import at.hannibal2.skyhanni.features.rift.area.westvillage.VerminTracker
Expand Down Expand Up @@ -170,6 +171,7 @@ object Commands {
{ DefaultConfigFeatures.onCommand(it) },
DefaultConfigFeatures::onComplete,
)
registerCommand("shremind", "Set a reminder for yourself") { ReminderManager.command(it) }
registerCommand("shwords", "Opens the config list for modifying visual words") { openVisualWords() }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ public class MiscConfig {
@Accordion
public PatcherCoordsWaypointConfig patcherCoordsWaypoint = new PatcherCoordsWaypointConfig();

@Expose
@ConfigOption(name = "Reminders", desc = "")
@Accordion
public RemindersConfig reminders = new RemindersConfig();

@Expose
@ConfigOption(name = "Show Outside SkyBlock", desc = "Show these features outside of SkyBlock.")
@ConfigEditorDraggableList
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package at.hannibal2.skyhanni.config.features.misc;

import com.google.gson.annotations.Expose;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider;
import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;

public class RemindersConfig {
@Expose
@ConfigOption(name = "Auto Delete Reminders", desc = "Automatically deletes reminders after they have been shown once.")
@ConfigEditorBoolean
public boolean autoDeleteReminders = false;

@Expose
@ConfigOption(
name = "Reminder Interval",
desc = "The interval in minutes in which reminders are shown again, after they have been shown once."
)
@ConfigEditorSlider(minValue = 0f, maxValue = 60f, minStep = 1f)
public float interval = 5f;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.hannibal2.skyhanni.config.storage;

import at.hannibal2.skyhanni.features.misc.reminders.Reminder;
import at.hannibal2.skyhanni.features.misc.visualwords.VisualWord;
import at.hannibal2.skyhanni.utils.LorenzVec;
import at.hannibal2.skyhanni.utils.tracker.SkyHanniTracker;
Expand Down Expand Up @@ -50,4 +51,7 @@ public class Storage {

@Expose
public List<String> blacklistedUsers = new ArrayList<>();

@Expose
public Map<String, Reminder> reminders = new HashMap<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package at.hannibal2.skyhanni.features.misc.reminders

import at.hannibal2.skyhanni.utils.SimpleTimeMark
import com.google.gson.annotations.Expose
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
import kotlin.time.Duration

data class Reminder(
@Expose var reason: String,
@Expose var remindAt: SimpleTimeMark,
@Expose var lastReminder: SimpleTimeMark = SimpleTimeMark.farPast(),
) {

fun formatShort(): String {
val time = getRemindTime()
val date = time.toLocalDate()
if (date.isEqual(LocalDate.now())) {
return time.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withDefaultLocale())
}
return date.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withDefaultLocale())
}

fun formatFull(): String = getRemindTime().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withDefaultLocale())

fun shouldRemind(interval: Duration) = remindAt.isInPast() && lastReminder.passedSince() >= interval

private fun getRemindTime(): ZonedDateTime = Instant.ofEpochMilli(remindAt.toMillis()).atZone(ZoneId.systemDefault())

private fun DateTimeFormatter.withDefaultLocale(): DateTimeFormatter = withLocale(Locale.getDefault())
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package at.hannibal2.skyhanni.features.misc.reminders

import at.hannibal2.skyhanni.SkyHanniMod
import at.hannibal2.skyhanni.events.SecondPassedEvent
import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule
import at.hannibal2.skyhanni.utils.ChatUtils
import at.hannibal2.skyhanni.utils.SimpleTimeMark
import at.hannibal2.skyhanni.utils.StringUtils
import at.hannibal2.skyhanni.utils.TimeUtils
import at.hannibal2.skyhanni.utils.TimeUtils.format
import at.hannibal2.skyhanni.utils.TimeUtils.minutes
import at.hannibal2.skyhanni.utils.chat.Text
import at.hannibal2.skyhanni.utils.chat.Text.asComponent
import at.hannibal2.skyhanni.utils.chat.Text.center
import at.hannibal2.skyhanni.utils.chat.Text.command
import at.hannibal2.skyhanni.utils.chat.Text.fitToChat
import at.hannibal2.skyhanni.utils.chat.Text.hover
import at.hannibal2.skyhanni.utils.chat.Text.send
import at.hannibal2.skyhanni.utils.chat.Text.style
import at.hannibal2.skyhanni.utils.chat.Text.suggest
import at.hannibal2.skyhanni.utils.chat.Text.wrap
import net.minecraft.util.EnumChatFormatting
import net.minecraft.util.IChatComponent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@SkyHanniModule
object ReminderManager {

private const val REMINDERS_PER_PAGE = 10

// Random numbers chosen, this will be used to delete the old list and action messages
private const val REMINDERS_LIST_ID = -546745
private const val REMINDERS_ACTION_ID = -546746
private const val REMINDERS_MESSAGE_ID = -546747

private val storage get() = SkyHanniMod.feature.storage.reminders
private val config get() = SkyHanniMod.feature.misc.reminders

private var listPage = 1

private fun getSortedReminders() = storage.entries.sortedBy { it.value.remindAt }

private fun sendMessage(message: String) = Text.join("§e[Reminder]", " ", message).send(REMINDERS_ACTION_ID)

private fun createDivider() = Text.HYPHEN.fitToChat().style {
strikethrough = true
color = EnumChatFormatting.BLUE
}

private fun parseDuration(text: String): Duration? = try {
val duration = TimeUtils.getDuration(text)
if (duration <= 1.seconds) null else duration
} catch (e: Exception) {
null
}

private fun listReminders(page: Int) {
val reminders = getSortedReminders()
val maxPage = (reminders.size + REMINDERS_PER_PAGE - 1) / REMINDERS_PER_PAGE

listPage = page.coerceIn(0, maxPage)

val text: MutableList<IChatComponent> = mutableListOf()

text.add(createDivider())

text.add(
Text.join(
if (listPage > 1) "§6§l<<".asComponent {
hover = "§eClick to view page ${listPage - 1}".asComponent()
command = "/shremind list ${listPage - 1}"
} else null,
" ",
"§6Reminders (Page $listPage of $maxPage)",
" ",
if (listPage < maxPage) "§6§l>>".asComponent {
hover = "§eClick to view page ${listPage + 1}".asComponent()
command = "/shremind list ${listPage + 1}"
} else null,
).center(),
)

if (reminders.isNotEmpty()) {
for (i in (listPage - 1) * REMINDERS_PER_PAGE until listPage * REMINDERS_PER_PAGE) {
if (i >= reminders.size) break
val (id, reminder) = reminders[i]

text.add(
Text.join(
"§c✕".asComponent {
hover = "§7Click to remove".asComponent()
command = "/shremind remove -l $id"
}.wrap("§8[", "§8]"),
" ",
"§e✎".asComponent {
hover = "§7Click to start editing".asComponent()
suggest = "/shremind edit -l $id ${reminder.reason} "
}.wrap("§8[", "§8]"),
" ",
"§6${reminder.formatShort()}".asComponent {
hover = "§7${reminder.formatFull()}".asComponent()
}.wrap("§8[", "§8]"),
" ",
"§7${reminder.reason}",
),
)
}
} else {
text.add(Text.EMPTY)
text.add("§cNo reminders found.".asComponent().center())
text.add(Text.EMPTY)
}

text.add(createDivider())

Text.join(*text.toTypedArray(), separator = Text.NEWLINE).send(REMINDERS_LIST_ID)
}

private fun createReminder(args: Array<String>) {
if (args.size < 2) return help()

val time = parseDuration(args.first()) ?: return ChatUtils.userError("Invalid time format")
val reminder = args.drop(1).joinToString(" ")
val remindAt = SimpleTimeMark.now().plus(time)

storage[StringUtils.generateRandomId()] = Reminder(reminder, remindAt)
sendMessage("§6Reminder set for ${time.format()}")
}

private fun actionReminder(
args: List<String>,
command: String,
vararg arguments: String,
action: (List<String>, Reminder) -> String,
) {
val argumentText = arguments.joinToString(" ")
if (args.size < arguments.size) return ChatUtils.userError("/shremind $command $argumentText")

if (args.first() == "-l") {
if (args.size < arguments.size + 1) return ChatUtils.userError("/shremind $command -l $argumentText")
if (storage[args.drop(1).first()] == null) return ChatUtils.userError("Reminder not found!")
action(args.drop(2), storage[args.drop(1).first()]!!).apply {
listReminders(listPage)
sendMessage(this)
}
} else if (storage[args.first()] == null) {
return ChatUtils.userError("Reminder not found!")
} else {
sendMessage(action(args.drop(1), storage[args.first()]!!))
}
}

private fun removeReminder(args: List<String>) = actionReminder(
args,
"remove",
"[id]",
) { _, reminder ->
storage.values.remove(reminder)
"§cReminder deleted."
}

private fun editReminder(args: List<String>) = actionReminder(
args,
"edit",
"[id]",
"[reminder]",
) { arguments, reminder ->
reminder.reason = arguments.joinToString(" ")
"§6Reminder edited."
}

private fun moveReminder(args: List<String>) = actionReminder(
args,
"move",
"[id]",
"[time]",
) { arguments, reminder ->
val time = parseDuration(arguments.first()) ?: return@actionReminder "§cInvalid time format!"
reminder.remindAt = SimpleTimeMark.now().plus(time)
"§6Reminder moved to ${time.format()}"
}

private fun help() {
createDivider().send()
"§6SkyHanni Reminder Commands:".asComponent().send()
"§e/shremind <time> <reminder> - §bCreates a new reminder".asComponent().send()
"§e/shremind list <page> - §bLists all reminders".asComponent().send()
"§e/shremind remove <id> - §bRemoves a reminder".asComponent().send()
"§e/shremind edit <id> <reminder> - §bEdits a reminder".asComponent().send()
"§e/shremind move <id> <time> - §bMoves a reminder".asComponent().send()
"§e/shremind help - §bShows this help message".asComponent().send()
createDivider().send()
}

@SubscribeEvent
fun onSecondPassed(event: SecondPassedEvent) {
val remindersToSend = mutableListOf<IChatComponent>()

for ((id, reminder) in getSortedReminders()) {
if (!reminder.shouldRemind(config.interval.minutes)) continue
reminder.lastReminder = SimpleTimeMark.now()
var actionsComponent: IChatComponent? = null

if (!config.autoDeleteReminders) {
actionsComponent = Text.join(
" ",
"§a✔".asComponent {
hover = "§7Click to dismiss".asComponent()
command = "/shremind remove $id"
}.wrap("§8[", "§8]"),
" ",
"§e§l⟳".asComponent {
hover = "§7Click to move".asComponent()
suggest = "/shremind move $id 1m"
}.wrap("§8[", "§8]"),
)
} else {
storage.remove(id)
}

remindersToSend.add(
Text.join(
"§e[Reminder]".asComponent {
hover = "§7Reminders by SkyHanni".asComponent()
},
actionsComponent,
" ",
"§6${reminder.reason}",
),
)
}

if (remindersToSend.isNotEmpty()) {
val id = if (config.autoDeleteReminders) 0 else REMINDERS_MESSAGE_ID
Text.join(remindersToSend, separator = Text.NEWLINE).send(id)
}
}

fun command(args: Array<String>) = when (args.firstOrNull()) {
"list" -> listReminders(args.drop(1).firstOrNull()?.toIntOrNull() ?: 1)
"remove", "delete" -> removeReminder(args.drop(1))
"edit", "update" -> editReminder(args.drop(1))
"move" -> moveReminder(args.drop(1))
"help" -> help()
else -> createReminder(args)
}
}

0 comments on commit 8852ced

Please sign in to comment.