Autoclose settings

This commit is contained in:
rxdn 2021-07-06 17:13:08 +01:00
parent 02ec5c8a90
commit 1bbd4a785b
8 changed files with 314 additions and 52 deletions

View File

@ -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
}

View File

@ -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)
}

View 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>

View File

@ -46,6 +46,7 @@
border-color: #2e3136 !important;
border-left: none;
color: white;
z-index: 2;
}
:global(.svelte-emoji-picker__trigger:active) {

View File

@ -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>

View 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);
}

View File

@ -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() {

View File

@ -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>