Autoclose settings
This commit is contained in:
parent
02ec5c8a90
commit
1bbd4a785b
@ -1,14 +1,25 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/database"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
// time.Duration marshals to nanoseconds, custom impl to marshal to seconds
|
||||
type autoCloseBody struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
SinceOpenWithNoResponse int64 `json:"since_open_with_no_response"`
|
||||
SinceLastMessage int64 `json:"since_last_message"`
|
||||
OnUserLeave bool `json:"on_user_leave"`
|
||||
}
|
||||
|
||||
func GetAutoClose(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
settings, err := database.Client.AutoClose.Get(guildId); if err != nil {
|
||||
settings, err := dbclient.Client.AutoClose.Get(guildId)
|
||||
if err != nil {
|
||||
ctx.AbortWithStatusJSON(500, gin.H{
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
@ -16,5 +27,41 @@ func GetAutoClose(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, settings)
|
||||
ctx.JSON(200, convertToAutoCloseBody(settings))
|
||||
}
|
||||
|
||||
func convertToAutoCloseBody(settings database.AutoCloseSettings) (body autoCloseBody) {
|
||||
body.Enabled = settings.Enabled
|
||||
|
||||
if settings.SinceOpenWithNoResponse != nil {
|
||||
body.SinceOpenWithNoResponse = int64(*settings.SinceOpenWithNoResponse / time.Second)
|
||||
}
|
||||
|
||||
if settings.SinceLastMessage != nil {
|
||||
body.SinceLastMessage = int64(*settings.SinceLastMessage / time.Second)
|
||||
}
|
||||
|
||||
if settings.OnUserLeave != nil {
|
||||
body.OnUserLeave = *settings.OnUserLeave
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func convertFromAutoCloseBody(body autoCloseBody) (settings database.AutoCloseSettings) {
|
||||
settings.Enabled = body.Enabled
|
||||
|
||||
if body.SinceOpenWithNoResponse > 0 {
|
||||
duration := time.Second * time.Duration(body.SinceOpenWithNoResponse)
|
||||
settings.SinceOpenWithNoResponse = &duration
|
||||
}
|
||||
|
||||
if body.SinceLastMessage > 0 {
|
||||
duration := time.Second * time.Duration(body.SinceLastMessage)
|
||||
settings.SinceLastMessage = &duration
|
||||
}
|
||||
|
||||
settings.OnUserLeave = &body.OnUserLeave
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -1,36 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/TicketsBot/GoPanel/botcontext"
|
||||
dbclient "github.com/TicketsBot/GoPanel/database"
|
||||
"github.com/TicketsBot/database"
|
||||
"github.com/TicketsBot/GoPanel/rpc"
|
||||
"github.com/TicketsBot/GoPanel/utils"
|
||||
"github.com/TicketsBot/common/premium"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
var maxDays = 90
|
||||
var maxLength = time.Hour * 24 * time.Duration(maxDays)
|
||||
|
||||
func PostAutoClose(ctx *gin.Context) {
|
||||
guildId := ctx.Keys["guildid"].(uint64)
|
||||
|
||||
var settings database.AutoCloseSettings
|
||||
if err := ctx.BindJSON(&settings); err != nil {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
var body autoCloseBody
|
||||
if err := ctx.BindJSON(&body); err != nil {
|
||||
ctx.JSON(400, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if settings.Enabled && (settings.SinceLastMessage == nil || settings.SinceOpenWithNoResponse == nil || settings.OnUserLeave == nil) {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
"success": false,
|
||||
"error": "No time period provided",
|
||||
})
|
||||
settings := convertFromAutoCloseBody(body)
|
||||
|
||||
// get premium
|
||||
botContext, err := botcontext.ContextForGuild(guildId)
|
||||
if err != nil {
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
if (settings.SinceOpenWithNoResponse != nil && *settings.SinceOpenWithNoResponse < 0) || (settings.SinceLastMessage != nil && *settings.SinceLastMessage < 0) {
|
||||
ctx.AbortWithStatusJSON(400, gin.H{
|
||||
"success": false,
|
||||
"error": "Negative time period provided",
|
||||
})
|
||||
premiumTier := rpc.PremiumClient.GetTierByGuildId(guildId, true, botContext.Token, botContext.RateLimiter)
|
||||
|
||||
if premiumTier < premium.Premium {
|
||||
settings.SinceOpenWithNoResponse = nil
|
||||
settings.SinceLastMessage = nil
|
||||
}
|
||||
|
||||
// Time period cannot be negative, convertFromAutoCloseBody will not allow
|
||||
|
||||
if (settings.SinceOpenWithNoResponse != nil && *settings.SinceOpenWithNoResponse > maxLength) ||
|
||||
(settings.SinceLastMessage != nil && *settings.SinceLastMessage > maxLength) {
|
||||
ctx.JSON(400, utils.ErrorStr("Time period cannot be longer than %d days", maxDays))
|
||||
return
|
||||
}
|
||||
|
||||
@ -41,14 +53,9 @@ func PostAutoClose(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
if err := dbclient.Client.AutoClose.Set(guildId, settings); err != nil {
|
||||
ctx.AbortWithStatusJSON(500, gin.H{
|
||||
"success": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
ctx.JSON(500, utils.ErrorJson(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, gin.H{
|
||||
"success": true,
|
||||
})
|
||||
ctx.JSON(200, utils.SuccessResponse)
|
||||
}
|
||||
|
117
frontend/src/components/form/Duration.svelte
Normal file
117
frontend/src/components/form/Duration.svelte
Normal file
@ -0,0 +1,117 @@
|
||||
<div class="col">
|
||||
<div class="row label">
|
||||
<div class="parent">
|
||||
<label class="form-label">{label}</label>
|
||||
{#if badge !== undefined}
|
||||
<div class="badge" style="margin-left: 4px">{badge}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row fields">
|
||||
<div class="parent">
|
||||
<input class="form-input" type="number" min=0 {disabled} bind:value={days}/>
|
||||
<div class="period" class:disabled>D</div>
|
||||
</div>
|
||||
|
||||
<div class="parent">
|
||||
<input class="form-input" type="number" min=0 {disabled} bind:value={hours}/>
|
||||
<div class="period" class:disabled>H</div>
|
||||
</div>
|
||||
|
||||
<div class="parent">
|
||||
<input class="form-input" type="number" min=0 {disabled} bind:value={minutes}/>
|
||||
<div class="period" class:disabled>M</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
export let label;
|
||||
export let badge;
|
||||
export let disabled = false; // note: bind:disabled isn't valid
|
||||
|
||||
export let days = 0;
|
||||
export let hours = 0;
|
||||
export let minutes = 0;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fields > .parent:not(:first-child) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
width: 100px;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.period.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.parent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.period {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-color: #2e3136 !important;
|
||||
background-color: #2e3136 !important;
|
||||
color: white !important;
|
||||
outline: none;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
padding: 0 10px;
|
||||
margin: 0 0 0.5em 0;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
:global(.badge) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
background-color: #3472f7;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
</style>
|
@ -46,6 +46,7 @@
|
||||
border-color: #2e3136 !important;
|
||||
border-left: none;
|
||||
color: white;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
:global(.svelte-emoji-picker__trigger:active) {
|
||||
|
@ -4,39 +4,117 @@
|
||||
</span>
|
||||
|
||||
<div slot="body" class="body-wrapper">
|
||||
<div class="alert danger">
|
||||
<span class="alert-text">
|
||||
<span>
|
||||
This feature is currently disabled. Discord will soon be releasing message threads,
|
||||
which will incorporate their own auto-close behaviour. Thank you for your patience.
|
||||
</span>
|
||||
</span>
|
||||
<form class="form-wrapper" on:submit|preventDefault={submit}>
|
||||
<div class="row do-margin">
|
||||
<Checkbox col4={true} label="Enabled" bind:value={data.enabled}/>
|
||||
<Checkbox col4={true} label="Close On User Leave" bind:value={data.on_user_leave}/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-2" style="flex-direction: row">
|
||||
<Duration label="Since Open With No Response" badge="Premium" disabled={!isPremium}
|
||||
bind:days={sinceOpenDays} bind:hours={sinceOpenHours} bind:minutes={sinceOpenMinutes}/>
|
||||
</div>
|
||||
<div class="col-2" style="flex-direction: row">
|
||||
<Duration label="Since Last Message" badge="Premium" disabled={!isPremium}
|
||||
bind:days={sinceLastDays} bind:hours={sinceLastHours} bind:minutes={sinceLastMinutes}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<Button icon="fas fa-paper-plane" fullWidth=true>Submit</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<style>
|
||||
.body-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.alert {
|
||||
.form-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
padding: 10px 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background-color: #fc727a;
|
||||
.row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.alert-text {
|
||||
width: 95%
|
||||
.form-wrapper > .row:not(:last-child) {
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import Card from "../Card.svelte";
|
||||
import Checkbox from "../form/Checkbox.svelte";
|
||||
import {notifyError, notifySuccess, withLoadingScreen} from "../../js/util";
|
||||
import axios from "axios";
|
||||
import {API_URL} from "../../js/constants";
|
||||
import Duration from "../form/Duration.svelte";
|
||||
import {toDays, toHours, toMinutes} from "../../js/timeutil";
|
||||
import Button from "../Button.svelte";
|
||||
|
||||
export let guildId;
|
||||
|
||||
let data = {};
|
||||
let isPremium = false;
|
||||
|
||||
let sinceOpenDays = 0, sinceOpenHours = 0, sinceOpenMinutes = 0;
|
||||
let sinceLastDays = 0, sinceLastHours = 0, sinceLastMinutes = 0;
|
||||
|
||||
async function submit() {
|
||||
data.since_open_with_no_response = sinceOpenDays * 86400 + sinceOpenHours * 3600 + sinceOpenMinutes * 60;
|
||||
data.since_last_message = sinceLastDays * 86400 + sinceLastHours * 3600 + sinceLastMinutes * 60;
|
||||
|
||||
const res = await axios.post(`${API_URL}/api/${guildId}/autoclose`, data);
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
notifySuccess('Auto close settings updated successfully');
|
||||
}
|
||||
|
||||
async function loadPremium() {
|
||||
const res = await axios.get(`${API_URL}/api/${guildId}/premium`);
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
isPremium = res.data.premium;
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
const res = await axios.get(`${API_URL}/api/${guildId}/autoclose`);
|
||||
if (res.status !== 200) {
|
||||
notifyError(res.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
data = res.data
|
||||
update(res.data);
|
||||
}
|
||||
|
||||
function update(data) {
|
||||
if (data.since_open_with_no_response) {
|
||||
sinceOpenDays = toDays(data.since_open_with_no_response);
|
||||
sinceOpenHours = toHours(data.since_open_with_no_response);
|
||||
sinceOpenMinutes = toMinutes(data.since_open_with_no_response);
|
||||
}
|
||||
|
||||
if (data.since_last_message) {
|
||||
sinceLastDays = toDays(data.since_last_message);
|
||||
sinceLastHours = toHours(data.since_last_message);
|
||||
sinceLastMinutes = toMinutes(data.since_last_message);
|
||||
}
|
||||
}
|
||||
|
||||
withLoadingScreen(async () => {
|
||||
await loadPremium();
|
||||
await loadSettings();
|
||||
});
|
||||
</script>
|
||||
|
11
frontend/src/js/timeutil.js
Normal file
11
frontend/src/js/timeutil.js
Normal file
@ -0,0 +1,11 @@
|
||||
export function toDays(value) {
|
||||
return Math.floor(value / 86400);
|
||||
}
|
||||
|
||||
export function toHours(value) {
|
||||
return Math.floor((value % 86400) / 3600);
|
||||
}
|
||||
|
||||
export function toMinutes(value) {
|
||||
return Math.floor((value % 86400 % 3600) / 60);
|
||||
}
|
@ -258,7 +258,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
panels = res.data;
|
||||
// convert button_style to string
|
||||
panels = res.data.map((p) => Object.assign({}, p, {button_style: p.button_style.toString()}));
|
||||
}
|
||||
|
||||
async function loadMultiPanels() {
|
||||
|
@ -1,15 +1,15 @@
|
||||
<div class="content">
|
||||
<div class="main-col">
|
||||
<div class="card">
|
||||
<SettingsCard guildId={guildId}/>
|
||||
<SettingsCard {guildId}/>
|
||||
</div>
|
||||
<div class="card">
|
||||
<AutoCloseCard/>
|
||||
<AutoCloseCard {guildId}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-col">
|
||||
<div class="card">
|
||||
<ClaimsCard guildId={guildId}/>
|
||||
<ClaimsCard {guildId}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user