Compare commits
No commits in common. "fix/casual-fixes" and "master" have entirely different histories.
fix/casual
...
master
10 changed files with 25 additions and 413 deletions
|
@ -32,7 +32,7 @@ val ownerID = 372703841151614976L
|
|||
group = "net.moonleay.bedge"
|
||||
version = System.getenv("CI_COMMIT_TAG")?.let { "$it-${System.getenv("CI_COMMIT_SHORT_SHA")}-prod" }
|
||||
?: System.getenv("CI_COMMIT_SHORT_SHA")?.let { "$it-dev" }
|
||||
?: "0.0.9"
|
||||
?: "0.0.8"
|
||||
|
||||
val kordver = "1.5.9-SNAPSHOT"
|
||||
val coroutinesver = "1.7.3"
|
||||
|
|
|
@ -20,21 +20,16 @@ package net.moonleay.bedge
|
|||
|
||||
import com.kotlindiscord.kord.extensions.ExtensibleBot
|
||||
import dev.kord.common.entity.PresenceStatus
|
||||
import dev.kord.core.behavior.interaction.response.respond
|
||||
import dev.kord.core.event.gateway.ReadyEvent
|
||||
import dev.kord.core.event.interaction.ButtonInteractionCreateEvent
|
||||
import dev.kord.core.on
|
||||
import dev.kord.gateway.Intent
|
||||
import dev.kord.gateway.PrivilegedIntent
|
||||
import kotlinx.coroutines.Job
|
||||
import net.moonleay.bedge.buttons.component.EditButtonManager
|
||||
import net.moonleay.bedge.data.CredentialManager
|
||||
import net.moonleay.bedge.data.database.DB
|
||||
import net.moonleay.bedge.features.WakeupFeature
|
||||
import net.moonleay.bedge.util.Logger
|
||||
import net.moonleay.bedge.extensions.*
|
||||
import net.moonleay.bedge.util.EmbedColor
|
||||
import net.moonleay.bedge.util.MessageUtil
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
object Bot {
|
||||
|
@ -84,7 +79,6 @@ object Bot {
|
|||
add(::AwakeExtension)
|
||||
add(::ProfileExtension)
|
||||
add(::TopExtension)
|
||||
add(::RequestStreakFix)
|
||||
// add(::ShopExtension)
|
||||
}
|
||||
|
||||
|
@ -104,38 +98,6 @@ object Bot {
|
|||
} */
|
||||
}
|
||||
|
||||
// Register button presses
|
||||
bot.kordRef.on<ButtonInteractionCreateEvent> {
|
||||
val inter = this.interaction
|
||||
val u = inter.user
|
||||
Logger.out("Button interaction: ${inter.componentId} from ${u.asUser().username}#${u.asUser().discriminator}")
|
||||
if (inter.componentId.startsWith("public.edit.")) {
|
||||
val response = inter.deferPublicMessageUpdate()
|
||||
val g = this.interaction.getOriginalInteractionResponse().getGuild()
|
||||
for (b in EditButtonManager.buttons) {
|
||||
if (b.id != inter.componentId)
|
||||
continue
|
||||
b.onInteraction(inter, response, g, u)
|
||||
return@on
|
||||
}
|
||||
return@on
|
||||
}
|
||||
if (inter.componentId.startsWith("public.message.")) {
|
||||
val response = inter.deferPublicResponse()
|
||||
response.respond {
|
||||
this.embeds = mutableListOf(
|
||||
MessageUtil.getEmbed(
|
||||
EmbedColor.ERROR,
|
||||
"404: Not Found",
|
||||
"Could not find button with id \"${inter.componentId}\"." +
|
||||
"\nPlease report this.",
|
||||
u.asUser().username
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bot.kordRef.on<ReadyEvent> {
|
||||
// run when init'd
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package net.moonleay.bedge.buttons.component
|
||||
|
||||
import net.moonleay.bedge.buttons.streakfix.*
|
||||
|
||||
object EditButtonManager {
|
||||
val buttons = listOf(
|
||||
VouchEditButton(),
|
||||
DeclineEditButton()
|
||||
)
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package net.moonleay.bedge.buttons.component
|
||||
|
||||
import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
|
||||
import dev.kord.core.entity.Guild
|
||||
import dev.kord.core.entity.User
|
||||
import dev.kord.core.entity.interaction.ButtonInteraction
|
||||
|
||||
interface IEditButton {
|
||||
|
||||
val id: String
|
||||
suspend fun onInteraction(
|
||||
interaction: ButtonInteraction,
|
||||
response: PublicMessageInteractionResponseBehavior,
|
||||
guild: Guild,
|
||||
user: User
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
package net.moonleay.bedge.buttons.streakfix
|
||||
|
||||
import dev.kord.common.entity.Snowflake
|
||||
import dev.kord.core.behavior.edit
|
||||
import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
|
||||
import dev.kord.core.entity.Guild
|
||||
import dev.kord.core.entity.User
|
||||
import dev.kord.core.entity.channel.MessageChannel
|
||||
import dev.kord.core.entity.interaction.ButtonInteraction
|
||||
import dev.kord.rest.builder.message.EmbedBuilder
|
||||
import dev.kord.rest.builder.message.modify.embed
|
||||
import net.moonleay.bedge.Bot
|
||||
import net.moonleay.bedge.buttons.component.IEditButton
|
||||
import net.moonleay.bedge.data.database.repository.UserRepository
|
||||
import net.moonleay.bedge.util.EmbedUtil
|
||||
import net.moonleay.bedge.util.Logger
|
||||
import net.moonleay.bedge.util.MessageUtil
|
||||
|
||||
class DeclineEditButton : IEditButton {
|
||||
override val id: String = "public.edit.btn.streakfix.decline"
|
||||
|
||||
override suspend fun onInteraction(
|
||||
interaction: ButtonInteraction,
|
||||
response: PublicMessageInteractionResponseBehavior,
|
||||
guild: Guild,
|
||||
user: User
|
||||
) {
|
||||
val m = interaction.message
|
||||
val eb = MessageUtil.getAClonedEmbed(m.embeds[0])
|
||||
var shouldEditButton = false
|
||||
if (!m.embeds[0].footer?.text?.endsWith("[ongoing]")!!){
|
||||
Logger.out("This streakfix is not ongoing")
|
||||
return
|
||||
}
|
||||
val member = interaction.user.asMember(guild.id)
|
||||
// if (m.embeds[0].footer?.text?.contains(member.username)!!){
|
||||
// Logger.out("cannot vote for own streakfix")
|
||||
// return
|
||||
// }
|
||||
if (m.embeds[0].fields[0].value.contains(user.id.value.toString())) {
|
||||
// remove the user from the 1st list in the embed
|
||||
Logger.out("Removing ${user.username} from the 1st list in the embed")
|
||||
eb.fields = EmbedUtil.replaceXWithYinValuesAtTable(user.id.value.toString(), "", eb, 1).fields
|
||||
shouldEditButton = true
|
||||
}
|
||||
if (!m.embeds[0].fields[1].value.contains(user.id.value.toString())) {
|
||||
// Add the user to the list in the embed
|
||||
Logger.out("Adding ${user.username} to the 2nd list in the embed")
|
||||
eb.fields = EmbedUtil.addXToValuesAtTable(user.id.value.toString(), eb, 2).fields
|
||||
shouldEditButton = true
|
||||
}
|
||||
if (m.embeds[0].fields[1].value.contains(user.id.value.toString())) {
|
||||
// Remove the user from all tables
|
||||
Logger.out("Removing ${user.username} from the 2nd list in the embed")
|
||||
eb.fields = EmbedUtil.replaceXWithYinValuesAtTable(user.id.value.toString(), "", eb, 2).fields
|
||||
shouldEditButton = true
|
||||
}
|
||||
//Check if the vote is over
|
||||
val vouchers = EmbedUtil.getAmountOfUsersInTableX(eb, 1)
|
||||
val decliners = EmbedUtil.getAmountOfUsersInTableX(eb, 2)
|
||||
Logger.out("Vouchers: $vouchers, Decliners: $decliners")
|
||||
val target = m.embeds[0].footer?.text!!.split("{")[1].split("}")[0].toLong()
|
||||
val targetData = UserRepository.getUserByID(target)!!
|
||||
Logger.out("Target user = $target")
|
||||
val canUpdate = targetData.isAwake && targetData.currentStreak == 0
|
||||
val minvotes = 3
|
||||
if (vouchers >= minvotes || decliners >= minvotes){
|
||||
Logger.out("Voting is over")
|
||||
eb.footer?.text = m.embeds[0].footer?.text?.replace("ongoing", "voting over").toString()
|
||||
if (vouchers >= minvotes && canUpdate) {
|
||||
Logger.out("Vouchers won")
|
||||
val amountOfDays = m.embeds[0].author!!.name!!.split(": ")[1].toInt()
|
||||
targetData.currentStreak = amountOfDays
|
||||
if (targetData.longestStreak < amountOfDays)
|
||||
targetData.longestStreak = amountOfDays
|
||||
UserRepository.update(targetData)
|
||||
eb.description += "\n**The vote was successful, the streak was fixed.**"
|
||||
}
|
||||
else {
|
||||
Logger.out("Decliners won")
|
||||
eb.description += "\n**The vote was unsuccessful, the streak was not fixed.**"
|
||||
}
|
||||
shouldEditButton = true
|
||||
}
|
||||
|
||||
if (!canUpdate) {
|
||||
eb.footer?.text = m.embeds[0].footer?.text?.replace("ongoing", "voting over").toString()
|
||||
eb.description += "\n**The vote was canceled, because a new streak was started.**"
|
||||
shouldEditButton = true
|
||||
}
|
||||
if (shouldEditButton) {
|
||||
// update the message
|
||||
Bot.bot.kordRef.getChannelOf<MessageChannel>(interaction.channelId)!!.getMessage(m.id).edit {
|
||||
this.embed(fun EmbedBuilder.() {
|
||||
author = eb.author
|
||||
color = eb.color
|
||||
title = eb.title
|
||||
description = eb.description
|
||||
fields = eb.fields
|
||||
footer = eb.footer
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
package net.moonleay.bedge.buttons.streakfix
|
||||
|
||||
import dev.kord.core.behavior.edit
|
||||
import dev.kord.core.behavior.interaction.response.PublicMessageInteractionResponseBehavior
|
||||
import dev.kord.core.entity.Guild
|
||||
import dev.kord.core.entity.User
|
||||
import dev.kord.core.entity.channel.MessageChannel
|
||||
import dev.kord.core.entity.interaction.ButtonInteraction
|
||||
import dev.kord.rest.builder.message.EmbedBuilder
|
||||
import dev.kord.rest.builder.message.modify.embed
|
||||
import net.moonleay.bedge.Bot
|
||||
import net.moonleay.bedge.buttons.component.IEditButton
|
||||
import net.moonleay.bedge.data.database.repository.UserRepository
|
||||
import net.moonleay.bedge.util.EmbedUtil
|
||||
import net.moonleay.bedge.util.Logger
|
||||
import net.moonleay.bedge.util.MessageUtil
|
||||
|
||||
class VouchEditButton: IEditButton {
|
||||
override val id: String = "public.edit.btn.streakfix.vouch"
|
||||
override suspend fun onInteraction(
|
||||
interaction: ButtonInteraction,
|
||||
response: PublicMessageInteractionResponseBehavior,
|
||||
guild: Guild,
|
||||
user: User
|
||||
) {
|
||||
val m = interaction.message
|
||||
val eb = MessageUtil.getAClonedEmbed(m.embeds[0])
|
||||
var shouldEditButton = false
|
||||
if (!m.embeds[0].footer?.text?.endsWith("[ongoing]")!!){
|
||||
Logger.out("This streakfix is not ongoing")
|
||||
return
|
||||
}
|
||||
val member = interaction.user.asMember(guild.id)
|
||||
// if (m.embeds[0].footer?.text?.contains(member.username)!!){
|
||||
// Logger.out("cannot vote for own streakfix")
|
||||
// return
|
||||
// }
|
||||
// do the checks and update
|
||||
if (m.embeds[0].fields[0].value.contains(user.id.value.toString())) {
|
||||
// remove the user from the 1st list in the embed
|
||||
Logger.out("Removing ${user.username} from the 1st list in the embed")
|
||||
eb.fields = EmbedUtil.replaceXWithYinValuesAtTable(user.id.value.toString(), "", eb, 1).fields
|
||||
shouldEditButton = true
|
||||
}
|
||||
if (m.embeds[0].fields[1].value.contains(user.id.value.toString())) {
|
||||
Logger.out("Removing ${user.username} from the 2nd list in the embed")
|
||||
eb.fields = EmbedUtil.replaceXWithYinValuesAtTable(user.id.value.toString(), "", eb, 2).fields
|
||||
shouldEditButton = true
|
||||
}
|
||||
if (!m.embeds[0].fields[0].value.contains(user.id.value.toString())) {
|
||||
//Add the user to the list in the embed
|
||||
Logger.out("Adding ${user.username} to the 1st list in the embed")
|
||||
eb.fields = EmbedUtil.addXToValuesAtTable(user.id.value.toString(), eb, 1).fields
|
||||
shouldEditButton = true
|
||||
}
|
||||
//Check if the vote is over
|
||||
val vouchers = EmbedUtil.getAmountOfUsersInTableX(eb, 1)
|
||||
val decliners = EmbedUtil.getAmountOfUsersInTableX(eb, 2)
|
||||
Logger.out("Vouchers = $vouchers, Decliners = $decliners")
|
||||
val target = m.embeds[0].footer?.text!!.split("{")[1].split("}")[0].toLong()
|
||||
val targetData = UserRepository.getUserByID(target)!!
|
||||
Logger.out("Target user = $target")
|
||||
val canUpdate = targetData.isAwake && targetData.currentStreak == 0
|
||||
val minvotes = 3
|
||||
if ((vouchers >= minvotes || decliners >= minvotes) && canUpdate){
|
||||
Logger.out("Voting is over")
|
||||
eb.footer?.text = m.embeds[0].footer?.text?.replace("ongoing", "voting over").toString()
|
||||
if (vouchers >= minvotes) {
|
||||
Logger.out("Vouchers won")
|
||||
val amountOfDays = m.embeds[0].author!!.name!!.split(": ")[1].toInt()
|
||||
targetData.currentStreak = amountOfDays
|
||||
if (targetData.longestStreak < amountOfDays)
|
||||
targetData.longestStreak = amountOfDays
|
||||
UserRepository.update(targetData)
|
||||
eb.description += "\n**The vote was successful, the streak was fixed.**"
|
||||
}
|
||||
else {
|
||||
Logger.out("Decliners won")
|
||||
eb.description += "\n**The vote was unsuccessful, the streak was not fixed.**"
|
||||
}
|
||||
shouldEditButton = true
|
||||
}
|
||||
|
||||
if (!canUpdate) {
|
||||
eb.footer?.text = m.embeds[0].footer?.text?.replace("ongoing", "voting over").toString()
|
||||
eb.description += "\n**The vote was canceled, because a new streak was started.**"
|
||||
shouldEditButton = true
|
||||
}
|
||||
|
||||
|
||||
// Finish the update
|
||||
if (shouldEditButton) {
|
||||
// update the message
|
||||
Bot.bot.kordRef.getChannelOf<MessageChannel>(interaction.channelId)!!.getMessage(m.id).edit {
|
||||
this.embed(fun EmbedBuilder.() {
|
||||
author = eb.author
|
||||
color = eb.color
|
||||
title = eb.title
|
||||
description = eb.description
|
||||
fields = eb.fields
|
||||
footer = eb.footer
|
||||
author?.name = eb.author!!.name
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package net.moonleay.bedge.extensions
|
||||
|
||||
import com.kotlindiscord.kord.extensions.commands.Arguments
|
||||
import com.kotlindiscord.kord.extensions.commands.application.slash.converters.impl.numberChoice
|
||||
import com.kotlindiscord.kord.extensions.extensions.Extension
|
||||
import com.kotlindiscord.kord.extensions.extensions.publicSlashCommand
|
||||
import com.kotlindiscord.kord.extensions.types.respond
|
||||
import dev.kord.core.behavior.interaction.followup.edit
|
||||
import dev.kord.rest.builder.message.create.actionRow
|
||||
import dev.kord.rest.builder.message.create.embed
|
||||
import dev.kord.rest.builder.message.modify.embed
|
||||
import net.moonleay.bedge.data.database.repository.UserRepository
|
||||
import net.moonleay.bedge.util.*
|
||||
|
||||
class RequestStreakFix : Extension() {
|
||||
|
||||
override val name = "requeststreakfix"
|
||||
override val allowApplicationCommandInDMs: Boolean
|
||||
get() = false
|
||||
|
||||
|
||||
override suspend fun setup() {
|
||||
publicSlashCommand(::RequestStreakFixArguments) {
|
||||
name = "requeststreakfix"
|
||||
description = "Request a streak fix. This will be voted on. atleast 3 votes are needed to fix the streak."
|
||||
|
||||
this.action {
|
||||
val target = this.user.asUser()
|
||||
val wantedStreak = this.arguments.amountOfDays
|
||||
if (!UserRepository.doesUserExist(target.id.value)) {
|
||||
this.respond {
|
||||
this.embeds.add(
|
||||
MessageUtil.getEmbed(
|
||||
EmbedColor.ERROR,
|
||||
"User not found!",
|
||||
"You cant fix what ain't broken. You did not use the bot yet, so there is no information about you.",
|
||||
target.username,
|
||||
)
|
||||
)
|
||||
}
|
||||
return@action
|
||||
}
|
||||
val tData = UserRepository.getUserByID(target.id.value)!!
|
||||
if (!tData.isAwake) {
|
||||
this.respond {
|
||||
this.embeds.add(
|
||||
MessageUtil.getEmbed(
|
||||
EmbedColor.ERROR,
|
||||
"You are already asleep!",
|
||||
"You can't fix a streak if you are already asleep.",
|
||||
target.username,
|
||||
)
|
||||
)
|
||||
}
|
||||
return@action
|
||||
}
|
||||
if (tData.currentStreak != 0) {
|
||||
this.respond {
|
||||
this.embeds.add(
|
||||
MessageUtil.getEmbed(
|
||||
EmbedColor.ERROR,
|
||||
"Your streak is not broken!",
|
||||
"You can't fix a streak if it is not broken.\n(Your current streak has to be 0.)",
|
||||
target.username,
|
||||
)
|
||||
)
|
||||
}
|
||||
return@action
|
||||
}
|
||||
val eb = MessageUtil.getEmbedWithTable(
|
||||
EmbedColor.INFO,
|
||||
"Fix ${target.username}s streak",
|
||||
"${target.mention} wants to fix their streak. Please vote on this request.\n" +
|
||||
"The wanted streak is ${wantedStreak}.",
|
||||
mapOf(
|
||||
"Vouched" to listOf(),
|
||||
"Declined" to listOf(),
|
||||
)
|
||||
)
|
||||
val msg = this.respond {
|
||||
this.content = "@everyone"
|
||||
this.embed {
|
||||
this.author {
|
||||
this.name = "Requested days: $wantedStreak"
|
||||
}
|
||||
this.color = eb.color
|
||||
this.title = eb.title
|
||||
this.description = eb.description
|
||||
this.fields = eb.fields
|
||||
this.footer {
|
||||
this.icon = target.avatar?.cdnUrl?.toUrl()
|
||||
this.text = "requested by ${target.username} {${target.id.value}} [ongoing]"
|
||||
}
|
||||
}
|
||||
|
||||
this.actionRow {
|
||||
this.components.addAll(EmbedUtil.getStreakFixButtons().components)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class RequestStreakFixArguments : Arguments() {
|
||||
|
||||
val amountOfDays by numberChoice {
|
||||
this.name = "amountofdays"
|
||||
this.description = "The desired amount of days for the streak."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ class TimeExtension : Extension() {
|
|||
lateinit var ud: UserData
|
||||
|
||||
// This should be correct;
|
||||
val timeToSleep = timeToWake.toEpochSecond() * 1000 - System.currentTimeMillis() - 1000 * 60 * 60
|
||||
val timeToSleep = timeToWake.toEpochSecond() * 1000 - System.currentTimeMillis()
|
||||
|
||||
if (UserRepository.doesUserExist(u.id.value)) {
|
||||
// Update existing user
|
||||
|
|
|
@ -24,14 +24,32 @@ import dev.kord.rest.builder.component.ActionRowBuilder
|
|||
import dev.kord.rest.builder.message.EmbedBuilder
|
||||
|
||||
object EmbedUtil {
|
||||
fun getStreakFixButtons(): ActionRowBuilder {
|
||||
fun getTimePlannerButtons(): ActionRowBuilder {
|
||||
val ar = ActionRowBuilder()
|
||||
ar.interactionButton(ButtonStyle.Success, "public.edit.btn.streakfix.vouch") {
|
||||
this.label = "Vouch"
|
||||
ar.interactionButton(ButtonStyle.Success, "public.edit.btn.timemanagement.available") {
|
||||
this.label = "Available"
|
||||
}
|
||||
ar.interactionButton(ButtonStyle.Danger, "public.edit.btn.streakfix.decline") {
|
||||
this.label = "Decline"
|
||||
ar.interactionButton(ButtonStyle.Primary, "public.edit.btn.timemanagement.maybeavailable") {
|
||||
this.label = "May be available"
|
||||
}
|
||||
ar.interactionButton(ButtonStyle.Danger, "public.edit.btn.timemanagement.notavailable") {
|
||||
this.label = "Not available"
|
||||
}
|
||||
return ar
|
||||
}
|
||||
|
||||
fun getMatchButtons(): ActionRowBuilder {
|
||||
val ar = ActionRowBuilder()
|
||||
ar.interactionButton(ButtonStyle.Success, "public.edit.btn.matchmanagement.accept") {
|
||||
this.label = "I'm in!"
|
||||
}
|
||||
ar.interactionButton(ButtonStyle.Danger, "public.edit.btn.matchmanagement.decline") {
|
||||
this.label = "I'm out!"
|
||||
}
|
||||
/*
|
||||
ar.interactionButton(ButtonStyle.Secondary, "public.edit.btn.matchmanagement.cancel") {
|
||||
this.label = "Cancel this match..."
|
||||
} */
|
||||
return ar
|
||||
}
|
||||
|
||||
|
@ -62,13 +80,6 @@ object EmbedUtil {
|
|||
return ebbb
|
||||
}
|
||||
|
||||
fun getAmountOfUsersInTableX(e: EmbedBuilder, table: Int): Int {
|
||||
val f = e.fields[table - 1]
|
||||
if (!f.value.contains("@"))
|
||||
return 0
|
||||
return f.value.split("\n").toMutableList().size
|
||||
}
|
||||
|
||||
fun getAllUsersInTheFirstXTables(amountOfTables: Int, e: Embed): List<String> {
|
||||
val users = mutableListOf<String>()
|
||||
for (i in 0 until amountOfTables - 1) {
|
||||
|
|
|
@ -75,11 +75,6 @@ object MessageUtil {
|
|||
val ebb = EmbedBuilder()
|
||||
ebb.color = e.color
|
||||
ebb.title = e.title
|
||||
ebb.author {
|
||||
this.icon = e.author?.iconUrl
|
||||
this.name = e.author?.name.toString()
|
||||
this.url = e.author?.url
|
||||
}
|
||||
e.fields.forEach {
|
||||
val fb = EmbedBuilder.Field()
|
||||
fb.name = it.name
|
||||
|
@ -87,10 +82,6 @@ object MessageUtil {
|
|||
fb.inline = it.inline
|
||||
ebb.fields.add(fb)
|
||||
}
|
||||
ebb.footer {
|
||||
this.icon = e.footer?.iconUrl
|
||||
this.text = e.footer?.text.toString()
|
||||
}
|
||||
ebb.description = e.description
|
||||
return ebb
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue