Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Remind Command #1708

Merged
merged 35 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3849842
base
Zickles Mar 9, 2024
ca7ca87
cleanup code
CalMWolfs Mar 10, 2024
beae2fd
move file
CalMWolfs Mar 10, 2024
bd450ed
add saving reminders
CalMWolfs Mar 10, 2024
838a0dd
Merge branch 'beta2' into fork/remindcommand
ThatGravyBoat May 4, 2024
8da0337
Merge cleanup
ThatGravyBoat May 4, 2024
c764cb3
Finish remind command
ThatGravyBoat May 5, 2024
28e20d3
Remove extra space and fix error if reminders is empty while listing
ThatGravyBoat May 5, 2024
7d1c075
Make time displayed in local format, disallow negative duration
ThatGravyBoat May 6, 2024
785d77c
Make reminders hide their old messages, make min time 0
ThatGravyBoat May 6, 2024
3d48f34
Move base62 to utilities package
ThatGravyBoat May 6, 2024
c999136
Backend: Text API
ThatGravyBoat May 6, 2024
543827b
Merge branch 'backend/text-utils' into feat/remind-command
ThatGravyBoat May 6, 2024
8b967ff
Merge remote-tracking branch 'origin/feat/remind-command' into feat/r…
ThatGravyBoat May 6, 2024
1dff78d
Merge branch 'beta' into feat/remind-command
ThatGravyBoat May 6, 2024
1f85029
Merge branch 'beta-main' into feat/remind-command
ThatGravyBoat May 8, 2024
fe4fc1e
Merge branch 'beta' into feat/remind-command
ThatGravyBoat May 13, 2024
dc7f638
Merge branch 'beta' into feat/remind-command
ThatGravyBoat May 14, 2024
147c074
Cleanup merge
ThatGravyBoat May 14, 2024
84f9d5c
Merge branch 'refs/heads/beta' into fork/ThatGravyBoat/feat/remind-co…
CalMWolfs Jun 6, 2024
be61f6f
fix merge
CalMWolfs Jun 6, 2024
df4acf8
Merge branch 'beta' into feat/remind-command
ThatGravyBoat Jun 10, 2024
552bfc2
Fix style
ThatGravyBoat Jun 10, 2024
f811ab0
Merge branch 'refs/heads/main-beta' into feat/remind-command
ThatGravyBoat Jun 16, 2024
06dc783
Merge branch 'refs/heads/beta' into fork/ThatGravyBoat/feat/remind-co…
hannibal002 Aug 23, 2024
2737223
formatting
hannibal002 Aug 23, 2024
7379c51
code cleanup
hannibal002 Aug 23, 2024
675ab64
Switch to UUID.randomUUID() and make help response default if no valu…
ThatGravyBoat Aug 23, 2024
a9b0de4
Merge remote-tracking branch 'origin/feat/remind-command' into feat/r…
ThatGravyBoat Aug 23, 2024
00aee7e
Fix reminders not being checked if a previous one couldn't be reminde…
ThatGravyBoat Aug 23, 2024
0c3e790
removed storage check & use property access instead of setters for style
ThatGravyBoat Aug 23, 2024
a7db602
Merge branch 'refs/heads/beta' into fork/ThatGravyBoat/feat/remind-co…
hannibal002 Aug 26, 2024
3101b68
using StringUtils
hannibal002 Aug 26, 2024
72d6357
wording
hannibal002 Aug 26, 2024
0b14cc6
formatting
hannibal002 Aug 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,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 @@ -169,6 +170,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,34 @@
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.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 = Instant.ofEpochMilli(remindAt.toMillis()).atZone(ZoneId.systemDefault())
val date = time.toLocalDate()
if (date.isEqual(LocalDate.now())) {
return time.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.getDefault()))
}
return date.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(Locale.getDefault()))
}

fun formatFull(): String {
val dateTime = Instant.ofEpochMilli(remindAt.toMillis()).atZone(ZoneId.systemDefault())
return dateTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(Locale.getDefault()))
}

fun shouldRemind(interval: Duration) = remindAt.isInPast() && lastReminder.passedSince() >= interval
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
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.Base62
import at.hannibal2.skyhanni.utils.ChatUtils
import at.hannibal2.skyhanni.utils.SimpleTimeMark
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.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 newId(): String {
ThatGravyBoat marked this conversation as resolved.
Show resolved Hide resolved
var id: String
do {
id = Base62.encode((Math.random() * Int.MAX_VALUE).toInt())
} while (storage.containsKey(id))
ThatGravyBoat marked this conversation as resolved.
Show resolved Hide resolved
return id
}

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 {
setStrikethrough(true)
setColor(EnumChatFormatting.BLUE)
}

private fun parseReminder(text: String) = 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 ChatUtils.userError("/shremind [time] [reminder]")

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

storage[newId()] = 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 = parseReminder(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)) break
ThatGravyBoat marked this conversation as resolved.
Show resolved Hide resolved
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)
ThatGravyBoat marked this conversation as resolved.
Show resolved Hide resolved
}

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)
}
}
25 changes: 25 additions & 0 deletions src/main/java/at/hannibal2/skyhanni/utils/Base62.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package at.hannibal2.skyhanni.utils

object Base62 {

private const val ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
private const val BASE = 62

fun encode(num: Int): String {
var n = num
val sb = StringBuilder()
while (n > 0) {
sb.append(ALPHABET[(n % BASE)])
n /= BASE
}
return sb.reverse().toString()
}

fun decode(str: String): Int {
var num = 0
for (c in str) {
num = num * BASE + ALPHABET.indexOf(c)
}
return num
}
}
Loading